182 lines
5.3 KiB
C#
182 lines
5.3 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Pathfinding;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace Pathfinding.Examples {
|
|
/// <summary>Helper script in the example scene 'Turn Based'</summary>
|
|
[HelpURL("https://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<GameObject> possibleMoves = new List<GameObject>();
|
|
EventSystem eventSystem;
|
|
|
|
public State state = State.SelectUnit;
|
|
|
|
public enum State {
|
|
SelectUnit,
|
|
SelectTarget,
|
|
Move
|
|
}
|
|
|
|
void Awake () {
|
|
eventSystem = FindObjectOfType<EventSystem>();
|
|
}
|
|
|
|
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<TurnBasedAI>(ray);
|
|
|
|
if (unitUnderMouse != null) {
|
|
Select(unitUnderMouse);
|
|
DestroyPossibleMoves();
|
|
GeneratePossibleMoves(selected);
|
|
state = State.SelectTarget;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Move to separate class
|
|
void HandleButtonUnderRay (Ray ray) {
|
|
var button = GetByRay<Astar3DButton>(ray);
|
|
|
|
if (button != null && Input.GetKeyDown(KeyCode.Mouse0)) {
|
|
button.OnClick();
|
|
|
|
DestroyPossibleMoves();
|
|
state = State.Move;
|
|
StartCoroutine(MoveToNode(selected, button.node));
|
|
}
|
|
}
|
|
|
|
T GetByRay<T>(Ray ray) where T : class {
|
|
RaycastHit hit;
|
|
|
|
if (Physics.Raycast(ray, out hit, float.PositiveInfinity, layerMask)) {
|
|
return hit.transform.GetComponentInParent<T>();
|
|
}
|
|
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;
|
|
}
|
|
|
|
/// <summary>Interpolates the unit along the path</summary>
|
|
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<Astar3DButton>().node = node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|