A* in a 2D Tile-Based environment?

I’m trying to implement the A* Pathfinding Project into my game,and am having a bit of difficulty accomplishing my desired result. I’m hoping to use this for pathfinding in a 2D tile-based game. What this would entail is moving in a grid, one space (world space) at a time, and not cutting corners.

Simply put, I can’t figure out how to make that work. I’m using four connections, with 2D Physics using Ray colliders. I’m also using the Manhattan heuristic with heuristic scale = 1.

My AStarAI.cs code looks like this:

using UnityEngine;
using System.Collections;
using Pathfinding;

public class AStarAI : MonoBehaviour {

	//the Seeker attached to our GameObject
	private Seeker seeker;

	//the GameObject we want to pathfind to
	public Transform target;

	//the controller attached to our GameObject
	private PlayerController playerController;

	//the calculated path
	public Path path;

	//distance to the next waypoint to trigger a move
	private float nextWaypointDistance = 2;

	//current waypoint we aremoving towards
	private int currentWaypoint = 0;

	// How often to recalculate the path (in seconds)
	private float repathRate = 0.5f;
	private float lastRepath = -9999;

	// Use this for initialization
	void Start () {
		seeker = GetComponent<Seeker>();
		playerController = GetComponent<PlayerController> ();
	}
	
	// Update is called once per frame
	void Update () {
		if (Time.time - lastRepath > repathRate && seeker.IsDone()) {
			lastRepath = Time.time+ Random.value*repathRate*0.5f;
			// Start a new path to the targetPosition, call the the OnPathComplete function
			// when the path has been calculated (which may take a few frames depending on the complexity)
			seeker.StartPath(transform.position, target.position, OnPathComplete);
		}

		if (path == null || currentWaypoint > path.vectorPath.Count) {
			return;
		}
			
		if (currentWaypoint == path.vectorPath.Count) {
			//Debug.Log("End Of Path Reached");
			currentWaypoint++;
			return;
		}

		Vector3 dir = (path.vectorPath[currentWaypoint]-transform.position).normalized;
		if (transform.position != path.vectorPath [currentWaypoint]) {
			playerController.SimpleMove (path.vectorPath [currentWaypoint]);
		} else {
			currentWaypoint++;
			return;
		}

		if ((transform.position-path.vectorPath[currentWaypoint]).sqrMagnitude < nextWaypointDistance*nextWaypointDistance) {
			currentWaypoint++;
			return;
		}
	}

	public void OnPathComplete (Path p) {
		//Debug.Log ("Yay, we got a path back. Did it have an error? "+p.error);
		if (!p.error) {
			path = p;
			currentWaypoint = 0;
		}
	}
}

And my PlayerController.SimpleMove() method looks like this:

public void SimpleMove(Vector3 direction) {
		transform.position = Vector2.MoveTowards (transform.position, direction, playerSpeed);
	}

This seems to generate the right path, but my Player object cuts the corners,and does a strange stuttering towards the end of the path, where it slows down before zigzagging back and forth very near the target. How can I modify this to help it work better?

Hi

You might want to check out the example scene called ‘2D’, it shows a setup in a 2D tile based game with no corner cutting. It uses 8 connections, but you can easily change that to 4.
That example scene uses the AILerp script which will follow the path exactly without smoothing out the movement like the other movement scripts (including yours) does.

Thanks for the reply! I did get this working in a custom script, pretty simply, one I figured out how it was supposed to work :slight_smile:

I am trying to figure out how make Seekers repath around each other, rather than passing through each other. From the forum, I’ve gathered that I need to update the graph and either assign penalties or tags to the occupied nodes, but I haven’t seen much explanation on how to do that.

The documentation for GraphUpdateScene and GraphUpdateObject seems to suggest that if I set a tag, penalty, or walk ability, it will set it on all nodes, which obviously isn’t my goal.

How do I accomplish something like this?

Yeah, there is no official doc page about it right now (sorry about that). But there are several threads in the forum.
For example this one (active right now) which have it working to some degree: Make the seekers avoid other seekers

