using UnityEngine; using System.Collections; using System.Collections.Generic; using Pathfinding; using UnityEngine.EventSystems; namespace Pathfinding.Examples { /// Helper script in the example scene 'Turn Based' [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_turn_based_manager.php")] public class TurnBasedManager : MonoBehaviour { TurnBasedAI selected; public float movementSpeed; public GameObject nodePrefab; public LayerMask layerMask; List possibleMoves = new List(); EventSystem eventSystem; public State state = State.SelectUnit; public enum State { SelectUnit, SelectTarget, Move } void Awake () { eventSystem = FindObjectOfType(); } void Update () { var ray = Camera.main.ScreenPointToRay(Input.mousePosition); // Ignore any input while the mouse is over a UI element if (eventSystem.IsPointerOverGameObject()) { return; } if (state == State.SelectTarget) { HandleButtonUnderRay(ray); } if (state == State.SelectUnit || state == State.SelectTarget) { if (Input.GetKeyDown(KeyCode.Mouse0)) { var unitUnderMouse = GetByRay(ray); if (unitUnderMouse != null) { Select(unitUnderMouse); DestroyPossibleMoves(); GeneratePossibleMoves(selected); state = State.SelectTarget; } } } } // TODO: Move to separate class void HandleButtonUnderRay (Ray ray) { var button = GetByRay(ray); if (button != null && Input.GetKeyDown(KeyCode.Mouse0)) { button.OnClick(); DestroyPossibleMoves(); state = State.Move; StartCoroutine(MoveToNode(selected, button.node)); } } T GetByRay(Ray ray) where T : class { RaycastHit hit; if (Physics.Raycast(ray, out hit, float.PositiveInfinity, layerMask)) { return hit.transform.GetComponentInParent(); } return null; } void Select (TurnBasedAI unit) { selected = unit; } IEnumerator MoveToNode (TurnBasedAI unit, GraphNode node) { var path = ABPath.Construct(unit.transform.position, (Vector3)node.position); path.traversalProvider = unit.traversalProvider; // Schedule the path for calculation AstarPath.StartPath(path); // Wait for the path calculation to complete yield return StartCoroutine(path.WaitForPath()); if (path.error) { // Not obvious what to do here, but show the possible moves again // and let the player choose another target node // Likely a node was blocked between the possible moves being // generated and the player choosing which node to move to Debug.LogError("Path failed:\n" + path.errorLog); state = State.SelectTarget; GeneratePossibleMoves(selected); yield break; } // Set the target node so other scripts know which // node is the end point in the path unit.targetNode = path.path[path.path.Count - 1]; yield return StartCoroutine(MoveAlongPath(unit, path, movementSpeed)); unit.blocker.BlockAtCurrentPosition(); // Select a new unit to move state = State.SelectUnit; } /// Interpolates the unit along the path static IEnumerator MoveAlongPath (TurnBasedAI unit, ABPath path, float speed) { if (path.error || path.vectorPath.Count == 0) throw new System.ArgumentException("Cannot follow an empty path"); // Very simple movement, just interpolate using a catmull rom spline float distanceAlongSegment = 0; for (int i = 0; i < path.vectorPath.Count - 1; i++) { var p0 = path.vectorPath[Mathf.Max(i-1, 0)]; // Start of current segment var p1 = path.vectorPath[i]; // End of current segment var p2 = path.vectorPath[i+1]; var p3 = path.vectorPath[Mathf.Min(i+2, path.vectorPath.Count-1)]; var segmentLength = Vector3.Distance(p1, p2); while (distanceAlongSegment < segmentLength) { var interpolatedPoint = AstarSplines.CatmullRom(p0, p1, p2, p3, distanceAlongSegment / segmentLength); unit.transform.position = interpolatedPoint; yield return null; distanceAlongSegment += Time.deltaTime * speed; } distanceAlongSegment -= segmentLength; } unit.transform.position = path.vectorPath[path.vectorPath.Count - 1]; } void DestroyPossibleMoves () { foreach (var go in possibleMoves) { GameObject.Destroy(go); } possibleMoves.Clear(); } void GeneratePossibleMoves (TurnBasedAI unit) { var path = ConstantPath.Construct(unit.transform.position, unit.movementPoints * 1000 + 1); path.traversalProvider = unit.traversalProvider; // Schedule the path for calculation AstarPath.StartPath(path); // Force the path request to complete immediately // This assumes the graph is small enough that // this will not cause any lag path.BlockUntilCalculated(); foreach (var node in path.allNodes) { if (node != path.startNode) { // Create a new node prefab to indicate a node that can be reached // NOTE: If you are going to use this in a real game, you might want to // use an object pool to avoid instantiating new GameObjects all the time var go = GameObject.Instantiate(nodePrefab, (Vector3)node.position, Quaternion.identity) as GameObject; possibleMoves.Add(go); go.GetComponent().node = node; } } } } }