Tactics RPG Path Finding

Pathfinding can be a relatively advanced task, mostly because the logic takes a moment to grasp. We will be using a form of pathfinding to highlight all of the tiles that a unit can reach. When one of those tiles is selected the unit will follow the best path to the target. To make it more interesting, I will add three different movement types: a walking unit which must go around enemy units and tiles with too large a jump delta, a flying unit, and a teleporting unit.

Directions

I want to add an enum which keeps track of cardinal directions. This will be used to indicate what direction a character on the board is facing. I need to know because I will want to make the characters turn in the direction of the path they are following. Later it can factor into damage formulas based on an angle of attack.

Create a subfolder of the Scripts folder called Enums and then create a script there named Directions.

using UnityEngine;
using System.Collections;

public enum Directions
{
	North,
	East,
	South,
	West
}

Directions Extensions

Recently I have been experimenting with Extension Methods as a means to help keep my classes decoupled from each other, and also as a way to keep related functionality better organized.

If you are unfamiliar with extension methods, they provide a way to extend the functionality of another class without actually modifying the other class itself. However, you can add methods which are accessed from instances of the class through dot-notation just as if it were a native method of the class. You can only add extensions as a static method in a static class, and the target to be extended is listed as the first parameter entry in the method with a this keyword. When you invoke the extension method, you treat it as if that first paramter was not there, because it is determined based on the object you call it from.

Create a subfolder of the Scripts folder called Extensions and then create a script there named DirectionsExtensions.

using UnityEngine;
using System.Collections;

public static class DirectionsExtensions
{
	public static Directions GetDirection (this Tile t1, Tile t2)
	{
		if (t1.pos.y < t2.pos.y)
			return Directions.North;
		if (t1.pos.x < t2.pos.x)
			return Directions.East;
		if (t1.pos.y > t2.pos.y)
			return Directions.South;
		return Directions.West;
	}

	public static Vector3 ToEuler (this Directions d)
	{
		return new Vector3(0, (int)d * 90, 0);
	}
}

With these methods I can get a cardinal direction based off of the relationship between two tiles. For example, if I had two references to tiles named t1 and t2 I could determine which direction you would need to travel from the first tile in order to reach the second tile like this:

Directions d = t1.GetDirection(t2);

In addition I can convert from a Directions enum to a Vector3. This will come in handy for rotating characters on the board. The following code shows how to convert a Directions enum to a Vector3.

Directions d = Directions.North;
Vector3 r = d.ToEuler();

Tile

The Tile component was created in an earlier lesson, but will need to be modified to show that it can hold something. For now it will only ever hold one of our game characters, but later we might want other content like traps, trees or some other non-traversable entity. Add the following field to the Tile script.

public GameObject content;

In addition, I will add a few fields which will be useful for our pathfinding and pathfollowing code. I want the fields to be public, but I dont want them to appear in the inspector so I will use a special tag to hide them. The first field prev stores the tile which was traversed to reach it. You can loop through the tiles for as long as the prev field is not null to determine the entire path taken to reach any given location. The second field distance stores the number of tiles which have been crossed to reach this point.

[HideInInspector] public Tile prev;
[HideInInspector] public int distance;

Unit

Let’s add a component named Unit to our Hero and Monster prefabs. In the future this component will also hold a reference to all of the data we might need such as stats, etc. but for now all we need to track is where the Unit is placed on the board and what direction it is facing. The code for this class is below:

using UnityEngine;
using System.Collections;

public class Unit : MonoBehaviour 
{
	public Tile tile { get; protected set; }
	public Directions dir;

	public void Place (Tile target)
	{
		// Make sure old tile location is not still pointing to this unit
		if (tile != null && tile.content == gameObject)
			tile.content = null;
		
		// Link unit and tile references
		tile = target;
		
		if (target != null)
			target.content = gameObject;
	}

	public void Match ()
	{
		transform.localPosition = tile.center;
		transform.localEulerAngles = dir.ToEuler();
	}
}

Board

The core of our pathfinding code will exist in the Board script. If any of you have worked with pathfinding before, you may wonder why I didn’t make use of a more efficient algorithm like A* (read as A star). With that sort of algorithm you must know where you are and where you want to go. The flow of this game creates a scenario where you pick where you want to go based on the knowledge of where you can reach. In other words, we wont yet know which of the tiles the unit wishes to move to.

We will first gather a list of all tiles within range of the moving unit while marking tiles in such a way as to know the path taken to reach it. Afterwards, a tile from the list is chosen, and we will already know the path and be able to simply return it. Because the algorithm is a bit complex I created the following picture to help visually walk through a sample process.

Key:

  • Orange Tiles – tiles which are added to the queue for checking now.
  • Green Tiles – the tile which is currently being analyzed.
  • Red Tiles – tiles which are added to the queue for checking in the future.
  • Grey Tiles – tiles which have already been processed.
  • Plus Icon – indicates a tile which will be added to the queue for checking in the future
  • No Icon – indicates a tile which is skipped since it has already been visited.

Steps:

  1. We prime our search by deciding which tile to start from. The tile is added to a queue of tiles for checking now. This first tile has a distance of 0, and no prev tile which indicates that it is the beginning of the path.
  2. In a loop, we dequeue a tile from the queue of tiles to check this round. Then we grab a reference to the tiles in each cardinal direction from the current tile and add them to a queue for checking in the future. Any tiles which are added have their distance set to 1 greater than the current tile’s distance. The current tile is also set as their prev tile reference.
  3. The current tile is marked as analyzed. There are no more tiles in the queue for checking now, so we will swap queues.
  4. This is a repeat of step 2, except that now you can observe the skipping of already visited tiles.
  5. There are additional tiles to check in the current queue so we move on and the loop continues.
  6. Same basic loop.
  7. Same basic loop.
  8. This is a repeat of step 3.
  9. This is a repeat of step 2 and 4.

Now let’s implement the algorithm in code. Add the following method stub:

public List<Tile> Search (Tile start, Func<Tile, Tile, bool> addTile)
{
	List<Tile> retValue = new List<Tile>();
	retValue.Add(start);

	// Add more code here

	return retValue;
}

This method will return a list of Tiles, starting from a specific tile, which meet a certain criteria. The criteria to be met is passed along as a delegate via the generic Func delegate which takes as parameters the segment of a potential path (where you would move from and where you would move to) and returns a bool indicating whether or not to allow the movement. At a minimum, the criteria will make sure that the distance to the tile is within the movement range of the unit. Other factors could include checking for blocking entities on the tile. Note that we will need to add a using System; statement at the top of our script in order to use the Func delegate type.

The very first thing we need to do when starting a new search will be to clear the results of any previous search. To take care of this we will use a separate method named ClearSearch. This method loops through all of the board’s tiles and resets their relevant pathfinding fields. The prev tile reference is set to null indicating that it is not currently part of a path. The distance is set to the largest value that an int value type can hold, which means that reaching the tile from any other tile will be able to succeed as a more efficient path.

void ClearSearch ()
{
	foreach (Tile t in tiles.Values)
	{
		t.prev = null;
		t.distance = int.MaxValue;
	}
}

As a side note, fast enumeration causes extra allocations which can lead to memory issues in your game. Because I am only using it sporadically, and because it allows a very easy and readable way to loop over the tiles in my dictionary, I decided it would be okay for use. In most other cases you should use a regular for loop.

I left a comment indicating where to continue adding code in the Search method. Add a statement which calls our ClearSearch method there. We will also declare two Tile queues: one for tiles which need to be checked now, and one for tiles we will check in the future.

ClearSearch();
Queue<Tile> checkNext = new Queue<Tile>();
Queue<Tile> checkNow = new Queue<Tile>();

Now let’s prime the system, by setting correct values on the start tile and adding it to the list of tiles which need to be checked.

start.distance = 0;
checkNow.Enqueue(start);

Next add the main loop, which dequeues a tile and will perform logic on it. The loop continues for as long as the checkNow queue contains tiles. It wont loop forever, because within every loop we dequeue one of the tiles it contains.

while (checkNow.Count > 0)
{
	Tile t = checkNow.Dequeue();
	// Add more code here
}

Before we flesh out the while loop, add an array of Points (one in each cardinal direction) as a private field to the Board class. Be sure NOT to add it inside of the while loop or it will be making a lot of unnecessary extra allocations.

Point[] dirs = new Point[4]
{
	new Point(0, 1),
	new Point(0, -1),
	new Point(1, 0),
	new Point(-1, 0)
};

Now, back inside the while loop where I left the comment, we will add a nested for loop which gets the tiles in each direction from the currently selected tile.

for (int i = 0; i < 4; ++i)
{
	Tile next = GetTile(t.pos + dirs[i]);
	// Add more code here
}

The implementation for GetTile follows

