/// <summary> /// Constructor of an edge /// </summary> /// <param name="node1">node 1 where node 1 != node 2</param> /// <param name="node2">node 2 where node 2 != node 1</param> /// <param name="edgeWeight">Initial weight of the edge ( a higher number is less attractive to the AI )</param> public Edge(Node node1, Node node2, float edgeWeight) { this.node1 = node1; this.node2 = node2; distance = (node1.Position - node2.Position).Length(); //precompute heuristic so that the run-time of the game is spead up weight = edgeWeight; }
/// <summary> /// Method to establish connection to another node. /// </summary> /// <param name="anotherNode">Node not the same instance as the node the method is invoked on</param> /// <param name="weightOfConnection">Weight of the established connection (not the distance, but a higher number will result in the edge being less desirable for the AI</param> /// <param name="distanceToNode">Sets the distance</param> public void connectToNode(Node anotherNode, float weightOfConnection, float distanceToNode) { connectToNode(anotherNode, weightOfConnection); foreach (Edge e in this.connectedEdges) if (e.node1 == anotherNode || e.node2 == anotherNode) e.distance = distanceToNode; }
/// <summary> /// Method to establish connection to another node. /// </summary> /// <param name="anotherNode">Node not the same instance as the node the method is invoked on</param> /// <param name="weightOfConnection">Weight of the established connection (not the distance, but a higher number will result in the edge being less desirable for the AI</param> public void connectToNode(Node anotherNode, float weightOfConnection) { if (anotherNode != this) { foreach (Edge e in this.connectedEdges) if (e.node1 == anotherNode || e.node2 == anotherNode) return; //already connected Edge conn = new Edge(this, anotherNode, weightOfConnection); //Bidirectional edge: connectedEdges.Add(conn); anotherNode.connectedEdges.Add(conn); } }
/// <summary> /// Method to load a map /// </summary> /// <param name="filename">file name of map</param> /// <param name="contentMgr">content manager instance</param> /// <param name="gfxDevice">graphics device instance</param> public static void loadMap(String filename, ContentManager contentMgr, GraphicsDevice gfxDevice) { XmlReader reader = XmlReader.Create(filename); XPathDocument docNav = new XPathDocument(reader); XPathNavigator nav = docNav.CreateNavigator(); XmlNamespaceManager nsmanager = new XmlNamespaceManager(nav.NameTable); XPathNodeIterator iter; XPathNavigator mapIter = nav.SelectSingleNode("/Map"); mapRadius = Convert.ToSingle(mapIter.GetAttribute("mapRadius", nsmanager.DefaultNamespace)); XPathNavigator skyboxIter = nav.SelectSingleNode("/Map/Skybox"); String texName = skyboxIter.GetAttribute("texture", nsmanager.DefaultNamespace); skyBoxRepeat = Convert.ToSingle(skyboxIter.GetAttribute("repeat", nsmanager.DefaultNamespace)); SetUpSkyBox(gfxDevice, contentMgr, texName, Convert.ToString(skyBoxRepeat)); //Now read in path nodes: iter = nav.Select("/Map/Marker[@className!='SpawnPoint' and @className!='PlayerSpawnPoint']"); while (iter.MoveNext()) { Node n = new Node(); readMarkerData(contentMgr, reader, docNav, nav, nsmanager, iter, n); content.Add(n.id, n); } //Read spawnpoints: iter = nav.Select("/Map/Marker[@className='SpawnPoint']"); while (iter.MoveNext()) { SpawnPoint n = new SpawnPoint(); readMarkerData(contentMgr, reader, docNav, nav, nsmanager, iter, n); content.Add(n.id, n); } //Read player spawnpoints: iter = nav.Select("/Map/Marker[@className='PlayerSpawnPoint']"); while (iter.MoveNext()) { SpawnPoint n = new PlayerSpawnPoint(); readMarkerData(contentMgr, reader, docNav, nav, nsmanager, iter, n); content.Add(n.id, n); } //Now read other content: iter = nav.Select("/Map/ContentItem"); while (iter.MoveNext()) { Drawer n = new Drawer(false); n.contentLoader = contentMgr; readObjectData(contentMgr, reader, docNav, nav, nsmanager, iter, n); } List<Edge> edgeList = new List<Edge>(); List<float> edgeDistances = new List<float>(); iter = nav.Select("/Map/PathEdge"); while (iter.MoveNext()) { float weight = Convert.ToSingle(iter.Current.GetAttribute("weight", nsmanager.DefaultNamespace)); float distance = Convert.ToSingle(iter.Current.GetAttribute("distance", nsmanager.DefaultNamespace)); String firstId = iter.Current.SelectSingleNode("firstNodeId").Value; String secondId = iter.Current.SelectSingleNode("secondNodeId").Value; Node first = null, second = null; foreach (Object item in content.Values) if (item is Node) { if ((item as Marker).id == firstId) first = item as Node; else if ((item as Marker).id == secondId) second = item as Node; if (first != null && second != null) break; } edgeList.Add(new Edge(first, second, weight)); edgeDistances.Add(distance); } //Connect nodes: for (int i = 0; i < edgeList.Count; i++) { Edge item = edgeList.ElementAt(i); float distance = edgeDistances.ElementAt(i); item.node1.connectToNode(item.node2, item.weight, distance); } reader.Close(); }
/// <summary> /// Method to disconnect another node /// </summary> /// <param name="otherNode">node to disconnect</param> public void disconnectFromNode(Node otherNode) { if (otherNode == this) return; Edge foundEdge = null; foreach (Edge e in this.connectedEdges) if (e.node1 == otherNode || e.node2 == otherNode) { foundEdge = e; break; } if (foundEdge != null) { foundEdge.node1.connectedEdges.Remove(foundEdge); foundEdge.node2.connectedEdges.Remove(foundEdge); } }
/// <summary> /// A* path finding algorithm. /// </summary> /// <param name="start">Start node</param> /// <param name="end">End node</param> /// <returns>Path to end node if one is found, otherwise null</returns> public List<Node> AStar(Node start, Node end, bool addRandomFactor) { if (start == end || start == null || end == null) return null; List<Node> openList = new List<Node>(); List<Node> visitedList = new List<Node>(); openList.Add(start); start.heuristic = (end.Position - start.Position).Length(); //calculate heuristic while (openList.Count > 0) //while we have more nodes to explore { Node bestChoice = openList.ElementAt<Node>(0); //get the best node choice: foreach (Node node in openList) if (bestChoice.pathCost + bestChoice.heuristic > node.pathCost + node.heuristic) { bestChoice = node; break; } //move best choice to visited list visitedList.Add(bestChoice); bestChoice.hasBeenVisited = true; openList.Remove(bestChoice); if (bestChoice == end) //REACHED DESTINATION!!! { List<Node> result = new List<Node>(); result.Add(end); Node it = (end.edgeToPrevNode.node1 == end) ? end.edgeToPrevNode.node2 : end.edgeToPrevNode.node1; while (it != null) { result.Add(it); if (it.edgeToPrevNode != null) it = (it.edgeToPrevNode.node1 == it) ? it.edgeToPrevNode.node2 : it.edgeToPrevNode.node1; else it = null; } //Finally clear node data for the next iteration of the A*: foreach (Node node in visitedList) node.clear(); return result; } //Not yet at destination so we look at the best choice's neighbours: foreach (Edge neighbourEdge in bestChoice.connectedEdges) { Node neighbour = (neighbourEdge.node1 == bestChoice) ? neighbourEdge.node2 : neighbourEdge.node1; if (neighbour.hasBeenVisited) continue; visitedList.Add(neighbour); Random randomizer = new Random(); double distToNeighbour = bestChoice.pathCost + (addRandomFactor ? neighbourEdge.distance + neighbourEdge.weight + randomizer.NextDouble() * ASTAR_RANDOM_FACTOR * neighbourEdge.distance : neighbourEdge.distance + neighbourEdge.weight); double newMoveLength = distToNeighbour + bestChoice.pathCost; neighbour.heuristic = (neighbour.Position - end.Position).Length(); Boolean shouldMove = false; if (openList.IndexOf(neighbour) < 0) { openList.Add(neighbour); shouldMove = true; } else if (distToNeighbour < neighbour.pathCost) shouldMove = true; else shouldMove = false; if (shouldMove == true) { neighbour.edgeToPrevNode = neighbourEdge; neighbour.pathCost = distToNeighbour; } } } return null; //Not found }
/// <summary> /// Sets a new path for an object if it is already registered /// </summary> /// <param name="AIObject">Any registered dynamic object</param> /// <param name="start">Start node (should be close to the object if possible)</param> /// <param name="end">End node</param> public void setNewPathForRegisteredObject(DynamicObject AIObject, Node start, Node end) { if (objectPaths.Keys.Contains(AIObject)) { List<Node> path = AStar(start, end, AIObject is Destroyer); if (path != null) objectPaths[AIObject].remainingPath = path; else //we just go the the start node { path = new List<Node>(); path.Add(start); objectPaths[AIObject].remainingPath = path; } } }
/// <summary> /// Method to update remaining path variables when an object reaches its waypoint /// </summary> internal void reachedWaypoint() { if (objectRemainingPath != null) //if there is a path { if (objectRemainingPath.Count > 1) //if we have not reached our destination yet { previousNode = objectRemainingPath.Last(); objectRemainingPath.Remove(objectRemainingPath.Last()); currentWaypoint = objectRemainingPath.Last(); calculateCurrentEdge(); } else //the destination have been reached { previousNode = null; objectRemainingPath.Clear(); currentWaypoint = null; currentEdge = null; } } }