Hello! I’m making a 2D turnbased movement system, and the example given is exactly what I need. However, I am having a lot of trouble with converting it from 3D to 2D. I deleted the unit selection code, since I have a separate script that cycles through unit turns. Please, let me know if there is anything I can do to help make it more clear.
It throws a null pointer exception when I click on a node that is in range. It says this is the line that causes problems.
var path = ABPath.Construct(unit.transform.position, (Vector3)node.position);
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Pathfinding;
using UnityEngine.EventSystems;
namespace Pathfinding.Examples
{
///
[HelpURL(“http://arongranberg.com/astar/docs/class_pathfinding_1_1_examples_1_1_turn_based_manager.php”)]
public class TurnBasedManager : MonoBehaviour
{
public TurnBasedAI selected;
public float movementSpeed;
public GameObject nodePrefab;
public LayerMask layerMask;
List<GameObject> possibleMoves = new List<GameObject>();
EventSystem eventSystem;
public State state = State.StartMovement;
//public State state = State.SelectUnit;
public enum State
{
//SelectUnit,
//SelectTarget,
//Move
StartMovement,
NodeSelection,
Move,
End
}
void Awake()
{
eventSystem = FindObjectOfType<EventSystem>();
}
void Update()
{
//var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePos2D = new Vector2(mousePosition.x, mousePosition.y);
Ray2D ray = new Ray2D(mousePos2D, transform.TransformDirection(Vector2.positiveInfinity));
// Ignore any input while the mouse is over a UI element
//if (eventSystem.IsPointerOverGameObject())
//{
// return;
//}
if (state == State.NodeSelection)
{
HandleButtonUnderRay(ray);
}
if (state == State.StartMovement)
{
DestroyPossibleMoves();
GeneratePossibleMoves(selected);
state = State.NodeSelection;
}
}
// TODO: Move to separate class
void HandleButtonUnderRay(Ray2D ray)
{
var button = GetByRay<Astar3DButton>(ray);
//if (Input.GetKeyDown(KeyCode.Mouse0))
//print("click");
if (button != null && Input.GetKeyDown(KeyCode.Mouse0))
{
print("click");
button.OnClick();
DestroyPossibleMoves();
state = State.Move;
StartCoroutine(MoveToNode(selected, button.node));
}
}
T GetByRay<T>(Ray2D ray) where T : class
{
//RaycastHit hit;
Vector3 mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector2 mousePos2D = new Vector2(mousePosition.x, mousePosition.y);
RaycastHit2D hit = Physics2D.Raycast(mousePos2D, Vector2.zero, Mathf.Infinity, layerMask);
//if (Physics2D.Raycast(transform.position, out hit, float.PositiveInfinity, layerMask))
if (hit)
{
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;
state = State.NodeSelection;
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;
state = State.End;
}
/// <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;
}
}
}
}
}