It will set the penalty/tag/whatever on all nodes inside the bounds of the graph update object.

So I would guess the approach would be to, constantly, both set all tags to normal, set occupied nodes to the tag of the occupying Seeker, and then make all Seekers ignore all tags but their own?

I’m trying to make that work, but it seems like that’s a lot of scans, and even though it’s not hurting my performance much, my Seekers tend to “get stuck” in the middle of paths and flip back and forth for a second (or more) before moving on.

So, I’ve landed on this code:

void Pathfind() {
		UpdateGraph ();

		if (path == null || currentWaypoint >= path.vectorPath.Count) {
			direction = Vector3.zero;
			return;
		}

		direction = (path.vectorPath[currentWaypoint]-transform.position).normalized;
		Vector3 dir = direction * playerSpeed * Time.deltaTime;
		transform.position += Vector3.Lerp (transform.position, dir, playerSpeed);

		if (Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]) < nextWaypointDistance) {
			UpdateGraph ();
			currentWaypoint++;
			return;
		}
	}

	public void OnPathComplete (Path p) {
		//Debug.Log ("Yay, we got a path back. Did it have an error? "+p.error);
		if (!p.error) {
			path = p;
			currentWaypoint = 0;
		}
	}

	void UpdateGraph() {
		Bounds prevBounds;

		if (currentWaypoint == 0) {
			prevBounds = new Bounds ((Vector3)AstarPath.active.GetNearest(transform.position).node.position, Vector3.one);
		} else {
			prevBounds = new Bounds (path.vectorPath [currentWaypoint - 1], Vector3.one);
		}

		GraphUpdateObject prev = new GraphUpdateObject (prevBounds);
		prev.modifyTag = true;
		prev.setTag = 0;
		AstarPath.active.UpdateGraphs (prev);

		Bounds currentBounds = new Bounds ((Vector3)AstarPath.active.GetNearest(transform.position).node.position, Vector3.one);
		GraphUpdateObject current = new GraphUpdateObject (currentBounds);
		current.modifyTag = true;
		current.setTag = graphTag;
		AstarPath.active.UpdateGraphs (current);
	}

	void MoveToWaypoint(Vector3 waypoint) {
		seeker.StartPath(transform.position, waypoint, OnPathComplete);
	}

This is with the thinking that each time a Seeker arrives at a node (the last if statement in Pathfind(), which is called in Update()), it will call UpdateGraph() to set the node it last visited back to “Basic Ground”, and set its current node to graphTag, which I’m setting to that Seeker's corresponding tag in the Inspector.

This seems to work fine unless, it seems, two Seekers are moving in a straight line towards each other, in which case they will occupy the same node when they meet. What would be a good approach to recalculating their path during these instances? I’ve tried simply calling MoveToWaypoint() again inside the UpdateGraph() method, but the path gets cancelled since it’s being calculated over and over again :frowning:

I’ve also tried this code as an alternative to updating the graph:

GraphNode prevNode;

		if (currentWaypoint == 0) {
			prevNode = AstarPath.active.GetNearest (transform.position).node;
		} else {
			prevNode = AstarPath.active.GetNearest (path.vectorPath [currentWaypoint - 1]).node;
		}

		prevNode.Tag = (uint) 0;

		GraphNode currentNode = AstarPath.active.GetNearest (path.vectorPath[currentWaypoint]).node;
		currentNode.Tag = (uint)graphTag;

But it doesn’t seem to be doing the job either :confused:

Hi

What is’t working? It looks fine to me.[quote=“drinfernoo, post:6, topic:3315”]
This seems to work fine unless, it seems, two Seekers are moving in a straight line towards each other, in which case they will occupy the same node when they meet. What would be a good approach to recalculating their path during these instances? I’ve tried simply calling MoveToWaypoint() again inside the UpdateGraph() method, but the path gets cancelled since it’s being calculated over and over again :frowning:
[/quote]

That’s tricky. I think one would have to have some separate structure where you reserve certain nodes for certain characters when they are about to start entering a new node. If the node an agent was about to enter was already reserved it will stop and then try to replan the path or something.