public Tile GetTile (Point p)
{
	return tiles.ContainsKey(p) ? tiles[p] : null;
}

Within the inner for loop, we will first verify that we actually got a tile reference (remember that we are on non-square boards, which could have holes, etc.) and if so, we will compare the distance which it has marked. We only need to consider tiles for which we can offer a more efficient (shorter distance) path – remember that if the tile has not been touched it should still hold its reset value of int.MaxValue.

if (next == null || next.distance <= t.distance + 1)
	continue;

Now we will perform a criteria check on the tile. A search of the board can only continue from that tile if it passes the check, otherwise getting to tiles beyond it would require going around it. If it passes the check, it will be added to the list of tiles to check in the future, as well as be added to the list of tiles returned by our search. We will also make sure to keep track of the path data inlcuding which tile we came from and how far we have traveled.

if (addTile(t, next))
{
	next.distance = t.distance + 1;
	next.prev = t;
	checkNext.Enqueue(next);
	retValue.Add(next);
}

Just after the inner for loop, but still inside of the while loop, add another check to see if we have now cleared our queue. If so we will swap the references of the queues so that checkNow points to the tiles we had queued for checking in the future, and checkNext points to the empty queue we have just cleared out.

if (checkNow.Count == 0)
	SwapReference(ref checkNow, ref checkNext);

The implementation for SwapReference is below:

void SwapReference (ref Queue<Tile> a, ref Queue<Tile> b)
{
	Queue<Tile> temp = a;
	a = b;
	b = temp;
}

When implementing the game states, we will want to be able to highlight the tiles a player can move to. Let’s add a few fields to represent a highlight and default color:

Color selectedTileColor = new Color(0, 1, 1, 1);
Color defaultTileColor = new Color(1, 1, 1, 1);

Following are the methods which can loop through the tiles and highlight them or un-highlight them.

public void SelectTiles (List<Tile> tiles)
{
	for (int i = tiles.Count - 1; i >= 0; --i)
		tiles[i].GetComponent<Renderer>().material.SetColor("_Color", selectedTileColor);
}

public void DeSelectTiles (List<Tile> tiles)
{
	for (int i = tiles.Count - 1; i >= 0; --i)
		tiles[i].GetComponent<Renderer>().material.SetColor("_Color", defaultTileColor);
}

Movement

Create a subfolder of Scripts/View Model Component named Movement and add our base class for the movement types which is also called Movement. The concrete subclasses of this component will be added to our board units. In the future it could be added directly to the prefab – a model with wings would obviously get the flying version. For now, we are reusing a sphere as placeholder art so the components will be added dynamically to make sure we get a sample of each.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public abstract class Movement : MonoBehaviour
{

}

The movement component will know how far the unit is allowed to move (as the number of board tiles) as well as how high the unit can jump. In the future this data may be set from stats which the Unit component would have a reference to, or it may just be a property which conveniently wraps the value directly. For the sake of this weeks lesson, the values will be assigned test values. We will also need a reference to the Unit component so that we can update its placement. Finally we will need a reference to the jumper Transform in order to assist with animation during the traversal of a path. The fields are implemented as follows:

public int range;
public int jumpHeight;
protected Unit unit;
protected Transform jumper;

We will assign some of the references in the Awake method:

protected virtual void Awake ()
{
	unit = GetComponent<Unit>();
	jumper = transform.FindChild("Jumper");
}

We will have a public method which can determine what tiles are reachable on a given board. The method will provide its own criteria for the board’s search method and return that result.

public virtual List<Tile> GetTilesInRange (Board board)
{
	List<Tile> retValue = board.Search( unit.tile, ExpandSearch );
	Filter(retValue);
	return retValue;
}

The ExpandSearch method will be overridable but also offer a base implementation which compares the distance traveled against the range of the character.

protected virtual bool ExpandSearch (Tile from, Tile to)
{
	return (from.distance + 1) <= range;
}

The Filter method will also be overridable while offering a base implementation. It loops through the list of tiles returned by a board search, and removes any which hold blocking content. This step is required because some search criteria may have allowed the unit to travel over tiles which had content (like an ally) but should not be allowed to stop there. In the future this check may be more complex, for example, we may want to allow a unit to occupy the same location as a trap, but for now, any content will be considered an obstacle.

protected virtual void Filter (List<Tile> tiles)
{
	for (int i = tiles.Count - 1; i >= 0; --i)
		if (tiles[i].content != null)
			tiles.RemoveAt(i);
}

We will also have a public method which tells the component to handle the animation of actually traversing a path. It will be left as abstract in the base class requiring all concrete subclasses to provide their own implementation.

public abstract IEnumerator Traverse (Tile tile);

We will also add a reusable bit of animation code which causes a character to rotate to face a specified direction. This method causes the unit to rotate in the fastest direction.

protected virtual IEnumerator Turn (Directions dir)
{
	TransformLocalEulerTweener t = (TransformLocalEulerTweener)transform.RotateToLocal(dir.ToEuler(), 0.25f, EasingEquations.EaseInOutQuad);
	
	// When rotating between North and West, we must make an exception so it looks like the unit
	// rotates the most efficient way (since 0 and 360 are treated the same)
	if (Mathf.Approximately(t.startValue.y, 0f) && Mathf.Approximately(t.endValue.y, 270f))
		t.startValue = new Vector3(t.startValue.x, 360f, t.startValue.z);
	else if (Mathf.Approximately(t.startValue.y, 270) && Mathf.Approximately(t.endValue.y, 0))
		t.endValue = new Vector3(t.startValue.x, 360f, t.startValue.z);

	unit.dir = dir;
	
	while (t != null)
		yield return null;
}

Walk Movement

Create a concrete subclass of Movement called WalkMovement.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class WalkMovement : Movement 
{
	// Add code here
}

We will start by overriding the ExpandSearch method. We will want to retain the base implementation (a distance check) while adding a check for the distance between tile heights – to make sure the character can jump that far. We will also add a check to see if the tile has any sort of blocking content. In the future, when we have data showing the alliance of a unit, we can allow a character to path through teammates, but for this demo we will assume that any content at all is a blocker.

protected override bool ExpandSearch (Tile from, Tile to)
{
	// Skip if the distance in height between the two tiles is more than the unit can jump
	if ((Mathf.Abs(from.height - to.height) > jumpHeight))
		return false;

	// Skip if the tile is occupied by an enemy
	if (to.content != null)
		return false;

	return base.ExpandSearch(from, to);
}

Next we must provide the implementation of the animation along a path. I created it sequentially using nested coroutines.

public override IEnumerator Traverse (Tile tile)
{
	unit.Place(tile);

	// Build a list of way points from the unit's 
	// starting tile to the destination tile
	List<Tile> targets = new List<Tile>();
	while (tile != null)
	{
		targets.Insert(0, tile);
		tile = tile.prev;
	}

	// Move to each way point in succession
	for (int i = 1; i < targets.Count; ++i)
	{
		Tile from = targets[i-1];
		Tile to = targets[i];

		Directions dir = from.GetDirection(to);
		if (unit.dir != dir)
			yield return StartCoroutine(Turn(dir));

		if (from.height == to.height)
			yield return StartCoroutine(Walk(to));
		else
			yield return StartCoroutine(Jump(to));
	}

	yield return null;
}

IEnumerator Walk (Tile target)
{
	Tweener tweener = transform.MoveTo(target.center, 0.5f, EasingEquations.Linear);
	while (tweener != null)
		yield return null;
}

IEnumerator Jump (Tile to)
{
	Tweener tweener = transform.MoveTo(to.center, 0.5f, EasingEquations.Linear);

	Tweener t2 = jumper.MoveToLocal(new Vector3(0, Tile.stepHeight * 2f, 0), tweener.easingControl.duration / 2f, EasingEquations.EaseOutQuad);
	t2.easingControl.loopCount = 1;
	t2.easingControl.loopType = EasingControl.LoopType.PingPong;

	while (tweener != null)
		yield return null;
}

Fly Movement

Create another concrete subclass of Movement called FlyMovement. We wont need to override the ExpandSearch method this time, because any obstacles can be flown over. All we need to implement is the animation for traversing the path:

using UnityEngine;
using System.Collections;

