/* static */ public void AddNodeToOpenList(MovePointBehavior theNode, float costFromPreviousObject, MovePointBehavior previousNode) { float costSoFar = costFromPreviousObject; if (previousNode != null) { costSoFar += previousNode.costSoFar; } theNode.costSoFar = costSoFar; theNode.previousPathNode = previousNode; openList.Add(theNode); }
/// <summary> /// The update function is to catch input and to do the movement of the agent, this is temporary code. for testing purposes only. /// /// Alex Reiss /// </summary> void Update () { if (!currentlyMoving && canMove) { if (pathList == null) pathList = new List<MovePointBehavior>(); if (pathList.Count > 0) { for (int index = 0; index < currentMovePoint.neighborList.Length; index++) { if (currentMovePoint.neighborList[index] == pathList[0]) { pointToMoveTo = currentMovePoint.neighborList[index]; currentlyMoving = true; AudioBehavior.isMoving = true; currentMovementTime = timeToMoveToPoint; } } } } else if(canMove) { if (currentMovementTime < 0.0f) { currentMovePoint = pointToMoveTo; currentMovementTime = 0.0f; transform.position = currentMovePoint.transform.position; currentlyMoving = false; AudioBehavior.isMoving = false; pointToMoveTo = null; pathList.RemoveAt(0); } else { float forTForLerp = (timeToMoveToPoint - currentMovementTime) / timeToMoveToPoint; float forTheChangeInZ = Mathf.Lerp(currentMovePoint.transform.position.z, pointToMoveTo.transform.position.z, forTForLerp); float forTheChangeInX = Mathf.Lerp(currentMovePoint.transform.position.x, pointToMoveTo.transform.position.x, forTForLerp); transform.position = new Vector3(forTheChangeInX, transform.position.y, forTheChangeInZ); currentMovementTime -= Time.deltaTime; } } if (pathList.Count == 0) canMove = false; }
/// <summary> /// Iterates over the Player & Enemy nodes to determine if the specified node is occupied. /// </summary> /// <param name="node"></param> /// <returns></returns> public ActorBehavior GetActorOnNode(MovePointBehavior node) { foreach (ActorBehavior squad in playerTeam) if (squad.currentMovePoint == node) return squad; foreach (ActorBehavior squad in enemyTeam) if (squad.currentMovePoint == node) return squad; foreach (ActorBehavior squad in nuetrals) if (squad.currentMovePoint == node) return squad; return null; }
/// <summary> /// Determines the target for this squad. /// </summary> /// <returns>AI state the controller should enter after determining the target.</returns> public override AIState DetermineMovePoint() { targetActor = null; MovePointBehavior movePoint = Actor.currentMovePoint; // Get a list of spaces that are within attack range (movement + range). CombatSquadBehavior combatSquad = Actor.GetComponent <CombatSquadBehavior>(); if (combatSquad == null) { Debug.LogError(string.Format("AI Squad ({0}) does not have a CombatSquadBehavior!", Actor.transform.name)); Actor.actorHasMovedThisTurn = true; return(AIState.PickingSquad); } int attackRange = combatSquad.Squad.Range; int moveDistance = combatSquad.Squad.Speed; List <MovePointBehavior> pointsInRange = new List <MovePointBehavior>(); movePoint.BuildGraph(attackRange + moveDistance, 0, grid, ref pointsInRange, true); // Find the closest actor in attack range. foreach (MovePointBehavior node in pointsInRange) { ActorBehavior actorOnNode = gameController.GetActorOnNode(node); if (actorOnNode == null || (actorOnNode.theSide != GameControllerBehaviour.UnitSide.player)) { continue; } // If the distance to the target is less than the distance to the selected target, target it instead. float distance = Vector3.Distance(Actor.transform.position, actorOnNode.transform.position); if (distance < distanceToTarget) { targetActor = actorOnNode; distanceToTarget = distance; } } // If a target was found, move toward it. if (targetActor != null) { // Build a path to the actor. List <MovePointBehavior> pathList = movePoint.FindPath(targetActor.currentMovePoint, (moveDistance + attackRange), grid, true); // Remove the target's move point from the grid. pathList.RemoveAt(pathList.Count - 1); // Determine the excess items in the path list. int excess = pathList.Count - moveDistance; if (excess > 0) { pathList.RemoveRange(pathList.Count - excess, excess); } if (pathList.Count > 0) { MovePointBehavior targetPoint = pathList[pathList.Count - 1]; // Determine the fastest path to the target point. pathList = movePoint.FindPath(targetPoint, moveDistance, grid); if (pathList != null) { Actor.pathList = pathList; Actor.canMove = true; grid.ignoreList.Remove(movePoint); grid.ignoreList.Add(targetPoint); Actor.actorHasMovedThisTurn = true; return(AIState.WaitingForMove); } } else { Actor.actorHasMovedThisTurn = true; return(AIState.WaitingForMove); } } else { targetActor = null; distanceToTarget = float.PositiveInfinity; // No target was found, so determine the closest target to move towards. foreach (ActorBehavior playerSquad in gameController.playerTeam) { float distance = Vector3.Distance(Actor.transform.position, playerSquad.transform.position); if (distance < distanceToTarget) { targetActor = playerSquad; distanceToTarget = distance; } } // Retrieve a list of all nodes within movement range. List <MovePointBehavior> nodesInRange = new List <MovePointBehavior>(); movePoint.BuildGraph(moveDistance, 0, grid, ref nodesInRange); // Cast a ray to the target and retrieve the farthest one that is in our movement range. Ray ray = new Ray(Actor.transform.position, (targetActor.transform.position - Actor.transform.position).normalized); // Perform raycasting and store a list of all objects that have been selected. List <RaycastHit> hits = new List <RaycastHit>(); hits.AddRange(Physics.RaycastAll(ray)); // Iterate over the selection list to determine if the player has clicked on one of her squads. foreach (RaycastHit hitInfo in hits.OrderBy(l => l.distance).Reverse()) { MovePointBehavior hitBehavior = hitInfo.transform.GetComponent <MovePointBehavior>(); if (hitBehavior == null || !nodesInRange.Contains(hitBehavior)) { continue; } // Target node has been found! Find a path to it. List <MovePointBehavior> pathList = movePoint.FindPath(hitBehavior, moveDistance, grid); if (pathList != null) { Actor.pathList = pathList; Actor.canMove = true; grid.ignoreList.Remove(movePoint); grid.ignoreList.Add(hitBehavior); Actor.actorHasMovedThisTurn = true; return(AIState.WaitingForMove); } } } Actor.actorHasMovedThisTurn = true; return(AIState.PickingSquad); }
/// <summary> /// The update function is to catch input and to do the movement of the agent, this is temporary code. for testing purposes only. /// </summary> void Update() { if (!currentlyMoving) { if (pathList.Count > 0) { if (pathList[0] == currentMovePoint.neighborList[0]) //if (Input.GetKeyDown(KeyCode.UpArrow) && currentMovePoint.North) { pointToMoveTo = currentMovePoint.neighborList[0]; currentMovementDirection = DirectionOfMovement.North; currentlyMoving = true; currentMovementTime = timeToMoveToPoint; } else if (pathList[0] == currentMovePoint.neighborList[2]) //else if (Input.GetKeyDown(KeyCode.DownArrow) && currentMovePoint.South) { pointToMoveTo = currentMovePoint.neighborList[2]; currentMovementDirection = DirectionOfMovement.South; currentlyMoving = true; currentMovementTime = timeToMoveToPoint; } else if (pathList[0] == currentMovePoint.neighborList[3]) //else if (Input.GetKeyDown(KeyCode.LeftArrow) && currentMovePoint.West) { pointToMoveTo = currentMovePoint.neighborList[3]; currentMovementDirection = DirectionOfMovement.West; currentlyMoving = true; currentMovementTime = timeToMoveToPoint; } else if (pathList[0] == currentMovePoint.neighborList[1]) //else if (Input.GetKeyDown(KeyCode.RightArrow) && currentMovePoint.East) { pointToMoveTo = currentMovePoint.neighborList[1]; currentMovementDirection = DirectionOfMovement.East; currentlyMoving = true; currentMovementTime = timeToMoveToPoint; } } } else { switch (currentMovementDirection) { case DirectionOfMovement.North: if (currentMovementTime < 0.0f) { currentMovePoint = pointToMoveTo; currentMovementDirection = DirectionOfMovement.None; currentMovementTime = 0.0f; transform.position = currentMovePoint.transform.position; currentlyMoving = false; pointToMoveTo = null; pathList.RemoveAt(0); if (GridBehavior.preCombat && pathList.Count == 0) theGrid.startCombat(); } else { float forTForLerp = (timeToMoveToPoint - currentMovementTime) / timeToMoveToPoint; float forTheChangeInZ = Mathf.Lerp(currentMovePoint.transform.position.z, pointToMoveTo.transform.position.z, forTForLerp); transform.position = new Vector3(transform.position.x, transform.position.y, forTheChangeInZ); currentMovementTime -= Time.deltaTime; } break; case DirectionOfMovement.South: if (currentMovementTime < 0.0f) { currentMovePoint = pointToMoveTo; currentMovementDirection = DirectionOfMovement.None; currentMovementTime = 0.0f; transform.position = currentMovePoint.transform.position; currentlyMoving = false; pointToMoveTo = null; pathList.RemoveAt(0); if (GridBehavior.preCombat && pathList.Count == 0) theGrid.startCombat(); } else { float forTForLerp = (timeToMoveToPoint - currentMovementTime) / timeToMoveToPoint; float forTheChangeInZ = Mathf.Lerp(currentMovePoint.transform.position.z, pointToMoveTo.transform.position.z, forTForLerp); transform.position = new Vector3(transform.position.x, transform.position.y, forTheChangeInZ); currentMovementTime -= Time.deltaTime; } break; case DirectionOfMovement.East: if (currentMovementTime < 0.0f) { currentMovePoint = pointToMoveTo; currentMovementDirection = DirectionOfMovement.None; currentMovementTime = 0.0f; transform.position = currentMovePoint.transform.position; currentlyMoving = false; pointToMoveTo = null; pathList.RemoveAt(0); if (GridBehavior.preCombat && pathList.Count == 0) theGrid.startCombat(); } else { float forTForLerp = (timeToMoveToPoint - currentMovementTime) / timeToMoveToPoint; float forTheChangeInX = Mathf.Lerp(currentMovePoint.transform.position.x, pointToMoveTo.transform.position.x, forTForLerp); transform.position = new Vector3(forTheChangeInX, transform.position.y, transform.position.z); currentMovementTime -= Time.deltaTime; } break; case DirectionOfMovement.West: if (currentMovementTime < 0.0f) { currentMovePoint = pointToMoveTo; currentMovementDirection = DirectionOfMovement.None; currentMovementTime = 0.0f; transform.position = currentMovePoint.transform.position; currentlyMoving = false; pointToMoveTo = null; pathList.RemoveAt(0); if (GridBehavior.preCombat && pathList.Count == 0) theGrid.startCombat(); } else { float forTForLerp = (timeToMoveToPoint - currentMovementTime) / timeToMoveToPoint; float forTheChangeInX = Mathf.Lerp(currentMovePoint.transform.position.x, pointToMoveTo.transform.position.x, forTForLerp); transform.position = new Vector3(forTheChangeInX, transform.position.y, transform.position.z); currentMovementTime -= Time.deltaTime; } break; } } }
/// <summary> /// Performs necessary steps to deselect the current squad. /// </summary> private void deselectSquad() { if(selectedSquad != null) { UnitIdleAnimationBehavior[] idles = selectedSquad.GetComponentsInChildren<UnitIdleAnimationBehavior>(); foreach(UnitIdleAnimationBehavior idle in idles) idle.Active = false; selectedSquad = null; } startingPoint = null; grid.HideMovePoints(); validTargets = null; }
/// <summary> /// Waits for the left mouse button to be clicked, then performs a raycast to determine if the player has clicked /// on one of her own squads. /// </summary> /// <remarks> /// Transitions: /// Player Clicks Valid Squad -> SelectingMovement /// </remarks> private void updateSelectingUnit() { // Wait for the left mouse button to be pressed. if(Input.GetMouseButtonDown (0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // Perform raycasting and store a list of all objects that have been selected. List<RaycastHit> hits = new List<RaycastHit>(); hits.AddRange(Physics.RaycastAll (ray)); // Iterate over the selection list to determine if the player has clicked on one of her squads. foreach(RaycastHit hitInfo in hits.OrderBy (l => l.distance)) { // Capture the actor behavior on the hit object. ActorBehavior actor = hitInfo.transform.GetComponent<ActorBehavior>(); if(actor == null) continue; // Ensure that the unit has not moved and belongs to the player. if(!actor.actorHasMovedThisTurn && actor.theSide == inputSide) { // Mark the actor as selected. selectedSquad = actor; startingPoint = actor.currentMovePoint; // Begin the scripted "Idle" animation. UnitIdleAnimationBehavior[] idles = selectedSquad.GetComponentsInChildren<UnitIdleAnimationBehavior>(); foreach(UnitIdleAnimationBehavior idle in idles) idle.Active = true; // Enable rendering of valid target movement nodes. if(actor.currentMovePoint != null) actor.currentMovePoint.HighlightValidNodes(actor, grid); // STATE CHANGE: SelectingUnit -> SelectingMovement controlState = GridControlState.SelectingMovement; break; } } } }
/// <summary> /// Creates the grid. The fenced variable is used to determine fences are required. /// /// Fences are just a name that makes it easier for the gmae designers, the fences are just a means to remove an edge from the graph visually. /// /// Alex Reiss /// </summary> public void CreateGrid() { for (int _i = (gameObject.transform.childCount - 1); _i >= 0; _i--) { DestroyImmediate(transform.GetChild(_i).gameObject); } theMap = new MovePointBehavior[theMapLength * theMapWidth]; if (isFenced) { theVerticalFence = new FenceBehavour[(theMapLength) * theMapWidth]; theHorizontalFence = new FenceBehavour[theMapLength * (theMapWidth)]; } float xPositionOffset = -(theMapWidth / 2); float yPositionOffset = -(theMapLength / 2); float currentXPosition = 0.0f; float currentYPosition = 0.0f; for (int x = 0; x < theMapLength; x++) { currentXPosition = xPositionOffset; currentYPosition = yPositionOffset + x; for (int z = 0; z < theMapWidth; z++) { MovePointBehavior newMovePoint = null; if ((z + x) % 2 == 0) { newMovePoint = (MovePointBehavior)Instantiate(theMovePointPrehab, new Vector3(currentXPosition, 1.0f, currentYPosition), Quaternion.identity); } else { newMovePoint = (MovePointBehavior)Instantiate(theAltMovePointPrehab, new Vector3(currentXPosition, 1.0f, currentYPosition), Quaternion.identity); } newMovePoint.transform.parent = transform; newMovePoint.name = abc[z].ToString() + x.ToString(); theMap[z + (x * theMapWidth)] = newMovePoint; if (isFenced) { if (x < theMapLength - 1) { FenceBehavour newVerticalFence = (FenceBehavour)Instantiate(theFencePointPrehab, new Vector3(currentXPosition, 1.0f, currentYPosition + 0.5f), Quaternion.identity); newVerticalFence.transform.parent = transform; newVerticalFence.name = abc[z].ToString() + x.ToString() + "fence" + abc[z].ToString() + (x + 1).ToString(); theVerticalFence[z + (x * theMapWidth)] = newVerticalFence; } if (z < theMapWidth - 1) { FenceBehavour newHorizontalFence = (FenceBehavour)Instantiate(theFencePointPrehab, new Vector3(currentXPosition + 0.5f, 1.0f, currentYPosition), Quaternion.identity); newHorizontalFence.transform.parent = transform; newHorizontalFence.name = abc[z].ToString() + x.ToString() + "fence" + abc[z + 1].ToString() + x.ToString(); theHorizontalFence[z + (x * theMapWidth)] = newHorizontalFence; } } currentXPosition = xPositionOffset + z + 1; } } }
/// <summary> /// Waits for the left mouse button to be clicked, then performs a raycast to determine if the player has clicked /// on a valid movement point. /// </summary> /// <remarks> /// Transitions: /// Player presses Escape key -> SelectingUnit /// Player presses Space key -> SelectingTarget /// /// Path to movement point cannot be found -> SelectingUnit /// Player selects a valid movement point -> AwaitingMovement /// </remarks> private void updateSelectingMovement() { // Allow the player to press the Escape key to deselect their current unit. if (Input.GetKeyDown(KeyCode.Escape)) { deselectSquad(); controlState = GridControlState.SelectingUnit; return; } // Allow the player to press the Space bar to skip the movement step. if (Input.GetKeyDown(KeyCode.Space)) { selectedSquad.actorHasMovedThisTurn = true; grid.HideMovePoints(); controlState = GridControlState.SelectingTarget; return; } // Wait for the left mouse button to be pressed. if (Input.GetMouseButtonDown(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // Perform raycasting and store a list of all objects that have been selected. List <RaycastHit> hits = new List <RaycastHit>(); hits.AddRange(Physics.RaycastAll(ray)); // Iterate over the selection list to determine if the player has clicked on a valid movementpoint. foreach (RaycastHit hitInfo in hits.OrderBy(l => l.distance)) { // Retrieve the movepoint behavior from the hit target. MovePointBehavior movePoint = hitInfo.transform.GetComponent <MovePointBehavior>(); if (movePoint == null || !movePoint.renderer.enabled) { continue; } // Retrieve the combat squad behavior from the currently-selected squad. CombatSquadBehavior combatSquad = selectedSquad.GetComponent <CombatSquadBehavior>(); if (combatSquad == null) { Debug.LogError("Selected a unit with no combat squad attached!"); } // Retrieve the maximum movement distance of the selected squad. int distance = (combatSquad == null ? 0 : combatSquad.Squad.Speed); // Mark the actor as having moved. selectedSquad.actorHasMovedThisTurn = true; // Retrieve a path from the starting point to the selected node. List <MovePointBehavior> pathList = startingPoint.FindPath(movePoint, distance, grid); if (pathList != null) { selectedSquad.pathList = pathList; selectedSquad.canMove = true; // Remove the squad's current movement point from the ignore list. grid.ignoreList.Remove(startingPoint); // Add the squad's target movement point to the ignore list. grid.ignoreList.Add(movePoint); // Hide visible nodes on the grid. grid.HideMovePoints(); // Transition into the AwaitingMovement state. controlState = GridControlState.AwaitingMovement; } else { Debug.LogError("No path to target!"); // Deselect the current squad, but do not allow it to move again! deselectSquad(); // Return to the SelectingUnit state. controlState = GridControlState.SelectingUnit; } } } }
/// <summary> /// Attempts to find a path to the target node. /// </summary> /// <param name="targetNode">Final node the unit should move to.</param> /// <param name="maxDistance">Maximum distnance the unit can move.</param> /// <param name="grid">Grid that the pathfinding is occurring on.</param> /// <param name="skipIgnoreList">Skips the ignore list when building the initial graph.</param> /// <returns></returns> public List <MovePointBehavior> FindPath(MovePointBehavior targetNode, int maxDistance, GridBehavior grid, bool skipIgnoreList = false) { // Build the Dijkstra's Graph List <MovePointBehavior> graph = new List <MovePointBehavior>(); List <MovePointBehavior> tGraph = new List <MovePointBehavior>(); BuildGraph(maxDistance, 0, grid, ref graph, skipIgnoreList); if (!graph.Contains(targetNode)) { return(null); } Dictionary <MovePointBehavior, int> distance = new Dictionary <MovePointBehavior, int>(); Dictionary <MovePointBehavior, MovePointBehavior> previous = new Dictionary <MovePointBehavior, MovePointBehavior>(); foreach (MovePointBehavior node in graph) { distance.Add(node, int.MaxValue); previous.Add(node, null); tGraph.Add(node); } distance[this] = 0; previous[this] = null; while (tGraph.Count > 0) { // Find the item with the smallest distance. MovePointBehavior node = tGraph[0]; for (int _i = 0; _i < tGraph.Count; _i++) { MovePointBehavior _node = tGraph[_i]; if (grid.ignoreList.Contains(_node) && !skipIgnoreList) { continue; } if (distance[_node] < distance[node]) { node = _node; MovePointBehavior tNode = tGraph[0]; tGraph[0] = _node; tGraph[_i] = tNode; } } tGraph.RemoveAt(0); if (distance[node] == int.MaxValue) { break; } foreach (MovePointBehavior neighbor in node.neighborList) { if (neighbor == null || (!skipIgnoreList && grid.ignoreList.Contains(neighbor)) || !graph.Contains(neighbor)) { continue; } int alt = distance[node] + 1; if (alt < distance[neighbor]) { distance[neighbor] = alt; previous[neighbor] = node; } } } List <MovePointBehavior> path = new List <MovePointBehavior>(); MovePointBehavior u = targetNode; while (previous[u] != null) { path.Add(u); u = previous[u]; } path.Reverse(); return(path); }
/// <summary> /// Attempts to find a path to the target node. /// </summary> /// <param name="targetNode">Final node the unit should move to.</param> /// <param name="maxDistance">Maximum distnance the unit can move.</param> /// <param name="grid">Grid that the pathfinding is occurring on.</param> /// <param name="skipIgnoreList">Skips the ignore list when building the initial graph.</param> /// <returns></returns> public List<MovePointBehavior> FindPath(MovePointBehavior targetNode, int maxDistance, GridBehavior grid, bool skipIgnoreList = false) { // Build the Dijkstra's Graph List<MovePointBehavior> graph = new List<MovePointBehavior>(); List<MovePointBehavior> tGraph = new List<MovePointBehavior>(); BuildGraph(maxDistance, 0, grid, ref graph, skipIgnoreList); if (!graph.Contains(targetNode)) return null; Dictionary<MovePointBehavior, int> distance = new Dictionary<MovePointBehavior, int>(); Dictionary<MovePointBehavior, MovePointBehavior> previous = new Dictionary<MovePointBehavior, MovePointBehavior>(); foreach (MovePointBehavior node in graph) { distance.Add(node, int.MaxValue); previous.Add(node, null); tGraph.Add(node); } distance[this] = 0; previous[this] = null; while (tGraph.Count > 0) { // Find the item with the smallest distance. MovePointBehavior node = tGraph[0]; for (int _i = 0; _i < tGraph.Count; _i++) { MovePointBehavior _node = tGraph[_i]; if (grid.ignoreList.Contains(_node) && !skipIgnoreList) continue; if (distance[_node] < distance[node]) { node = _node; MovePointBehavior tNode = tGraph[0]; tGraph[0] = _node; tGraph[_i] = tNode; } } tGraph.RemoveAt(0); if (distance[node] == int.MaxValue) break; foreach (MovePointBehavior neighbor in node.neighborList) { if (neighbor == null || (!skipIgnoreList && grid.ignoreList.Contains(neighbor)) || !graph.Contains(neighbor)) continue; int alt = distance[node] + 1; if(alt < distance[neighbor]) { distance[neighbor] = alt; previous[neighbor] = node; } } } List<MovePointBehavior> path = new List<MovePointBehavior>(); MovePointBehavior u = targetNode; while (previous[u] != null) { path.Add(u); u = previous[u]; } path.Reverse(); return path; }