Hello,
I’m trying to implement pathfinding for my 2d platformer game. I didn’t want to create a graph for each map manually, so I created a script that creates nodes when running a level. Then I use the “StartPath” method on Seeker to find a path. As a start point I use the position of the NPC and as an end point the place where I click with the mouse. However, the method returns a path of length 1 with two vectors. Both vectors have the same position, the position of the node that is closest to the start point.
PlatformPathfinder.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
using Pathfinding;
public class PlatformPathfinder : MonoBehaviour
{
public List<Vector2> actions;
[SerializeField] private GameObject solid;
[SerializeField] private GameObject ladders;
[SerializeField] private GameObject pointGraph;
[SerializeField] private GameObject nodeObject;
private LayerMask groundMask;
private Seeker seeker;
private Tilemap solidTilemap;
private Tilemap laddersTilemap;
private List<Vector2> solidTilePositions;
private List<GraphNode> twoWayConnectionPoints;
private List<GraphNode> oneWayConnectionPoints;
private int jumpHeight = 2;
private int jumpDistance = 3;
void Awake()
{
actions = new List<Vector2>();
seeker = GetComponent<Seeker>();
solidTilePositions = new List<Vector2>();
solidTilemap = solid.GetComponent<Tilemap>();
laddersTilemap = ladders.GetComponent<Tilemap>();
groundMask = LayerMask.GetMask("Ground");
BoundsInt solidBounds = solidTilemap.cellBounds;
TileBase[] solidTiles = solidTilemap.GetTilesBlock(solidBounds);
BoundsInt laddersBounds = laddersTilemap.cellBounds;
TileBase[] laddersTiles = laddersTilemap.GetTilesBlock(laddersBounds);
for (int y = 0; y < solidBounds.yMax - solidBounds.y; y++)
{
for (int x = 0; x < solidBounds.xMax - solidBounds.x; x++)
{
TileBase tile = solidTiles[x + y * (solidBounds.xMax - solidBounds.x)];
if (tile != null)
{
solidTilePositions.Add(new Vector2(x + solidBounds.x, y + solidBounds.y));
}
}
}
for (int y = 0; y < laddersBounds.yMax - laddersBounds.y; y++)
{
for (int x = 0; x < laddersBounds.xMax - laddersBounds.x; x++)
{
TileBase tile = laddersTiles[x + y * (laddersBounds.xMax - laddersBounds.x)];
if (tile != null)
{
Vector3 tilePosition = new Vector3(x + laddersBounds.x + 0.5f, y + laddersBounds.y + 0.5f, 0);
Instantiate(nodeObject, tilePosition, Quaternion.identity, pointGraph.transform);
}
}
}
foreach (var solidTilePosition in solidTilePositions)
{
Vector2 type = GetCellType(solidTilePosition);
if (type != new Vector2(0, 0))
{
CreatePoint(solidTilePosition);
if (type.x == -1)
{
Vector2 position = new Vector2(solidTilePosition.x - 1, solidTilePosition.y);
Vector2 rayPosition = new Vector2(solidTilePosition.x - 0.5f, solidTilePosition.y + 0.5f);
Vector2 direction = Vector2.down;
RaycastHit2D result = Physics2D.Raycast(rayPosition, direction, 100, groundMask);
if (result)
CreatePoint(new Vector2(position.x, result.point.y), true);
}
if (type.y == -1)
{
Vector2 position = new Vector2(solidTilePosition.x + 1, solidTilePosition.y);
Vector2 rayPosition = new Vector2(solidTilePosition.x + 1.5f, solidTilePosition.y + 0.5f);
Vector2 direction = Vector2.down;
RaycastHit2D result = Physics2D.Raycast(rayPosition, direction, 100, groundMask);
if (result)
CreatePoint(new Vector2(position.x, result.point.y), true);
}
}
}
AstarPath.active.Scan();
}
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < pointGraph.transform.childCount; i++)
{
int closestRightIndex = -1;
int closestLeftDropIndex = -1;
int closestRightDropIndex = -1;
Transform closestRightNodeTransform = null;
Transform closestLeftDropNodeTransform = null;
Transform closestRightDropNodeTransform = null;
Transform nodeTransform = pointGraph.transform.GetChild(i);
oneWayConnectionPoints = new List<GraphNode>();
twoWayConnectionPoints = new List<GraphNode>();
Vector2 nodeType = GetCellType(nodeTransform.position, true, true);
for (int j = 0; j < pointGraph.transform.childCount; j++)
{
Transform nodeTransform2 = pointGraph.transform.GetChild(j);
if (nodeType.y == 0 && nodeTransform2.position.y == nodeTransform.position.y && nodeTransform2.position.x > nodeTransform.position.x)
{
if (closestRightIndex < 0 || nodeTransform2.position.x < pointGraph.transform.GetChild(closestRightIndex).position.x)
{
closestRightIndex = j;
closestRightNodeTransform = nodeTransform2;
}
}
if (nodeType.x == -1)
{
if (nodeTransform2.position.x == nodeTransform.position.x - 1 && nodeTransform2.position.y < nodeTransform.position.y)
{
if (closestLeftDropIndex < 0 || nodeTransform2.position.y > pointGraph.transform.GetChild(closestLeftDropIndex).position.y)
{
closestLeftDropIndex = j;
closestLeftDropNodeTransform = nodeTransform2;
}
}
if (nodeTransform2.position.y >= nodeTransform.position.y - jumpHeight &&
nodeTransform2.position.y <= nodeTransform.position.y &&
nodeTransform2.position.x > nodeTransform.position.x - (jumpDistance + 2) &&
nodeTransform2.position.x < nodeTransform.position.x &&
GetCellType(nodeTransform2.position, true, true).y == -1)
{
GraphNode node2 = AstarPath.active.GetNearest(nodeTransform2.position).node;
twoWayConnectionPoints.Add(node2);
}
}
if (nodeType.y == -1)
{
if (nodeTransform2.position.x == nodeTransform.position.x + 1 && nodeTransform2.position.y < nodeTransform.position.y)
{
if (closestRightDropIndex < 0 || nodeTransform2.position.y > pointGraph.transform.GetChild(closestRightDropIndex).position.y)
{
closestRightDropIndex = j;
closestRightDropNodeTransform = nodeTransform2;
}
}
}
}
GraphNode node = AstarPath.active.GetNearest(nodeTransform.position).node;
if (closestRightNodeTransform != null)
{
GraphNode node2 = AstarPath.active.GetNearest(closestRightNodeTransform.position).node;
twoWayConnectionPoints.Add(node2);
}
if (closestLeftDropNodeTransform != null)
{
GraphNode node2 = AstarPath.active.GetNearest(closestLeftDropNodeTransform.position).node;
if (node2.position.y <= node.position.y + jumpHeight)
twoWayConnectionPoints.Add(node2);
else
oneWayConnectionPoints.Add(node2);
}
if (closestRightDropNodeTransform != null)
{
GraphNode node2 = AstarPath.active.GetNearest(closestRightDropNodeTransform.position).node;
if (node2.position.y <= node.position.y + jumpHeight)
twoWayConnectionPoints.Add(node2);
else
oneWayConnectionPoints.Add(node2);
}
foreach (GraphNode point in twoWayConnectionPoints)
{
node.AddConnection(point, 10);
point.AddConnection(node, 10);
}
foreach (GraphNode point in oneWayConnectionPoints)
node.AddConnection(point, 10);
}
}
public IEnumerator FindPath(Vector2 start, Vector2 end)
{
Path path = seeker.StartPath(start, end, OnPathComplete);
yield return StartCoroutine(path.WaitForPath());
}
private void OnPathComplete(Path path)
{
if (path.error)
{
Debug.Log("No path found.");
}
else
{
actions.Clear();
List<Vector3> pathVectors3 = path.vectorPath;
foreach (var vector in pathVectors3)
{
actions.Add(vector);
}
}
}
private Vector2 GetCellType(Vector2 position, bool global = false, bool isAbove = false)
{
if (global)
position = new Vector2(position.x - 0.5f, position.y - 0.5f);
if (isAbove)
position = new Vector2(position.x, position.y - 1);
Vector2 result = new Vector2(0, 0);
if (solidTilePositions.Contains(new Vector2(position.x, position.y + 1)))
return result;
if (solidTilePositions.Contains(new Vector2(position.x - 1, position.y + 1)))
result += new Vector2(1, 0);
else if (!solidTilePositions.Contains(new Vector2(position.x - 1, position.y)))
result += new Vector2(-1, 0);
if (solidTilePositions.Contains(new Vector2(position.x + 1, position.y + 1)))
result += new Vector2(0, 1);
else if (!solidTilePositions.Contains(new Vector2(position.x + 1, position.y)))
result += new Vector2(0, -1);
return result;
}
private void CreatePoint(Vector2 tilePosition, bool isAbove = false)
{
if (isAbove)
tilePosition.y -= 1;
Vector3 above = new Vector3(tilePosition.x + 0.5f, tilePosition.y + 1.5f, 0);
for (int i = 0; i < pointGraph.transform.childCount; i++)
if (pointGraph.transform.GetChild(i).position == above)
return;
if (tilePosition.y % 1 == 0)
Instantiate(nodeObject, above, Quaternion.identity, pointGraph.transform);
}
}
EnemyAI.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class EnemyAI : MonoBehaviour
{
[SerializeField] private PlatformPathfinder pathfinder;
private Character character;
private LayerMask groundMask;
private List<Vector2> currentPath;
private Vector2 currentTarget;
private Vector2 endPoint;
private readonly Vector2 nullVector2 = new Vector2(-9999, -9999);
// Start is called before the first frame update
void Start()
{
character = GetComponent<Character>();
currentTarget = nullVector2;
endPoint = nullVector2;
currentPath = new List<Vector2>();
groundMask = LayerMask.GetMask("Ground");
}
// Update is called once per frame
void Update()
{
if (Mouse.current.leftButton.wasPressedThisFrame)
{
Vector2 mousePosition = Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue());
RaycastHit2D result = Physics2D.Raycast(mousePosition, Vector2.down,100, groundMask);
Debug.DrawRay(mousePosition, Vector3.down * 100, Color.magenta, 10);
if (result)
{
endPoint = result.point;
StartCoroutine(pathfinder.FindPath(transform.position, endPoint));
currentPath = pathfinder.actions;
NextTarget();
}
else
{
endPoint = nullVector2;
}
}
if (currentTarget != nullVector2)
{
if (currentTarget.x - 0.1 > transform.position.x)
character.SetMoveDirection(1);
else if (currentTarget.x + 0.1 < transform.position.x)
character.SetMoveDirection(-1);
else
character.SetMoveDirection(0);
if (Vector2.Distance(transform.position, currentTarget) < 1)
{
NextTarget();
}
}
else
{
NextTarget();
character.SetMoveDirection(0);
}
}
void NextTarget()
{
if (currentPath.Count < 2)
{
currentTarget = nullVector2;
return;
}
currentTarget = currentPath[1];
currentPath.RemoveAt(0);
}
}
Edit:
Added video.
https://drive.google.com/file/d/1XwXFoZo-uWQjTkitOuvobQW5AtgmnHTG/view?usp=sharing