public class FlyMovement : Movement 
{
	public override IEnumerator Traverse (Tile tile)
	{
		// Store the distance between the start tile and target tile
		float dist = Mathf.Sqrt(Mathf.Pow(tile.pos.x - unit.tile.pos.x, 2) + Mathf.Pow(tile.pos.y - unit.tile.pos.y, 2));
		unit.Place(tile);

		// Fly high enough not to clip through any ground tiles
		float y = Tile.stepHeight * 10;
		float duration = (y - jumper.position.y) * 0.5f;
		Tweener tweener = jumper.MoveToLocal(new Vector3(0, y, 0), duration, EasingEquations.EaseInOutQuad);
		while (tweener != null)
			yield return null;

		// Turn to face the general direction
		Directions dir;
		Vector3 toTile = (tile.center - transform.position);
		if (Mathf.Abs(toTile.x) > Mathf.Abs(toTile.z))
			dir = toTile.x > 0 ? Directions.East : Directions.West;
		else
			dir = toTile.z > 0 ? Directions.North : Directions.South;
		yield return StartCoroutine(Turn(dir));

		// Move to the correct position
		duration = dist * 0.5f;
		tweener = transform.MoveTo(tile.center, duration, EasingEquations.EaseInOutQuad);
		while (tweener != null)
			yield return null;

		// Land
		duration = (y - tile.center.y) * 0.5f;
		tweener = jumper.MoveToLocal(Vector3.zero, 0.5f, EasingEquations.EaseInOutQuad);
		while (tweener != null)
			yield return null;
	}
}

Teleport Movement

Create our third and final concrete subclass of Movement called TeleportMovement. As with flying, the only thing we need to implement is the animation for path traversal.

using UnityEngine;
using System.Collections;

public class TeleportMovement : Movement 
{
	public override IEnumerator Traverse (Tile tile)
	{	
		unit.Place(tile);

		Tweener spin = jumper.RotateToLocal(new Vector3(0, 360, 0), 0.5f, EasingEquations.EaseInOutQuad);
		spin.easingControl.loopCount = 1;
		spin.easingControl.loopType = EasingControl.LoopType.PingPong;

		Tweener shrink = transform.ScaleTo(Vector3.zero, 0.5f, EasingEquations.EaseInBack);

		while (shrink != null)
			yield return null;

		transform.position = tile.center;

		Tweener grow = transform.ScaleTo(Vector3.one, 0.5f, EasingEquations.EaseOutBack);
		while (grow != null)
			yield return null;
	}
}

Battle Controller

Let’s give the BattleController script a referenc to our Hero prefab so that we can instantiate a few heroes on the board. After modifying the script make sure to open the scene and actually connect the reference. This is prototype code which I expect not to keep, so I wont bother wrapping it in the BaseBattleState. A more complete implementation for spawning our characters would load the correct models through a Resources.Load call. Let’s also add a field to keep track of the currently selected unit and a property to wrap the currently selected tile.

public GameObject heroPrefab;
public Unit currentUnit;
public Tile currentTile { get { return board.GetTile(pos); }}

Init Battle State

Lets have the InitBattleState instantiate three copies of our Hero prefab and place them on the board. This code is only placeholder and will be removed later. But for now, it allows us to test each of the three movement types. Invoke the following method just before the Init method yields for a frame, and then have it change state to SelectUnitState.

IEnumerator Init ()
{
	board.Load( levelData );
	Point p = new Point((int)levelData.tiles[0].x, (int)levelData.tiles[0].z);
	SelectTile(p);
	SpawnTestUnits(); // This is new
	yield return null;
	owner.ChangeState<SelectUnitState>(); // This is changed
}

void SpawnTestUnits ()
{
	System.Type[] components = new System.Type[]{ typeof(WalkMovement), typeof(FlyMovement), typeof(TeleportMovement) };
	for (int i = 0; i < 3; ++i)
	{
		GameObject instance = Instantiate(owner.heroPrefab) as GameObject;

		Point p = new Point((int)levelData.tiles[i].x, (int)levelData.tiles[i].z);

		Unit unit = instance.GetComponent<Unit>();
		unit.Place(board.GetTile(p));
		unit.Match();

		Movement m = instance.AddComponent(components[i]) as Movement;
		m.range = 5;
		m.jumpHeight = 1;
	}
}

Select Unit State

For the purposes of this demo, we are adding and inserting a state which wont be part of the final game flow. In the real game, Units will take turns based on their speed. For now, you will be able to select any of our demo units that you wish by moving the cursor onto them and then selecting them using the “Fire” button input. Add a new script named SelectUnitState to the Scripts/Battle States folder.

using UnityEngine;
using System.Collections;

public class SelectUnitState : BattleState 
{
	protected override void OnMove (object sender, InfoEventArgs<Point> e)
	{
		SelectTile(e.info + pos);
	}
	
	protected override void OnFire (object sender, InfoEventArgs<int> e)
	{
		GameObject content = owner.currentTile.content;
		if (content != null)
		{
			owner.currentUnit = content.GetComponent<Unit>();
			owner.ChangeState<MoveTargetState>();
		}
	}
}

Move Target State

Now let’s give the MoveTargetState a more complete implementation. This state becomes active after the game has decided what Unit should take its turn. It begins by highlighting the tiles within the Unit’s movement range and exits when a valid move location is chosen via the “Fire” button input. The highlighted tiles are un-highlighted before the state exits.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MoveTargetState : BattleState
{
	List<Tile> tiles;
	
	public override void Enter ()
	{
		base.Enter ();
		Movement mover = owner.currentUnit.GetComponent<Movement>();
		tiles = mover.GetTilesInRange(board);
		board.SelectTiles(tiles);
	}
	
	public override void Exit ()
	{
		base.Exit ();
		board.DeSelectTiles(tiles);
		tiles = null;
	}
	
	protected override void OnMove (object sender, InfoEventArgs<Point> e)
	{
		SelectTile(e.info + pos);
	}
	
	protected override void OnFire (object sender, InfoEventArgs<int> e)
	{
		if (tiles.Contains(owner.currentTile))
			owner.ChangeState<MoveSequenceState>();
	}
}

Move Sequence State

Let’s add one more state to complete this demo. In this state, we already know the current unit, and where the unit should move. This state triggers the path traversal animation and waits for it to complete before looping back to the unit selection state. By making this phase of the game its own state, I dont have to worry about additional user input causing bugs while the character is moving.

using UnityEngine;
using System.Collections;

public class MoveSequenceState : BattleState 
{
	public override void Enter ()
	{
		base.Enter ();
		StartCoroutine("Sequence");
	}
	
	IEnumerator Sequence ()
	{
		Movement m = owner.currentUnit.GetComponent<Movement>();
		yield return StartCoroutine(m.Traverse(owner.currentTile));
		owner.ChangeState<SelectUnitState>();
	}
}

Summary

There was a lot to cover in this lesson, with lots of little setup material. Extension methods were introduced for the first time. The pathfinding algorithm was covered in detail, and made good use of passing along a delegate as a parameter for search validation. Finally we added and modified several game states to allow a simple demo game loop of selecting and moving units around the board so you can watch all of the different traversal modes.

