/// <summary> Destroy Grid array and creates a new one from properties. </summary> /// <param name="nodeType"> You can specify a custom Node class derived from MapNavNode </param> public override void CreateGrid(System.Type nodeType) { grid = new MapNavNode[mapHorizontalSize * mapVerticalSize]; Vector3 posOffs = new Vector3(-mapHorizontalSize * nodeSize / 2f, 0f, -mapVerticalSize * nodeSize / 2f) + new Vector3(nodeSize / 2f, 0f, nodeSize / 2f); int idx = -1; for (int y = 0; y < mapVerticalSize; y++) { for (int x = 0; x < mapHorizontalSize; x++) { idx++; grid[idx] = (MapNavNode)ScriptableObject.CreateInstance(nodeType); grid[idx].idx = idx; grid[idx].q = x; grid[idx].r = y; grid[idx].parent = transform; grid[idx].h = (nodeHeightOpt == NodeHeightOpt.Flat ? minNodeHeight : Random.Range(minNodeHeight, maxNodeHeight + 1)); grid[idx].localPosition = new Vector3(nodeSize * x, nodeHeightStep * grid[idx].h, nodeSize * y) + posOffs; OnNodeCreated(grid[idx]); } } OnGridChanged(true); }
/// <summary> Return a list of nodes in a specified range around the given node. /// The returned list is in no specific order. Excludes invalid nodes. </summary> /// <param name="node"> The central node. </param> /// <param name="radius"> Radius from central node that ring starts. </param> /// <param name="width"> Width of the ring. </param> /// <param name="callback"> An optional callback that can first check if the node is "valid" and /// return True if so, else it should return False. </param> /// <returns>Returns null if there was an error. </returns> public virtual List <T> NodesRing <T>(MapNavNode node, int radius, int width, ValidationCallback callback) where T : MapNavNode { if (radius < 1) { radius = 1; } if (width < 1) { width = 1; } List <T> all = NodesAround <T>(node, radius + (width - 1), false, callback); List <T> inner = radius > 1 ? NodesAround <T>(node, radius - 1, false, callback) : new List <T>(); if (all == null || inner == null) { return(null); } for (int i = 0; i < inner.Count; i++) { if (all.Contains(inner[i])) { all.Remove(inner[i]); } } return(all); }
// ------------------------------------------------------------------------------------------------------------ /// <summary> Returns the distance from node 1 to node 2. Returns 0 on error. </summary> public virtual int Distance(MapNavNode node1, MapNavNode node2) { if (node1 == null || node2 == null) { return(0); } return(Distance(node1.idx, node2.idx)); }
private void DrawLinkToolMarkMode() { // check what node was clicked on if (Event.current.button == 0 && (Tools.viewTool == ViewTool.Pan || Tools.viewTool == ViewTool.None)) { int nSet = Event.current.modifiers == EventModifiers.Shift ? 1 : 0; if (Event.current.type == EventType.MouseDown || Event.current.type == EventType.MouseDrag) { float rayDist = 0f; Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); if (plane.Raycast(ray, out rayDist)) { Vector3 rayPoint = ray.GetPoint(rayDist); MapNavNode n = mapnav.NodeAtWorldPosition <MapNavNode>(rayPoint); if (n != null) { if (lastMarked != n.idx) { lastMarked = n.idx; if (linkTool_Nodes[nSet].Contains(n.idx)) { linkTool_Nodes[nSet].Remove(n.idx); } else { linkTool_Nodes[nSet].Add(n.idx); } } } } Event.current.Use(); } else if (Event.current.type == EventType.MouseUp) { lastMarked = -1; Event.current.Use(); } } float sz = mapnav.nodeSize / 3f; Handles.color = Color.blue; for (int i = 0; i < linkTool_Nodes[0].Count; i++) { Handles.DrawSolidDisc(mapnav.grid[linkTool_Nodes[0][i]].position, Vector3.up, sz); } sz -= 0.05f; Handles.color = Color.red; for (int i = 0; i < linkTool_Nodes[1].Count; i++) { Handles.DrawSolidDisc(mapnav.grid[linkTool_Nodes[1][i]].position, Vector3.up, sz); } }
/// <summary> This returns a list of nodes around the given node, using the notion of "movement costs" to determine /// if a node should be included in the returned list or not. The callback can be used to specify the "cost" to /// reach the specified node, making this useful to select the nodes that a unit might be able to move to. </summary> /// <param name="node"> The central node. </param> /// <param name="radius"> The maximum area around the node to select nodes from. </param> /// <param name="callback"> An optional callback (pass null to not use it) that can be used to indicate the cost of /// moving from one node to another. By default it will "cost" 1 to move from one node to another. By returning 0 in /// this callback you can indicate that the target node can't be moved to (for example when the tile is occupied). /// Return a value higher than one (like 2 or 3) if moving to the target node would cost more and potentially /// exclude the node from the returned list of nodes (when cost to reach it would be bigger than "radius"). </param> /// <returns> Returns a list of nodes that can be used with grid[]. Returns empty list (not null) if there was an error. </returns> public virtual List <T> NodesAround <T>(MapNavNode node, int radius, NodeCostCallback callback) where T : MapNavNode { List <int> accepted = NodeIndicesAround(node.idx, radius, callback); if (accepted.Count > 0) { List <T> res = new List <T>(); for (int i = 0; i < accepted.Count; i++) { res.Add((T)grid[accepted[i]]); } return(res); } return(new List <T>(0)); }
/// <summary> Override this for notification while the while a grid is being created. The function is called for /// each Node being added to the node. You could use this to add your own initialization data to a custom node /// being created via CreateGrid[T]() </summary> /// <param name="node"> The node that was created. </param> public virtual void OnNodeCreated(MapNavNode node) { }
/// <summary> Returns a list of nodes that represents a path from one node to another. An A* algorithm is used /// to calculate the path. Return an empty list on error or if the destination node can't be reached. </summary> /// <param name="start"> The node where the path should start. </param> /// <param name="end"> The node to reach. </param> /// <param name="callback"> An optional callback that can return an integer value to indicate the /// cost of moving onto the specified node. This callback should return 1 /// for normal nodes and 2+ for higher cost to move onto the node and 0 /// if the node can't be moved onto; for example when the node is occupied. </param> /// <returns> Return an empty list on error or if the destination node can't be reached. </returns> public virtual List <T> Path <T>(MapNavNode start, MapNavNode end, NodeCostCallback callback) where T : MapNavNode { if (start == null || end == null) { return(new List <T>(0)); } if (start.idx == end.idx) { return(new List <T>(0)); } List <T> path = new List <T>(); int current = -1; int next = -1; float new_cost = 0; float next_cost = 0; double priority = 0; // first check if not direct neighbour and get out early List <int> neighbors = PathNodeIndicesAround(start.idx); // NodeIndicesAround(start.idx, false, false, null); if (neighbors != null) { if (neighbors.Contains(end.idx)) { if (callback != null) { next_cost = callback(start, end); if (next_cost >= 1) { path.Add((T)end); } } return(path); } } HeapPriorityQueue <PriorityQueueNode> frontier = new HeapPriorityQueue <PriorityQueueNode>(grid.Length); frontier.Enqueue(new PriorityQueueNode() { idx = start.idx }, 0); Dictionary <int, int> came_from = new Dictionary <int, int>(); // <for_idx, came_from_idx> Dictionary <int, float> cost_so_far = new Dictionary <int, float>(); // <idx, cost> came_from.Add(start.idx, -1); cost_so_far.Add(start.idx, 0); while (frontier.Count > 0) { current = frontier.Dequeue().idx; if (current == end.idx) { break; } neighbors = PathNodeIndicesAround(current); //NodeIndicesAround(current, false, false, null); if (neighbors != null) { for (int i = 0; i < neighbors.Count; i++) { next = neighbors[i]; if (callback != null) { next_cost = callback(grid[current], grid[next]); } if (next_cost <= 0.0f) { continue; } new_cost = cost_so_far[current] + next_cost; if (false == cost_so_far.ContainsKey(next)) { cost_so_far.Add(next, new_cost + 1); } if (new_cost < cost_so_far[next]) { cost_so_far[next] = new_cost; priority = new_cost + Heuristic(start.idx, end.idx, next); frontier.Enqueue(new PriorityQueueNode() { idx = next }, priority); if (false == came_from.ContainsKey(next)) { came_from.Add(next, current); } else { came_from[next] = current; } } } } } // build path next = end.idx; while (came_from.ContainsKey(next)) { if (came_from[next] == -1) { break; } if (came_from[next] == start.idx) { break; } path.Add((T)grid[came_from[next]]); next = came_from[next]; } if (path.Count > 0) { path.Reverse(); path.Add((T)end); } return(path); }
/// <summary> Return list of nodes in a certain range around given node. /// The returned list is in no specific order. Excludes invalid nodes. /// </summary> /// <param name="node"> The central node around which to get neighbouring nodes.</param> /// <param name="range"> The radius around the central node. </param> /// <param name="includeCentralNode">Should the central node be included? It will be added first in the list if so.</param> /// <param name="callback"> An optional callback that can first check if the node is "valid" and return True if /// so, else it should return False. </param> /// <returns>Returns null if there was an error. </returns> public virtual List <T> NodesAround <T>(MapNavNode node, int range, bool includeCentralNode, ValidationCallback callback) where T : MapNavNode { return(null); }
/// <summary> /// Return list of nodes in a certain range around given node. /// The returned list is in no specific order. Excludes invalid nodes. /// </summary> /// <param name="node"> The central node around which to get neighboring nodes.</param> /// <param name="radius"> The radius around the central node. </param> /// <param name="includeCentralNode">Should the central node be included? It will be added first in the list if so.</param> /// <param name="callback"> An optional callback that can first check if the node is "valid" and return True if /// so, else it should return False. </param> /// <returns>Returns null if there was an error. </returns> public override List <T> NodesAround <T>(MapNavNode node, int radius, bool includeCentralNode, ValidationCallback callback) { if (radius < 1) { radius = 1; } if (node == null) { return(null); } if (radius == 1) { return(NodesAround <T>(node, false, includeCentralNode, callback)); } List <T> nodes = new List <T>(0); if (node.idx < 0 || node.idx >= grid.Length) { return(null); } if (includeCentralNode) { if (node.isValid) { if (callback != null) { if (true == callback(node)) { nodes.Add((T)node); } } else { nodes.Add((T)node); } } } for (int x = -radius; x <= radius; x++) { for (int y = -radius; y <= radius; y++) { //if (Mathf.Abs(x + y) > radius) continue; int q = node.q + x; int r = node.r + y; if (q < 0 || r < 0 || q >= mapHorizontalSize || r >= mapVerticalSize) { continue; } int id = (r * mapHorizontalSize + q); if (id == node.idx) { continue; } if (id >= 0 && id < grid.Length) { if (grid[id].isValid) { if (callback != null) { if (false == callback(grid[id])) { continue; } } if (false == nodes.Contains((T)grid[id])) { nodes.Add((T)grid[id]); } } } } } return(nodes); }
/// <summary> /// Returns list of nodes starting at the "next" node and going anti-clockwise around the central node. /// </summary> /// <param name="node"> The central node around which to get neighboring nodes.</param> /// <param name="includeInvalid"> Should "invalid" nodes be included? If so then a NULL entry will be added to the /// returned list for invalid nodes. An invalid node might be one that is stored in /// the grid array but not considered to be in the grid. This normally happens with /// Hexa grids. An invalid node might also be one marked as invalid by the callback function. </param> /// <param name="includeCentralNode">Should the central node be included? It will be added first in the list if so.</param> /// <param name="callback"> An optional callback that can first check if the node is "valid" and return True if /// so, else it should return False. </param> /// <returns>Returns null if there was an error. </returns> public override List <T> NodesAround <T>(MapNavNode node, bool includeInvalid, bool includeCentralNode, ValidationCallback callback) { if (node == null) { return(null); } List <T> nodes = new List <T>(includeCentralNode ? 7 : 6); if (node.idx < 0 || node.idx >= grid.Length) { return(null); } if (includeCentralNode) { if (callback != null) { if (false == node.isValid || false == callback(node)) { if (includeInvalid) { nodes.Add(null); } } else { nodes.Add((T)node); } } else { if (node.isValid) { nodes.Add((T)node); } else if (includeInvalid) { nodes.Add(null); } } } //int[,] neighbours = diagonalNeighbours ? Neighbours8 : Neighbours4; int[,] neighbours = Neighbours8; // always use 8-neighbour for this so that a ring around can be selected. // the special select function witch takes "cost" into account will // take 4-neighbour type selection into account for (int dir = 0; dir < neighbours.GetLength(0); dir++) { int q = grid[node.idx].q + neighbours[dir, 0]; int r = grid[node.idx].r + neighbours[dir, 1]; MapNavNode n = NodeAt <MapNavNode>(q, r); if (n == null) { if (includeInvalid) { nodes.Add(null); } continue; } if (false == n.isValid) { if (includeInvalid) { nodes.Add(null); } continue; } if (callback != null) { if (false == callback(n)) { if (includeInvalid) { nodes.Add(null); } continue; } } nodes.Add((T)n); } return(nodes); }