197 lines
5.9 KiB
C#
197 lines
5.9 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using Pathfinding;
|
|
using Pathfinding.Util;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace Pathfinding.Examples {
|
|
/// <summary>Helper script in the example scene 'Turn Based'</summary>
|
|
[HelpURL("https://arongranberg.com/astar/documentation/stable/turnbasedmanager.html")]
|
|
public class TurnBasedManager : VersionedMonoBehaviour {
|
|
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
|
|
}
|
|
|
|
protected override void Awake () {
|
|
base.Awake();
|
|
eventSystem = UnityCompatibility.FindAnyObjectByType<EventSystem>();
|
|
}
|
|
|
|
void Update () {
|
|
var mousePos = Input.mousePosition;
|
|
|
|
// If the game view is not active, the mouse position can be infinite
|
|
if (!float.IsFinite(mousePos.x)) return;
|
|
|
|
Ray ray = Camera.main.ScreenPointToRay(mousePos);
|
|
|
|
// 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>[MoveAlongPath]</summary>
|
|
/// <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)];
|
|
|
|
// Approximate the length of the spline
|
|
var segmentLength = Vector3.Distance(p1, p2);
|
|
|
|
// Move the agent forward each frame, until we reach the end of the segment
|
|
while (distanceAlongSegment < segmentLength) {
|
|
// Use a Catmull-rom spline to smooth the path. See https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline
|
|
var interpolatedPoint = AstarSplines.CatmullRom(p0, p1, p2, p3, distanceAlongSegment / segmentLength);
|
|
unit.transform.position = interpolatedPoint;
|
|
yield return null;
|
|
distanceAlongSegment += Time.deltaTime * speed;
|
|
}
|
|
|
|
distanceAlongSegment -= segmentLength;
|
|
}
|
|
|
|
// Move the agent to the final point in the path
|
|
unit.transform.position = path.vectorPath[path.vectorPath.Count - 1];
|
|
}
|
|
/// <summary>[MoveAlongPath]</summary>
|
|
|
|
void DestroyPossibleMoves () {
|
|
foreach (var go in possibleMoves) {
|
|
GameObject.Destroy(go);
|
|
}
|
|
possibleMoves.Clear();
|
|
}
|
|
|
|
/// <summary>[GeneratePossibleMoves]</summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>[GeneratePossibleMoves]</summary>
|
|
}
|
|
}
|