122 thoughts on “Tactics RPG Path Finding

    1. Glad you liked it! Feel free to translate and share it, as long as it remains free and as long as I remain credited as the original author. A link back to my blog is always appreciated too.

  1. In both your project download and my “follow along” project I get the following errors when testing out the battle.scene.

    When the scene loads:

    NullReferenceException: Object reference not set to an instance of an object
    MoveTargetState.Enter () (at Assets/Scripts/Controller/Battle States/MoveTargetState.cs:12)
    StateMachine.Transition (.State value) (at Assets/Scripts/Common/State Machine/StateMachine.cs:40)
    StateMachine.set_CurrentState (.State value) (at Assets/Scripts/Common/State Machine/StateMachine.cs:9)
    StateMachine.ChangeState[MoveTargetState] () (at Assets/Scripts/Common/State Machine/StateMachine.cs:24)
    InitBattleState+c__Iterator1.MoveNext () (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:20)

    Whenever I click:

    NullReferenceException: Object reference not set to an instance of an object
    MoveTargetState.OnFire (System.Object sender, .InfoEventArgs`1 e) (at Assets/Scripts/Controller/Battle States/MoveTargetState.cs:31)
    InputController.Update () (at Assets/Scripts/Controller/InputController.cs:31)

    1. Let’s see if I can help you figure out what the problem is. I went ahead and downloaded a fresh copy of my repository and then checked out the commit “b1eecb68c1a7db006768529b42dbaea94c7dca92” which correlates to this part of the lesson checkin. I do not get any exceptions when running the scene. What version of Unity are you using? I am using Unity version 5.1.1f1 at the moment. If you are on a version before version 5 then your prefabs will almost certainly be broken and could explain why the repository project didn’t work for you.

      If you are using a compatible version of Unity then let’s see if we can solve the problem a different way. Since we know where the NullReferenceException is occurring, I usually try to solve it using Debug.Log calls like this:


      public override void Enter ()
      {
      base.Enter ();
      if (owner == null)
      Debug.Log("owner is null");
      if (owner.currentUnit == null)
      Debug.Log("currentUnit is null");
      Movement mover = owner.currentUnit.GetComponent();
      if (mover == null)
      Debug.Log("mover is null");
      if (board == null)
      Debug.Log("board is null");
      tiles = mover.GetTilesInRange(board);
      board.SelectTiles(tiles);
      }

      * note that in the code post above that some of the code is not displayed properly. The important thing to grasp is that you are checking each object against null. When you figure out “what” object is null we can next figure out “why” it is null.

    2. I’ve just had the exact same error and after going through every script and replacing the code; found out that I’d attached a copy of the BattleController script to the Main Camera in the hierarchy meaning there were two copies on the scene and one of them with no references. Hope it helps.

  2. This is out of scope for this project, but how would you do npc/ai movement in a free movement game (non-tiled)? Pathfinding, steering behaviours or a combination of both?

    1. I have tried various combinations of steering behaviours and way-points etc, but I haven’t personally found satisfactory results with it. The steering stuff was always too finicky and caused a lot of jittery and wobbly movement. However I have used Unity’s Navigation Mesh with pretty good results. If you haven’t looked at that you should give it a try.

  3. Hi Jon,

    I’m in a similar situation as @davooslice, but somewhat different (I didn’t get anything from MoveTargetState). I’m using Unity 5.2.3 (free).

    When I run the game, it displays the Board I created but it spawns a Hero Unit outside of it (somewhere without a Tile), and the Debug shows the following error:

    NullReferenceException: Object reference not set to an instance of an object
    InitBattleState.SpawnTestUnits () (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:34)
    InitBattleState+c__Iterator1.MoveNext () (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:18)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    InitBattleState:Enter() (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:9)
    StateMachine:Transition(State) (at Assets/Scripts/Common/State Machine/StateMachine.cs:42)
    StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/State Machine/StateMachine.cs:9)
    StateMachine:ChangeState() (at Assets/Scripts/Common/State Machine/StateMachine.cs:24)
    BattleController:Start() (at Assets/Scripts/Controller/BattleController.cs:17)

    line 34 in my InitBattleState is “unit.Place(board.GetTile(p));” from the “SpawnTestUnits();” method, which seems to cause all the other stuff.

    PS: I did reference the heroPrefab to the GameController object in the Hirerachy, and I did review if all my code is following exactly what u said in the post and the repository.

    1. Since you’re actually seeing the unit get created It sounds like the unit is not null. The next candidate on line 34 would be the board. You also see the board, so I am guessing that is not null either, but you should verify that your state’s property is correctly wrapping the GameControllers reference. Try doing Debug lines printing out whether each object is null or not as I mentioned to davooslice.

      1. I suspect that whatever broke your ability to move the TileSelectionIndicator is probably related to the fact that setup didn’t complete so once you fix the null ref issue everything should be fine.

      2. Jon, had the same issue. Resolution? Add the Unit component to Hero and Monster Prefabs. lol. It’s the small things….

  4. Hi, I’ve been following this tutorial to help me create my own tactic RPG and it’s been really helpful. Thanks for taking to time to write this guide.

    I was having no problems until now, trying to implement the path finding system. It’s hard to follow which codes go where and the project i downloaded as a reference has different coding so it just confused me more. Right where I need to input the algorithm code, after the diagram, I get lost.

    1. Hey Robbie, I’m glad you’ve been enjoying it. The code for the searching algorithm goes into the “Board.cs” script.

      The project link that was on the intro page was just the initial prototype (I plan to update this page soon). The easiest way to help fill in any gaps in what I’ve described and what you should be doing is to look at the repository which has a check-in for each of these blog posts.

      You can grab the repository here: My Repository

      It is a git repository, hopefully you are familiar with git. If not, I would recommend getting a copy of something like SourceTree to help you manage it. In addition I have a blog post on the topic in general which might help:
      Intro to Source Control

      1. Thanks for the quick reply. Both links you provided gave 404 errors saying the link is dead.

        I went through this lesson again and the coding in Board.cs seems to be where I’m having trouble and I think I just have the codes in the wrong places or I’m missing something obvious

  5. Hey, thanks again for all the help. I managed to fix all the errors on the board.cs and I was able to make my heroes move. Problem now seems to be similar to what davooslice and Nelson had. The board, 3 heroes and the selection indicator show up and work fine but the fly and teleport heroes don’t physically move (no animation) even though their location shows being different.

    I get 3 errors when moving my heroes, one after the first hero successfully moves:

    NullReferenceException: Object reference not set to an instance of an object
    TransformAnimationExtensions.MoveToLocal (UnityEngine.Transform t, Vector3 position, Single duration, System.Func`4 equation) (at Assets/Scripts/Common/Animation/TransformAnimationExtensions.cs:40)
    WalkMovement+c__Iterator8.MoveNext () (at Assets/Scripts/View Model Component/Movement/WalkMovement.cs:66)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    c__Iterator6:MoveNext() (at Assets/Scripts/View Model Component/Movement/WalkMovement.cs:47)

    One when trying to move the teleport hero:

    NullReferenceException: Object reference not set to an instance of an object
    TransformAnimationExtensions.RotateToLocal (UnityEngine.Transform t, Vector3 euler, Single duration, System.Func`4 equation) (at Assets/Scripts/Common/Animation/TransformAnimationExtensions.cs:51)
    TeleportMovement+c__Iterator5.MoveNext () (at Assets/Scripts/View Model Component/Movement/TeleportMovement.cs:10)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    c__Iterator2:MoveNext() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:15)
    UnityEngine.MonoBehaviour:StartCoroutine(String)
    MoveSequenceState:Enter() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:9)
    StateMachine:Transition(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:40)
    StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:9)
    StateMachine:ChangeState() (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:24)
    MoveTargetState:OnFire(Object, InfoEventArgs`1) (at Assets/Scripts/Controller/Battle States/MoveTargetState.cs:32)
    InputController:Fire(Int32) (at Assets/Scripts/Controller/InputController.cs:64)
    InputController:Update() (at Assets/Scripts/Controller/InputController.cs:56)

    And one while trying to move the fly hero:

    NullReferenceException: Object reference not set to an instance of an object
    FlyMovement+c__Iterator4.MoveNext () (at Assets/Scripts/View Model Component/Movement/FlyMovement.cs:14)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    c__Iterator2:MoveNext() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:15)
    UnityEngine.MonoBehaviour:StartCoroutine(String)
    MoveSequenceState:Enter() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:9)
    StateMachine:Transition(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:40)
    StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:9)
    StateMachine:ChangeState() (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:24)
    MoveTargetState:OnFire(Object, InfoEventArgs`1) (at Assets/Scripts/Controller/Battle States/MoveTargetState.cs:32)
    InputController:Fire(Int32) (at Assets/Scripts/Controller/InputController.cs:64)
    InputController:Update() (at Assets/Scripts/Controller/InputController.cs:56)

    Any idea what I did wrong? I feel like I skipped something obvious.

    1. Unfortunately the problem could be anywhere. I’m guessing that it is a problem either in the way you setup the scene or the prefabs. For example, if you didn’t set up your Hero prefab’s hierarchy the same way I did, or if you had a typo in the name of one of the game objects, etc. then that could cause a null ref. Look in the “Awake” method of the Movement class and see how it looks through the hierarchy for a child named “Jumper” as an example. You could put a debug log in there to make sure it is finding everything it needs.

      Also, try to grab a copy of the repository. You can double click any of the check-ins to see how the project was set up at that time, including the scenes and prefabs. That should be the biggest helper.

  6. Outstanding CODE WRITING! (I’m a computer Science student on my 2nd year) such an AMAZING tutorial, sorry for the caps but i’m overwhelmed and its the first time I find myself so fascinated and I actually READ every single word you write.

    about the bugs people mentioned above, so I stumbled into both of them and managed to Debug using your advice, it seems that when you follow this kind of path finding guides at 3 am you seem to miss bits of explanations.

    to resolve this issue ::

    “When I run the game, it displays the Board I created but it spawns a Hero Unit outside of it (somewhere without a Tile), and the Debug shows the following error:

    NullReferenceException: Object reference not set to an instance of an object
    InitBattleState.SpawnTestUnits () (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:34)
    InitBattleState+c__Iterator1.MoveNext () (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:18)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    InitBattleState:Enter() (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:9)
    StateMachine:Transition(State) (at Assets/Scripts/Common/State Machine/StateMachine.cs:42)
    StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/State Machine/StateMachine.cs:9)
    StateMachine:ChangeState() (at Assets/Scripts/Common/State Machine/StateMachine.cs:24)
    BattleController:Start() (at Assets/Scripts/Controller/BattleController.cs:17)

    line 34 in my InitBattleState is “unit.Place(board.GetTile(p));” from the “SpawnTestUnits();” method, which seems to cause all the other stuff.”

    Solution == go to your Hero and Monster Prefabs and make sure you actually Add the UNIT component (the Unit.cs script, otherwise it points to Null).

    2nd issue is as such :: ” I get 3 errors when moving my heroes, one after the first hero successfully moves:

    NullReferenceException: Object reference not set to an instance of an object
    TransformAnimationExtensions.MoveToLocal (UnityEngine.Transform t, Vector3 position, Single duration, System.Func`4 equation) (at Assets/Scripts/Common/Animation/TransformAnimationExtensions.cs:40)
    WalkMovement+c__Iterator8.MoveNext () (at Assets/Scripts/View Model Component/Movement/WalkMovement.cs:66)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    c__Iterator6:MoveNext() (at Assets/Scripts/View Model Component/Movement/WalkMovement.cs:47)

    One when trying to move the teleport hero:

    NullReferenceException: Object reference not set to an instance of an object
    TransformAnimationExtensions.RotateToLocal (UnityEngine.Transform t, Vector3 euler, Single duration, System.Func`4 equation) (at Assets/Scripts/Common/Animation/TransformAnimationExtensions.cs:51)
    TeleportMovement+c__Iterator5.MoveNext () (at Assets/Scripts/View Model Component/Movement/TeleportMovement.cs:10)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    c__Iterator2:MoveNext() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:15)
    UnityEngine.MonoBehaviour:StartCoroutine(String)
    MoveSequenceState:Enter() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:9)
    StateMachine:Transition(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:40)
    StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:9)
    StateMachine:ChangeState() (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:24)
    MoveTargetState:OnFire(Object, InfoEventArgs`1) (at Assets/Scripts/Controller/Battle States/MoveTargetState.cs:32)
    InputController:Fire(Int32) (at Assets/Scripts/Controller/InputController.cs:64)
    InputController:Update() (at Assets/Scripts/Controller/InputController.cs:56)

    And one while trying to move the fly hero:

    NullReferenceException: Object reference not set to an instance of an object
    FlyMovement+c__Iterator4.MoveNext () (at Assets/Scripts/View Model Component/Movement/FlyMovement.cs:14)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    c__Iterator2:MoveNext() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:15)
    UnityEngine.MonoBehaviour:StartCoroutine(String)
    MoveSequenceState:Enter() (at Assets/Scripts/Controller/Battle States/MoveSequenceState.cs:9)
    StateMachine:Transition(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:40)
    StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:9)
    StateMachine:ChangeState() (at Assets/Scripts/Common/State Machine/Path/StateMachine.cs:24)
    MoveTargetState:OnFire(Object, InfoEventArgs`1) (at Assets/Scripts/Controller/Battle States/MoveTargetState.cs:32)
    InputController:Fire(Int32) (at Assets/Scripts/Controller/InputController.cs:64)
    InputController:Update() (at Assets/Scripts/Controller/InputController.cs:56)

    Any idea what I did wrong? I feel like I skipped something obvious. ”

    Solution for this Issue :: in InitBattleState.cs check for the following code ill post and make sure its exactly the same

    IEnumerator Init()
    {
    board.Load(levelData);
    Point p = new Point((int)levelData.tiles[0].x, (int)levelData.tiles[0].z);
    SelectTile(p);
    SpawnTestUnits(); // MAKE SURE YOU ACTUALLY ADDED THIS LINE!!!
    yield return null;
    owner.ChangeState(); // MAKE SURE YOU ACTUALLY CHANGED THIS LINE!!!
    }

    Good luck to everyone, and realy realy realy good job Jon. I wish you will keep up adding tutorials.
    maybe expand outside of the Battle state into a Sandbox world? 😀

    1. Daniel, you are very kind! Thanks – I am always glad to hear you enjoy this since I worked so hard on it! Also, thanks for taking the time to write the solutions to the other people’s issues, I hope it helps them. It’s hard for me to know where people get stuck because it’s hard to look at the content with fresh eyes.

  7. Hello! These lessons are awesome, I’m really learning a lot!
    There’s just something wrong with this chapter: is the flying unit supposed to float up, move, then land? Because mine doesn’t float, and instead clips right through the terrain. Have I skipped something…?
    There’s nothing in the console tab, and I’m using Unity 5.1.0f3.

    Thanks in advance!

    1. Yep, the flying unit should float up, move, then land. One possibility is that you missed something with the setup of the enemy prefab. Download the project from the repository and compare your setup against the one there so you can make sure that the hierarchy and connections all match.

      If everything there matches, you might try adding some console logs to the FlyMovement’s Traverse method. Verify that the “y” position to animate to isn’t zero, etc.

  8. The y position is correct. I’ve also noticed that the walking unit doesn’t jump, and the teleporting unit doesn’t spin. I’ll try and compare my project with the repository, thanks!

  9. The only difference between my current FlyMovement.cs and yours is that mine (and the code snippet in your post) has “unit.place(tile)”, whereas the repository version has “yield return StartCoroutine(base.Traverse(tile))”. But this can’t work, since base.Traverse is an abstract method, so I went to check Movement.cs as well, and found that this time there were several key differences between the code snippets and the repo. Is this simply not supposed to work correctly until further on in the project? 😀

    P.S. All this said and done, setting the Hero prefab’s Sphere object as a child of the Jumper object, instead of a sibling, fixed everything. My bad.

    1. I’m glad you got it working. Also, in case you run into any issues in the future, it is worth pointing out that each step of the tutorial should work on its own, although of course features are added and improved on over time in this series.

      Also, the repository allows you to “checkout” the project like it would have looked at any chapter of this tutorial. Grab yourself a copy of something like Atlassian Sourcetree if you haven’t already. This will make it easy to see the various commits and check out the one you want as well as to easily see exactly what code was added/removed etc between each lesson.

    1. Looking great 🙂

      There will be little things all over the project that will need to be updated. Currently everything is designed around the concepts of cardinal directions like ‘N’,’E’,’S’,’W’. It doesn’t make as much sense to use that on a Hex Grid since you might use ‘NE’ and not ‘E’ in order to get the 6 different directions. It might be that you use some other enum, or that you simply resort to angles (in degrees or radians) for everything.

      You could also still navigate the board using keyboard input if you wanted, such as “left” moving the cursor down-left, and “right” moving the cursor up-right. Up and down would work normally. I would want to keep this as a backup just in case some board configurations or unit sizes made it difficult to click a tile you wanted to target.

      To use a mouse, I would begin by using Unity’s event system (the same one used on canvas buttons). You can attach a 3D raycaster to your camera to handle this. It would be easy to make each tile include its own collider and event and post an event when detecting a mouse click. Using the battle states, you could basically just register to listen for those clicks when it is time for the user to make a move, and then unregister after input has been provided. Don’t forget to either remove colliders from units, put their colliders on a different layer, or use logic so you can click units or tiles.

        1. Do you have the example code for what you did for achieving this? I’d want to take a look as I’m trying something similar but am less advanced

  10. Hi sir jon! Can I ask where exactly should I place the animation of the “Attack” and the “Skill” animations? I’m tried to place it on the “Ability” script, but everytime 1 character attacks, everyone else attacks on the same time. Oh and btw i’m using boolean as parameters. I’m already able to place the walk anim on the Walk movement.Though now i’m having a hard time where to put the animation of attack. Can you please guide me? Thanks to your project, i’ve learned a lot. More power to you, sir! 😀

  11. Hi, I’m really enjoying this tutorial! One question though. I’m still wrapping my head around the pahfinding algorithm, so please excuse me if this is really basic. Say I have different tile types (I’ve already successfully implemented this in my own project) of grass and water. I don’t want the units to be able to travel as far in water as they can on grass (unless they are a swimming type unit, then I’d want it to be reversed). How would the implementation of this work? Would it greatly complicate the algorithm? I would want it to be visually represented when the tiles are highlighted by a unit.

    Thanks!

    1. Great question- One suggestion would be to modify the way that distance is added. Currently I always add exactly “1” to the distance when adding a new tile:
      next.distance = t.distance + 1;
      … but you could add another method reference (kind of like we passed the “ExpandSearch” method) which would return a custom travel cost. This way it is generic and the different types of units can have different movement bonuses or penalties based on their own type and the terrain they traveled on. Perhaps it would look something like this:
      next.distance = t.distance + travelCost(t);
      … where “travelCost” was a method that accepted a tile parameter (the tile you would be traversing) and would return a value for the cost for the current unit to move across it.

      1. Perfect, this is exactly what I needed! I think digging deeper into it is helping me understand it much better as well.

        By the way, I do quite a bit of teaching and advocacy of programming for audio producers (I started out as a game composer and have been learning programming for a few years to help with implementation and audio system programming in Unity and Unreal), so I’d love to share your tutorials as a resource for learning with students. I stumbled upon them by accident, but they are really good and helping me learn a lot. Let me know if there is any specific link you’d want me to use or anything like that.

        1. Glad to help Brennan 🙂 Also always glad to connect with another pro. Do you have a portfolio site or something? Would be happy to hear some of your work.

          You are free to link to the content however you think is most helpful, whether that is the home page, projects, or a specific category etc.

        2. May I ask in more detail how you went about this? I am trying to do something similar. Right now I have a map that instantiates tiles saved with a vector 4 to read the correct block to instantiate.

          I would like to make deep water for example a -4 penalty for a knight, while giving a ninja normal movement. I am a bit stuck.

          On my board script:

          continue;
          }

          if(addTile(t, next)
          {
          next.distance = t.distance + t.travelCost();

          And then in my tile script:

          public int materialInt;
          public int travelDifficulty;

          public int travelCost()
          {

          return travelDifficulty;

          }

          I set the difficulty in the I spector on the tile prefab script. It seems to work more or less, however tiles that have the modified movement will remain highlighted after they should no longer be. The effect stacks and a tile will completely have a new color after a few move phases. It doesn’t affect anything in the game as far as I can see though…..

          No abnormal debug errors occur either.

          Was hoping one of you could shed some light on where I went wrong in this, or how I should have proceeded instead to achieve per unit movement.
          (New movement script per unit type seems like overkill, but maybe that would be better? Not sure what I need to change for that though lol.)

          Thank you in advance if you have any ideas!

          Learning so much from your tutorials ~

          1. Looking closer perhaps the select tiles field can not read the correct number of tileoverlays being created for some reason, but I can not make sense of why it doesn’t.

          2. Hey, this is a potentially long discussion. I’d be happy to try to help, but I think it would be easier to follow along if you post in my forum instead.

            To help get you started, I didn’t intend you to put a “travelCost” method on the tile itself. Done this way, it would be the same for every unit traversing the tile regardless of the kind of unit. Instead, the component responsible for traversing a map will pass in its own method which accepts a tile as a parameter and returns a travel cost based on the unit it was attached to. Hopefully that makes sense, but if not, I will elaborate further in the forums with you.

            As to your tile highlighting, I would probably need to see your code to help.

      2. Absolutely! I have my personal site which is a tiny bit outdated, but the SoundCloud player on it is current. Feel free to shoot me an email, would love to connect if we are in a similar area (I’m based out of San Francisco, but travel to LA and Austin every once in a while).

        http://brennananderson.com/

      3. Thanks! FIY, once I finish the tutorial, I fully plan on writing some SNES inspired music for my version. I’d be happy to send it over if you’d like to include it in your version.

      4. Yeah, no problem! I don’t know when I will be done with it yet, but I will keep you posted. I started about a week ago, and I am currently working on the area of effect lesson.

      5. Hey there, so I am making a bunch of progress on my own version of this project. I have finished your main tutorial (did about a week ago) and have been adding a bunch of my own things. I’ll be taking a look at your shop and bestiary tutorials, then I plan to put a full, albeit short, game with a story, map screen, sufficient menus, etc.

        I also just finished writing the battle music for it. You can listen to the music at the link below, and if you want to PM me, I can send you the audio files.

        https://soundcloud.com/brennan-a/voiceless-confrontation

  12. Hey, I’m really enjoying this tutorial, its really helpful for trying to figure out grid based systems. Going through this phase of the tutorial however everything is working fine but it seems that for me my spawned units X position is 1 point lower than where they can be selected on the board. In other words visually they appear in 1 X position lower than where the system says.

    Ive been trying many things to fix it however havent fully figured out whats causing it. Any help would be appreciated.

    Thanks

  13. I’m yet another guy who only found your tutorial in 2017, long after you were done with it.

    I’m having some weird problem in taht with the first board I created (just randomly, for testing the pathfinding code), the tile selection wouldn’t move to some parts of the board (I thought due to geography, but most notably south), and when I selected a place to the north, the units would instead turn east and move.

    Later I replaced the board with a flat one, and when I tried again, the units refuse to move at all.

    Do you have any idea where I could be going wrong here?
    I copy-pasted the code (while reading both it and the blog) rather than typing it myself, so I can’t find any typos.

    1. I haven’t heard any problems similar to this yet… My initial guess for some of the issues, like not being able to navigate far enough in one direction or having your units turn to face the wrong direction, makes me wonder if your prefabs are setup incorrectly. Look through all of the GameObjects in the tile, tile selection indicator, and unit prefabs and make sure that they are all centered around the origin (X,Y,Z all zero) and also that the rotation is zeroed out. That wouldn’t cause them to not be able to move at all on a different board though. Not really sure what happened there, maybe your game states got out of whack somehow. Have you tried downloading the demo project and comparing against it yet?

      1. Well, that was dumb of my part.
        Tripple checked again, then noticed the transform for the battle controller gameobject wasn’t reset.
        Once it was reset, everything worked.

  14. Hey! First off, this tutorial series is awesome, thank you so much.

    My question is that in the board script I’m getting an error in the “GetTilesInRange” section.

    Inside the following:
    List retValue = board.Search(unit.tile, ExpandSearch);

    Expanded search is giving me an error saying “Argument2: cannot convernt from ‘method group’ to ‘Func’

    Any idea what I’ve done wrong here? I’ve tried checking it against the latest version as well, which adds a ton of stuff to board, however it still retains the highlight.

    Cheers,

    Alex

    1. I would double check your method signatures. The “ExpandSearch” argument needs to accept two ’tile’ paramters and return a ‘bool’ result.

  15. My spouse and I stumbled over here different web page and thought I might
    as well check things out. I like what I see so i am just following you.
    Look forward to going over your web page yet again.

  16. Hi there, long time reader, first time posting.
    I was thinking about modifying the scripts a little, to include jumping pits/high cost (assuming different terrain types) tiles in the pathfinding for walking units.

    The idea is to add an exception to the Board.cs Search method: check the next tile in the row/column if the tile being checked is too low/ the cost assigned is high.

    Do you think this approach is a good one? Is there a better one in your mind? Looking forward to your reply.

    1. Yes, that would work fine. Specifically, I would probably add the new code to the same place where I set the “distance” of a “next” tile. You already have a reference to where you are standing and trying to get to, so it should be easy to compare jump heights and add a penalty. The same would also work with penalties on differing terrain types like entering water or mud etc.

  17. Have you ever thought about including a little bit
    more than just your articles? I mean, what you say is fundamental and all.

    Nevertheless just imagine if you added some
    great images or video clips to give your posts
    more, “pop”! Your content is excellent but with
    pics and videos, this blog could undeniably be one of the best in its niche.

    Wonderful blog!

    1. Thanks, I am glad you like it! Adding the things that make a blog “pop” has been one of the number one requests from my fans, so it is definitely something I am concerned about. Of course creating those extra pics and videos, like every other aspect of this process, take a lot of time and would greatly benefit from someone with that skillset. This poses a challenge to me as a solo dev who isn’t focused on art. Still, as I have continued blogging over time I have increased my efforts at including more. With enough support from patrons I might even be able to pay for help, who knows 🙂

  18. I do not even know how I stopped up right here, however I believed this post
    was great. I don’t know who you might be however
    certainly you’re going to a well-known blogger when you are not already.
    Cheers!

  19. Thanks on your marvelous posting! I truly enjoyed reading it,
    you are a great author.I will remember to bookmark your
    blog and will eventually come back later in life. I want to encourage that you continue your great
    writing, have a nice afternoon!

  20. Marvelous tut so far, man!

    So far, I have run into only two issues. Firstly, my Tile Selection Indicator seems to be a little wrong, it won’t correctly go over tiles (being slightly off of where the square would be in the tile), and it doesn’t appear to be going on tiles above it, only through them.

    Secondly my units don’t go to where the TSI is, instead sort of mirroring it. For example, if I drag the TSI in a line from left to right, and click the screen, the walk unit moves from left to right, and the other two units respond in a similar way.

    Beyond that, I’m very pleased to see everything coming along so nice!

    1. Glad you’re enjoying the project!

      For the Tile Selection Indicator, I would suggest double checking the object hierarchy of your TSI and Tile Prefabs against the ones in my repository. For example, if the TSI is not centered at the origin in its prefab, then there will be an offset when positioning it relative to the tiles.

      Im not sure what you mean by the second issue, but it could be a similar problem with hero or board hierarchy configurations. Make sure to zero out any unneeded positions and rotations. Hope that helps!

  21. Hi, firstly, great tutorial, thank you so much for sharing this. I have encountered a problem with trying to get the units to move, I am able to select the unit but no highlighted tiles show up when I do and they will not move where I select I’m using the most current version of Unity and I hope this is not what is causing the problem. Also my board script completely mimics yours with the exception of the #regions

    1. Hey Mike, I just tried running the repo on Unity 2018.2.3 and it ran fine with no problems. Don’t worry about the #region markers in the code – that is just an organization trick you can use for your code editor so that it can group things together by a name, collapse them etc. It won’t make any difference if they are there or not.

      There are lots of things that could be causing you problems besides the code. It is most likely that you missed a scene, object, or prefab configuration step such as assigning references from one thing to another. Without an explicit error it will be hard for me to give you much more guidance than that, but I would recommend you try re-reading the lesson and make sure that you didn’t miss anything important. Also, don’t forget that you can download the complete project from my repository. Good luck!

      1. Oh man, you’re awesome, this post is more than a year old and within a day you replied, thank you very much.

        Anyway, I couldn’t find the problem so I copied your scripts from Github into my project, all the Gameobjects were setup correctly, nothing needed to be changed and it worked perfectly, unfortunately I couldn’t locate the project and in my arrogance did not create a backup to compare, which is my bad. Thank you for your prompt reply.

      2. Hi everyone!

        First, Thanks for your awesome work Jon! Even 6 years later, some guys like me still use your tutorial as a base for their own work!

        So, I did have the same issue, and the problem was that in “Input Controller” script, in the “update” method, the “for” loop was inside the moveEvent check, so the fireEvent info is never sent (if I understood well).

        Hope this will help fellow students.

        1. Thanks for helping to trouble shoot! If someone stuck the “fireEvent” check inside one of the conditions that checked for movement, then it could certainly cause misses.

  22. Is there an easy way to modify this to account for diagonal movement using Pythagorean theorem?

    1. I would say that “easy” is relative to your experience and comfort level with this material. For example, my search algorithm only searches in the four cardinal directions, so you would need to modify the loop to search in all 8 directions instead. Then you would use the Pythagorean theorem to determine overall travelled distance, but currently I was storing distance using an int datatype which would now need greater precision (a float). If you want to try this sort of refactor, and get stuck, you can always start up a thread on my forum and I’ll try to help. Good luck!

  23. I dont quite understand this check here:

    if (addTile(t, next))
    {
    next.distance = t.distance + 1;
    next.prev = t;
    checkNext.Enqueue(next);
    retValue.Add(next);
    }

    I don’t really see the method definition for the addtile delegate; Based on the context, Im infering this is a null check for t and next? But I can’t seem to find the proper documentation for using delegates in if statements (and trust me i checked, so im must be pretty dumb).
    So I had no other choice but to come and ask: If im right, or then what are you checking here?.

    1. You’re probably already familiar with passing regular data-type parameters to a method, but in this case we are also passing a method as a parameter. The “Search” method defined at the beginning of the lesson accepts the “addTile” method as one of its own parameters. Just like we must specify the data-type of normal parameters, we have to specify the signature of the “addTile” method. In this case, it is a type of generic “Func” (see more https://docs.microsoft.com/en-us/dotnet/api/system.func-3?view=netframework-4.8) which will accept two tiles as inputs and will return a bool as its return type.

      The “if” statement is asking “can we add the tile ‘next’ given the current tile ‘t’?” and it is up to the passed in method to answer that question. If we ask a unit that has to walk, and the next tile might be too high for him to jump to, then he could say “no” don’t add it.

      Does that help?

  24. I keep on getting these two errors and I’m struggling to figure out how to fix it without breaking the code.

    Type ‘InitBattleState’ already defines a member called ‘Init’ with the same parameter types.

    The call is ambiguous between the following methods or properties: ‘InitBattleState.Init()’ and ‘InitBattleState.Init()’

    This is my code:

    using UnityEngine;
    using System.Collections;
    public class InitBattleState : BattleState
    {
    public override void Enter()
    {
    base.Enter();
    StartCoroutine(Init());
    }
    IEnumerator Init ()
    {
    board.Load(levelData);
    Point p = new Point((int)levelData.tiles[0].x, (int)levelData.tiles[0].z);
    SelectTile(p);
    SpawnTestUnits(); // This is new
    yield return null;
    owner.ChangeState(); // This is changed
    }
    void SpawnTestUnits()
    {
    System.Type[] components = new System.Type[] { typeof(WalkMovement)}; //typeof(FlyMovement), typeof(TeleportMovement) };
    for (int i = 0; i < 3; ++i)
    {
    GameObject instance = Instantiate(owner.heroPrefab) as GameObject;
    Point p = new Point((int)levelData.tiles[i].x, (int)levelData.tiles[i].z);
    Unit unit = instance.GetComponent();
    unit.Place(board.GetTile(p));
    unit.Match();
    Movement m = instance.AddComponent(components[i]) as Movement;
    m.range = 5;
    m.jumpHeight = 1;
    }
    }

    IEnumerator Init ()
    {

    board.Load(levelData);
    Point p = new Point((int)levelData.tiles[0].x, (int)levelData.tiles[0].z);
    SelectTile(p);
    yield return null;
    owner.ChangeState();
    }
    }

    1. The errors sometimes are a little hard to read, but it is pointing you right to the problem. You have provided two of the same-named method. There is an “Init” method right after “Enter”, and another “Init” at the end of the class. Delete one of those methods and your error should go away.

  25. I am curious, if you wanted to add some variables to the tiles like movement cost (thinking closer to fire emblem here), where in the code would that be put?

    I was thinking of having my tiles have a movement cost, and some even reducing the unit on them’s accuracy and such all wrapped into a nice little TileEffects() so I am assuming i’d just need to call but where is my issue.

    1. I sort of figured this out already so ignore it lol. Unless you have advice or tips on the idea. The fundamental method however i got working (not 100% sure how to have different units ignore certain cost however)

      1. Haha, good on you for making your own way forward. To look at unit specific logic, maybe take another look at how the WalkMovement looks at a specific unit’s jump height to determine what tiles it can reach. You could do some similar things that store terrain compatibilities or something like that.

  26. Ok another question; currently it seems the board creator can only hold a single type of tile. If you wanted numerous tiles in your game (forest, water, etc) would the board creator need to be update or is there another way to do this and make it compatible with what we did here?

      1. Awesome, I will dive into that once finishing this. I have one question though, and let me know if the forum addresses this, I want designated spawn points on each of my maps (hero, neutral, and enemies).
        The board can not save this information so how would I go about this? Would it simply be adjusting the board to be able to save it?

        1. I don’t think anyone has posted about that specifically, but I think my general approach would be to store data in `LevelData` that represents the starting locations, and facing directions for units.

  27. Hello again! Sorry to bother but I’m attempting to assemble this in Unity 2020.3.12f and everything was working perfectly until now.

    The types ‘TransformLocalEulerTweener’ on Movement.cs and ‘Tweener’ on the walk, fly and teleport movements are not recognized by Unity, could I be missing an import somewhere?

    1. Tweeter is an extension, if you are following along it can be found in the git along with the notification system.

  28. I continue to get two errors in my InitBattleState script a namespace cannot contain members such as fields or methods. This is the code I have for the InitBattleState:

    using UnityEngine;
    using System. Collections;

    public class InitBattleState : BattleState
    {
    public override void Enter()
    {
    base.Enter();
    StartCoroutine(Init());
    }
    IEnumerator Init()
    {
    board.Load(levelData);
    Point p = new Point((int)levelData.tiles[0].x, (int)levelData.tiles[0].z);
    SelectTile(p);
    yield return null;
    owner.ChangeState();
    }
    }

    IEnumerator Init()
    {
    board.Load(levelData);
    Point p = new Point((int)levelData.tiles[0].x, (int)levelData.tiles[0].z);
    SelectTile(p);
    SpawnTestUnits();
    yield return null;
    owner.ChangeState();
    }
    void SpawnTestUnits()
    {
    System.Type[] components = new System.Type[] { typeof(WalkMovement), typeof(FlyMovement), typeof(TeleportMovement) };
    for (int i = 0; i < 3; ++i)
    {
    GameObject instance = Instantiate(owner.heroPrefab) as GameObject;
    Point p = new Point((int)levelData.tiles[i].x, (int)levelData.tiles[i].z);
    Unit unit = instance.GetComponent();
    unit.Place(board.GetTile(p));
    unit.Match();
    Movement m = instance.AddComponent(components[i]) as Movement;
    m.range = 5;
    m.jumpHeight = 1;
    }
    }

    1. It looks like you’ve got two copies of the `Init` method, one inside the `InitBattleState` class and one outside. Try replacing the one inside the class with the one outside the class. Then also move `SpawnTestUnits` into the class. You can determine what is inside vs outside the class based on what falls in between its opening and closing curly braces ‘{‘ and ‘}’. A good IDE (code editor) will also automatically indent for you and the things inside the class will be indented to the right, whereas the things outside the class will not.

  29. For anyone having issues changing the material color for Tiles during pathfinding, Unity changed “_Color” to “_BaseColor”. So, make sure to change the SelectTiles and DeSelectTiles methods in the Board script to reflect this. (I’m using Unity 2020.3.29f1)

  30. Having a small issue with getting pathfinding working in my game. The search algorithm is working fine, but I’m getting some unintended behavior and I dont know where/ how to remove it.

    After getting a list of tiles in range of my selected unit, I’m filtering out ‘blocking’ tiles much like you did in the article. This is working properly in that my unit cannot move onto ‘blocking’ tiles, but in certain circumstances, my units are able to pass through them to another tile.

    I’ll use a bit of ASCII art to show what I mean. Below, the [] are walkable tiles, the U is my unit, and the B are ‘blocking’ tiles.

    [][][]
    []B[]
    UB[]

    Say my unit has a move range of 5. Because they should have to walk around the blocking tiles, the bottom right tile should be inaccessible. However, currently if you select that tile, the unit will just pass through the blocking tile and move straight to it.

    Would you recommend trying to tackle this in Movement.cs when removing tiles from the path, where you remove blocking tiles in the blog post? Or should I try to deal with this earlier, in the Board Search script, and try and somehow exclude those tiles from the search? What do you think?

    I read through the comments here and did a search on the forum, but I didn’t find any posts raising this issue — if it’s already been asked, sorry!

    1. The part of the algorithm you are looking for is the “Func” that gets passed to the Board’s Search function that determines whether or not to add a tile to the search space or not. The methods that are provided for this function come from the “ExpandSearch” methods in the various “Movement” classes. For example, if you look at the “WalkMovement” class, there are checks for things like the difference in jump height between tiles, and whether or not the target “to” tile has content on it or not. Those can be cases to say “no, don’t continue searching this tile”.

      After all this time, I feel like I may have meant (and missed) to add checks to differentiate between types of content at this point, such as if the content is a friendly unit, allow searching to continue, but not if it is an enemy unit, and then relied on the “Filter” after the search to make sure you cant move onto an occupied tile, but can move through a friendly location.

      As it stands, the presence of any content at all, including allies, would prevent walking and be treated like a blocking tile.

      1. Ah, I see what you mean. I’m creating a TRPG more in line with Fire Emblem than Final Fantasy Tactics, so I’m deviating from your code in certain places. I think I misunderstood what was really happening in ExpandSearch, and so I recreated it in my code incorrectly. What I was doing before was returning true in ExpandSearch if the tile was InRange, with no other conditions. Then, I would apply my filters on the whole list of tiles that were in range after the search was finished. This excluded the blocking tiles themselves, but it kept squares like the bottom right one since it was only two squares away and therefore included in the initial search.

        Thanks for prompting me to take a closer look at the ExpandSearch function in your example. I’ve got it working now.

        I really appreciate your responsiveness as well. This is an incredible resource, enhanced 100 times by the fact you’re still supporting it. Thanks!!

  31. Hi I’m a big fan of this series and am using it a lot in my project. I want to have multiple tiles at the same x z coordinates with different heights (for example a bridge units could go over or under. Do you have any pointers on how to do that? My thinking so far is that board.GetTiles(pos) will return an array of all the tiles at that point. Then when moving I’d have to check that the difference between the ceiling of the units current tile (tile above at the same point) if there is one and the the target tile is large enough for a unit to fit through. This would also prevent units from popping through the floor of a bridge when their jump height is large enough. Then I’d also have to check in general if the targetTile had a tile above it that didn’t allow a unit to fit in the space between.

    Those two checks in psuedo code:

    Tile [] tilesAtCurrentPosition = board.GetTiles(currentTile.pos);
    Tile currentTileCeiling;
    float currentCeilingHeight = float.MaxValue; // to make sure we’re only getting the nearest ceiling
    for (int i = 0; i currentTile.height && tilesAtCurrentPosition[i].height < currentCeilingHeight;)
    {
    //current tile has a ceiling
    currentTileCeiling = tilesAtCurrentPosition[i];
    currentCeilingHeight = currentTileCeiling.height;
    }
    }

    if (currentTileCeiling.height – targetTile.height < 1)
    {
    //space is large enough for unit to fit
    //I'd make a function to get the tile above a given tile but im not sure off the top of my head how to get around that it would sometimes return a tile and sometimes nothing
    if ( targetTile.GetCeiling() == null)
    //add tile
    if(targetTile.GetCeiling != null && targetTile.GetCeiling().height -targetTile.height)
    {
    //theres enough space on target tile
    //add tile
    }
    }

    I'm sorry if that's hard to read, I'm pretty new and don't what the conventions are.

    My main problem is I have no idea how to integrate these checks into the rest of the search function. I think I'd have another for loop in the for loop that checks for tiles in each direction. But I'd appreciate any tips. Also I'm probably overlooking more extra checks I'd have to make.

      1. Woah thank you theres a lot of useful stuff there but i actually got it working this way. If anyone else wants to know how:

        In board:

        Board.GetTiles(pos) returns a List containing the single or multiple tiles at a x, y point

        I put a inner for loop in the directions for loop in Board.Search() so it now loops through the List to the East, West, North, and South returned by GetTiles(t.pos + dirs[i]).

        The rest of the the function stays pretty much the same.

        I also added a GetCeiling(Tile t) function in board that looks like this:

        public Tile GetCeiling(Tile t){
        List tilesAtCurrentPosition = GetTiles(t.pos);
        Tile currentTileCeiling = null;
        float currentCeilingHeight = float.MaxValue; // to make sure we’re only getting the nearest ceiling
        for (int i = 0; i t.height && tilesAtCurrentPosition[i].height < currentCeilingHeight)
        {
        //current tile has a ceiling
        currentTileCeiling = tilesAtCurrentPosition[i];
        currentCeilingHeight = currentTileCeiling.height;
        }
        }
        if(currentTileCeiling != null)
        return currentTileCeiling;
        return null;
        }

        The new checks are in your movement script or wherever your expand search function is. I put it in the WalkMovement script for now because I don’t think other movement types will need theses checks.

        Along with checks like if the tile is occupied and in the same format:

        If the current tile has a ceiling, check that the ceiling is high enough above the target tile for your unit to pass through with their head clipping through the current Tile ceiling.

        If the target tile has a ceiling, check a)that the targetCeiling is high enough off the target tile (the floor) for a unit to fit in that space and b) check that the targetCeiling is high enough above the current tile to allow a unit to fit through without their head clipping through the target tile ceiling

        I think that was it and I just got it working so their could be mistakes. Hopefully the formatting doesn’t get messed up this time.

        1. thinking about it now the check that a target tiles ceiling isn’t to low to allow a unit high space above the target tile should be in movement (or somewhere more basic) as no sub class should be able to stand on that tile. The rest can go in WalkMovement.

  32. That first part posted weird for some reason here is the for loop again:

    Tile [] tilesAtCurrentPosition = board.GetTiles(currentTile.pos);
    Tile currentTileCeiling;
    float currentCeilingHeight = float.MaxValue; // to make sure we’re only getting the nearest ceiling
    for (int i = 0; i currentTile.height && tilesAtCurrentPosition[i].height < currentCeilingHeight;)
    {
    //current tile has a ceiling
    currentTileCeiling = tilesAtCurrentPosition[i];
    currentCeilingHeight = currentTileCeiling.height;
    }
    }

    1. Yeah the comments do some weird auto formating. You may need to use code tags to wrap your code samples. That would be like <code>int foo = 2;</code>

  33. Hi!
    Im struggling with a pathfinding issue, I’m trying to modify your walking movement script to allow moving through an allied unit but I cannot figure it out :/
    Currently if a unit of walking movement type would like to move to a spot 3 tiles away, but an allied unit is uccupying a tile on his path (not on the end) it is not possible. It creates an issue with walls of slow units not letting through the allied fast ones.
    in ExpandSearch for WalkMovement
    // Skip if the tile is occupied by an enemy
    if (to.content.GetComponentInChildren().type != gameObject.GetComponent().type || to.content != null)
    return false;

    I’m trying to reference to alliance type somehow but Unassigned reference occurs.

    Any thought in this matter? Or did anyone play with this also and found a solution?

    1. Remember that the content reference of a tile can be null, so you need a check for null before trying to get references to other components.

      Something like

      if (to.content != null)
      {
      // Now do a check for your components
      if (the content is determined to be a blocking type of content)
      {
      return false;
      }
      }

  34. Hey, back to making tweaks. Currently working on swapping out the map format, which we’ll see how that goes later, but I had a bit of question on the base code.

    I feel like maybe I’m missing something, but is there actually a purpose to have two separate queue’s? If we use checkNow.Enqueue(next); instead of adding it to checkNext, it would eliminate the need to swap at the end and simplify things slightly.

    I downloaded the source for this version and deleted out checkNext, and it seems to work as far as I can tell?

    1. It sounds like you already answered your own question. After thinking about it, I didn’t identify a benefit to two queues either, and as long as it still works, the code is probably also easier to read without it. Great observation!

Leave a Reply to admin Cancel reply

Your email address will not be published. Required fields are marked *