/// <summary> /// Initializes a new instance of the <see cref="NPCUnit"/> class. /// </summary> /// <param name="id"> The id of the unit. </param> /// <param name="name"> The name. </param> /// <param name="position"> The position of the unit. </param> /// <param name="sprite"> The sprite. </param> /// <param name="unitClass"> The class of the unit. </param> public NPCUnit(int id, string name, Vector position, AnimatedSprite sprite, UnitClass unitClass) : base(id, name, position, sprite, unitClass) { }
/// <summary> /// Finds the shortest path between the start vector and the goal vector, /// taking into account whether the unit can actually get there or not. /// A* pathfinding. /// </summary> /// <param name="start"> The vector to start the search from. </param> /// <param name="goal"> The vector to end the search at. </param> /// <param name="unit"> The unit that is trying to find the route. </param> /// <param name="dataMap"> The data map. </param> /// <returns> /// A list representing the shortest path. /// </returns> public static List<Vector> FindShortestPathWithinReach(Vector start, Vector goal, Unit unit, MapData dataMap) { // Set stuff up var closedList = new List<Vector>(); var openList = new List<Vector>(); var openListQueue = new PriorityQueue<Vector, int>(); var newStart = start; newStart.CameFrom = null; newStart.GScore = 0; newStart.FScore = newStart.GScore + CostEstimate(newStart, goal); openList.Add(newStart); openListQueue.Enqueue(newStart); if (newStart.Equals(goal)) { return openList; } // While the queue isn't empty ... while (!openListQueue.Empty) { // ... get highest prioritied point var current = openListQueue.Dequeue(); openList.Remove(current); if (current.Equals(goal)) { return RetracePath(current); } closedList.Add(current); // Iterates over nearby points foreach (var neighbor in NeighborNodes(current)) { // If the point has been visited, ignore it if (closedList.Contains(neighbor)) { continue; } // If it hasn't been visited and the unit can reach it, add it // to the list. if (!openList.Contains(neighbor) && dataMap.WithinMapAndCanTraverse(neighbor.X, neighbor.Y, unit) && CostEstimate(unit.Location, neighbor) <= unit.MoveRange) { neighbor.CameFrom = current; neighbor.GScore = current.GScore + 1; neighbor.FScore = newStart.GScore + CostEstimate(neighbor, goal); openList.Add(neighbor); openListQueue.Enqueue(neighbor); } } } return openList; }
/// <summary> /// Finds the shortest path for the unit from the unit's position /// to the target vector. /// </summary> /// <param name="unit"> The unit. </param> /// <param name="target"> The target position. </param> /// <returns> /// A list containing the Vectors that make up the shortest path from /// the unit's position to the vector. /// </returns> public List<Vector> FindShortestPathWithinReach(Unit unit, Vector target) { var start = new Vector(unit.Location.X, unit.Location.Y); return PathFinding.FindShortestPathWithinReach(start, target, unit, this.dataMap); }
/// <summary> /// Calculates the steps it takes to move from start to target. /// </summary> /// <param name="start"> The start location. </param> /// <param name="target"> The target location. </param> /// <returns> /// The amount of steps it takes. /// </returns> private static int CostEstimate(Vector start, Vector target) { var temp = new Vector(start.X, start.Y); var cost = 0; while (!temp.Equals(target)) { if (temp.X > target.X) { temp = new Vector(temp.X - 1, temp.Y); cost++; } else if (temp.X < target.X) { temp = new Vector(temp.X + 1, temp.Y); cost++; } if (temp.Y > target.Y) { temp = new Vector(temp.X, temp.Y - 1); cost++; } else if (temp.Y < target.Y) { temp = new Vector(temp.X, temp.Y + 1); cost++; } } return cost; }
/// <summary> /// Traces the path back to construct the shortest route. /// </summary> /// <param name="retraceFrom"> The vector to retrace from. </param> /// <returns> /// A list that represents the shortest path found. /// </returns> private static List<Vector> RetracePath(Vector retraceFrom) { var tempList = new List<Vector>(); var tempNode = retraceFrom.CameFrom; var maxRuns = 0; while (maxRuns < 500) { tempList.Add(tempNode); if (tempNode.CameFrom == null) { break; } tempNode = tempNode.CameFrom; maxRuns++; } return tempList; }
/// <summary> /// Finds the nodes that are just around the input node. /// </summary> /// <param name="node"> The node to find the neighbors for. </param> /// <returns> /// A list of the neighbor nodes. /// </returns> public static List<Vector> NeighborNodes(Vector node) { var neighbors = new List<Vector> { new Vector(node.X - 1, node.Y), new Vector(node.X, node.Y - 1), new Vector(node.X + 1, node.Y), new Vector(node.X, node.Y + 1) }; return neighbors; }
/// <summary> /// Finds the areas the unit can move to. /// </summary> /// <param name="unit"> The unit. </param> /// <param name="dataMapData"> The datamap. </param> /// <returns> /// The list of vectors. /// </returns> public static List<Vector> MarkValidMoves(Unit unit, MapData dataMapData) { var startVector = new Vector(unit.Location.X, unit.Location.Y); var validMovesList = new List<Vector>(); var validMoves = new LinkedList<Vector>(); validMovesList.Add(startVector); validMoves.AddFirst(startVector); // While there are places left to check ... while (validMoves.Count > 0) { // ... get the first move var moveNode = validMoves.First.Value; validMoves.RemoveFirst(); // Find further possible moves foreach (var neighborNode in NeighborNodes(moveNode)) { var tempVector = new Vector(neighborNode.X, neighborNode.Y); // If the point has been visited, ignore it if (validMovesList.Contains(tempVector) || !unit.PointWithinMoveRange(tempVector)) { continue; } // If the unit can move there ... if (dataMapData.WithinMapMoveRangeAndCanTraverse(tempVector.X, tempVector.Y, unit)) { // ... add it to the list. validMoves.AddLast(tempVector); validMovesList.Add(tempVector); } } } return validMovesList; }
/// <summary> /// Creates a new object that is a copy of the current instance. /// </summary> /// <returns> /// A new object that is a copy of this instance. /// </returns> public object Clone() { var tempVector = new Vector(this.X, this.Y) { GScore = this.GScore, FScore = this.FScore, CameFrom = this.CameFrom }; return tempVector; }
/// <summary> /// Moves the selected unit to the clicked location, assuming the location /// is within range of the unit. /// </summary> /// <param name="level"> The level. </param> private void MoveUnit(Level level) { if (InputHandler.LeftMouseClicked() && this.unitSelected && !this.justClicked) { // Note! MouseState.X is the horizontal position and MouseState.Y is the vertical position. // I want Y to be the horizontal coordinate (the width) and X to be the vertical coordinate // (the height), so tileY is using the X values and tileX is using the Y values. var tileY = (int)(InputHandler.MouseState.X + level.Camera.Position.X) / Engine.TileHeight; var tileX = (int)(InputHandler.MouseState.Y + level.Camera.Position.Y) / Engine.TileWidth; var tempVector = new Vector(tileX, tileY); // Checks if the chosen tile is occupied or not. if (!level.LevelMap.IsOccupied(tileX, tileY) && this.selectedUnit.CanTraverse(level.LevelMap.GetCollisionType(tileX, tileY)) && this.selectedUnit.PointWithinMoveRange(tempVector)) { var pathToTarget = level.LevelMap.FindShortestPathWithinReach(this.selectedUnit, tempVector).Count; if (pathToTarget != 0 && pathToTarget <= this.selectedUnit.MoveRange) { // Removes the unit from the list, as we need to change the // position of the unit. this.PlayerUnits.Remove(this.selectedUnit); // Notifies the map that the unit is disappearing from its current // location. level.LevelMap.MoveUnitAway(this.selectedUnit, level); // Changes the unit's location. this.selectedUnit.Location = (Vector)tempVector.Clone(); this.selectedUnit.MoveUnit(); // Notifies the map that the unit has arrived at its target // location and adds the unit to the unit list again. level.LevelMap.MoveUnitToNewPosition(this.selectedUnit); this.PlayerUnits.Add(this.selectedUnit); this.unitSelected = false; this.selectedUnit = null; } } // Ensures that other parts in the update method won't be triggered. this.justClicked = true; } }
/// <summary> /// Checks if a location is within the map, if the location is within the /// move range of the unit and if the unit can traverse the position. /// </summary> /// <param name="x"> The x coordinate to check. </param> /// <param name="y"> The y coordinate to check. </param> /// <param name="unit"> The unit. </param> /// <returns> /// True if the above conditions are fulfilled. /// </returns> public bool WithinMapMoveRangeAndCanTraverse(int x, int y, Unit unit) { var tempVector = new Vector(x, y); if (this.WithinMap(x, y) && unit.PointWithinMoveRange(tempVector)) { var test2 = PathFinding.FindShortestPathWithinReach(unit.Location, tempVector, unit, this); if (unit.CanTraverse(this.GetCollisionType(x, y)) && test2.Count <= unit.MoveRange) { return true; } } return false; }