Tactics RPG State Machine

This week we are going to create a State Machine which, over time, will handle all of the states which ultimately control our game’s logic. Initially I will create a state which is responsible for initialization (creating the game board, etc.) and then we will add another state which allows us to move the tile selection indicator around the board using events from our Input Controller. We will also add a simple Camera Rig to make sure that the game camera is always looking at something relevant.

State Machine

A state machine is a very useful idea, particularly in game programming. Put simply, it is a controller which manages different states. To help illustrate why it is useful, I will show an example of one way programmers have tried to work WITHOUT it… you may have come across a design pattern (or implemented it yourself) where you define an enum with various state names and then have logic which operates differently depending on a variable which is set to one of those. For example:

enum State
{
	Loading,
	Playing,
	GameOver
}
State _state;

void CheckState ()
{
	switch (_state)
	{
	case State.Loading:
		// Loading Logic here
		break;
	case State.Playing:
		// Playing Logic here
		break;
	case State.GameOver:
		// GameOver Logic here
		break;
	}
}

When you only have a few states, and very simple logic per state, it can be tempting to use a pattern like this. However, this pattern doesn’t grow very well. Every time you want to add another state, you must add an entry to the State enum, and then verify that it will be checked in any method which relies on the current state. In this example we only had one method, called CheckState, but in a more realistic example, you might end up needing different logic per state in multiple methods, such as after special game events or after user input, etc. In these cases, it can be easy to miss adding the entry to one of your switch statements, or if…else conditions, etc and may lead to some confusing bugs.

A better approach would be to change the state from a mere flag to an actual object which can support its own variables and methods etc. Applied to our previous example you would see a few immediate benefits:

  • You don’t need to keep adding states to the State enum (could become a very long list), you simply create a new state class in a new script whenever you want.
  • You don’t need a switch statement or complex if condition to execute the logic for a current state. You simply maintain a reference to the current state and tell it to update as necessary.
  • You can avoid creating a monolithic class. All of your logic will be able to be composed only of relevant bits of data and will be much easier to read and maintain.

State

The scripts we will be creating will be very re-usable, so let’s organize accordingly. Add a sub-folder called State Machine to the Scripts/Common folder. Now create a new script State.cs and use the following:

using UnityEngine;
using System.Collections;

public abstract class State : MonoBehaviour 
{
	public virtual void Enter ()
	{
		AddListeners();
	}
	
	public virtual void Exit ()
	{
		RemoveListeners();
	}

	protected virtual void OnDestroy ()
	{
		RemoveListeners();
	}

	protected virtual void AddListeners ()
	{

	}
	
	protected virtual void RemoveListeners ()
	{

	}
}

It is a very simple script so I only have a few notes. I chose to make the script abstract because I want to help illustrate that other programmers should be creating concrete subclasses in order to use it. The basic use-case will be that a State Machine will determine what state, if any, is current – and as it changes state it will call Exit and Enter on the states as they change from the current state or change to the current state respectively.

I’ve use the Enter and Exit methods as an opportunity to Add and Remove event listeners, but just to be safe I also Remove listeners in OnDestroy. I could have marked the AddListeners and RemoveListeners methods abstract instead of virtual since they are currently implemented with an empty body. However, had I implemented them with abstract, then all subclasses would be required to implement them (whether they needed them or not).

As an example, we could (and will later) register for the InputController events. By only ‘listening’ to events while the state is ‘active’ we dont have to worry about conflicting code. This helps to protect us from scenarios like accidentally moving a tile position on the board at the sime time as changing a menu selection.

I often go back and forth when trying to decide whether or not I want my states and state machines to inherit from MonoBehaviour or not. Being able to create objects using constructors (and without needing GameObjects, Transforms etc) certainly has some benefits, but I also like patterns which appear from the use of MonoBehaviours, such as the ability to easily see data in the inspector (great for debugging), or have convenient methods like OnEnable and OnDisable. Ultimately I decided to go with the MonoBehaviour because I think it is the easier of the two patterns and provides the most benefits.

State Machine

Create a new script called StateMachine.cs in the Scripts/Common/State Machine/ path. The only purpose of this script is to maintain a reference to the current state, and handle switching from one state to another.

using UnityEngine;
using System.Collections;

public class StateMachine : MonoBehaviour 
{
	public virtual State CurrentState
	{
		get { return _currentState; }
		set { Transition (value); }
	}
	protected State _currentState;
	protected bool _inTransition;

	public virtual T GetState<T> () where T : State
	{
		T target = GetComponent<T>();
		if (target == null)
			target = gameObject.AddComponent<T>();
		return target;
	}
	
	public virtual void ChangeState<T> () where T : State
	{
		CurrentState = GetState<T>();
	}

	protected virtual void Transition (State value)
	{
		if (_currentState == value || _inTransition)
			return;

		_inTransition = true;
		
		if (_currentState != null)
			_currentState.Exit();
		
		_currentState = value;
		
		if (_currentState != null)
			_currentState.Enter();
		
		_inTransition = false;
	}
}

This script is also relatively simple. It tracks a single instance of State as its current state. This is held in the protected field called _currentState and is exposed through a property called CurrentState. The property supports both getting and setting a value, although the setter has some additional logic via the Transition method:

  1. If you try to set the current state to the state it already is, then it just exits early (instead of exiting and re-entering the same state).
  2. You can not set the state during a transition (for example, you cant have one state cause another state to become current in its Exit or Enter method). I chose to force this issue in case I want to post events such as when a state changes. Otherwise, in a case where the transition process doesn’t complete before switching to a new state, you would end up seeing multiple posted events showing the same newest event as current (rather than getting one event per state), which could cause unexpected bugs.
  3. Mark the beginning of a transition…
  4. If the previous state is not null, it is sent a message to exit
  5. The backing field is set to the value passed along in the setter
  6. If the new state is not null, it is sent a message to enter
  7. …mark the end of a transition

I also added a few Convenience methods. I wanted an easy way to tell the StateMachine to change state, based on the type of the state in a generic method call. This way, I did not have to hard code references to the instances of the state I want to swap to. I did this with the ChangeState method using a constraint that the generic parameter must be a type of State. Inside the method, it uses another generic method called GetState which passes along the generic type. The GetState method attempts to get a state for you using Unity’s GetComponent call, and when that fails, performs an AddComponent.

Battle Controller

Our battle controller will be a subclass of the StateMachine class. I will also be assigning references which might be useful to the states. The states will have a reference to their owner by which they will be able to easily reach any of the references they need. Create a new script named BattleController.cs in the Scripts/Controller folder path and implement it with the following:

using UnityEngine;
using System.Collections;

public class BattleController : StateMachine 
{
	public CameraRig cameraRig;
	public Board board;
	public LevelData levelData;
	public Transform tileSelectionIndicator;
	public Point pos;

	void Start ()
	{
		ChangeState<InitBattleState>();
	}
}

Battle State

It can be very convenient to add reusable code to a common base class, which we will do for our states. Things like registering/unregistering for events, hooking up references or properties, etc are things which may be applied pretty consistently across them all. Add another folder inside of the Scripts/Controller folder called Battle States (there will be many). Then create our abstract base class state called BattleState.cs. All of the states used by BattleController will be subclasses of this class.

using UnityEngine;
using System.Collections;

public abstract class BattleState : State 
{
	protected BattleController owner;
	public CameraRig cameraRig { get { return owner.cameraRig; }}
	public Board board { get { return owner.board; }}
	public LevelData levelData { get { return owner.levelData; }}
	public Transform tileSelectionIndicator { get { return owner.tileSelectionIndicator; }}
	public Point pos { get { return owner.pos; } set { owner.pos = value; }}

	protected virtual void Awake ()
	{
		owner = GetComponent<BattleController>();
	}

	protected override void AddListeners ()
	{
		InputController.moveEvent += OnMove;
		InputController.fireEvent += OnFire;
	}
	
	protected override void RemoveListeners ()
	{
		InputController.moveEvent -= OnMove;
		InputController.fireEvent -= OnFire;
	}

	protected virtual void OnMove (object sender, InfoEventArgs<Point> e)
	{
		
	}
	
	protected virtual void OnFire (object sender, InfoEventArgs<int> e)
	{
		
	}

	protected virtual void SelectTile (Point p)
	{
		if (pos == p || !board.tiles.ContainsKey(p))
			return;

		pos = p;
		tileSelectionIndicator.localPosition = board.tiles[p].center;
	}
}

This simple script does a few important things. First, it has a reference to its owner, which is the BattleController (a subclass of StateMachine which determines when this state will be ‘active’). The owner reference is connected in the Awake call which is triggered by Unity.

The BattleController holds references to items in the scene which the states will need to perform their own logic. These references are accessible through dot notation, but instead of having to say owner.whatever all over the place, I decided to wrap its fields in properties. This way, I am not adding duplicate pointers (if the BattleController reference changes or updates, the states will all still be pointing to the correct entity) but because of the convenience property I still dont have to use the longer form of reference. In this way it feels more like the state is a natural extension of the state machine class.

I added the event handlers for the Input events, and like before, I decided to implement them as virtual with an empty body. This way, concrete subclasses are not required to override it, unless they actually want to modify the functionality.

Finally, I added a SelectTile method, which sets the selected tile of the Game Board, assuming the board contains a tile at the specified location. It will update the field as well as moving the tileSelectionIndicator. I chose to add this method to the base class because I imagine that several states will make use of setting the selected tile. As we see other functionality required by multiple states, it can be added as well.

Board

Let’s add a script which can load our LevelData and create a game board level at run-time. Create a new script named Board.cs in the Scripts/View Model Component folder path. Add the following code:

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

public class Board : MonoBehaviour 
{
	[SerializeField] GameObject tilePrefab;
	public Dictionary<Point, Tile> tiles = new Dictionary<Point, Tile>();

	public void Load (LevelData data)
	{
		for (int i = 0; i < data.tiles.Count; ++i)
		{
			GameObject instance = Instantiate(tilePrefab) as GameObject;
			Tile t = instance.GetComponent<Tile>();
			t.Load(data.tiles[i]);
			tiles.Add(t.pos, t);
		}
	}
}

There shouldn’t be anything unusual to you here. We use a reference of a tile prefab to instantiate all of the board tiles. Then we save the board tiles into a dictionary based on the Point (location) just like we did with the BoardGenerator. This will be useful in later lessons when we need to do path finding, etc.

Camera Rig

Now let’s add a script which will cause the camera to follow our tile selection indicator when we move it around the board.

using UnityEngine;
using System.Collections;

public class CameraRig : MonoBehaviour 
{
	public float speed = 3f;
	public Transform follow;
	Transform _transform;
	
	void Awake ()
	{
		_transform = transform;
	}
	
	void Update ()
	{
		if (follow)
			_transform.position = Vector3.Lerp(_transform.position, follow.position, speed * Time.deltaTime);
	}
}

We expose a speed parameter which modifies the rate at which the rig will move from where it is toward where it wants to be. Note that this is really a smoothing rate, not a speed in units per second, etc. It works by using a LERP in the update loop. Have you ever heard that if you try to cross a distance by only moving forward half of the remaining distance at a time, that you will never reach the destination? That exercise in logic helps illustrate what’s going on here. When you interpolate from one point to another by a certain fraction of the space, the initial ground covered will be noticeable. As you continue to approach the destination, the fractions of the distance remaining are much smaller, and gives the move a nice ‘ease’ to its animation.

Init Battle State

Let’s go ahead and create our first concrete subclass of BattleState. This is the state which the BattleController begins with. It will create things which need to be created, load things which need to be loaded, etc, and then will trigger the next state when it is complete. Add a new file inside of the Scripts/Controller/Battle States/ folder named InitBattleState.cs.

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<MoveTargetState>();
	}
}

We have overriden the Enter method to allow us to add additional logic. Note that it is important to call the base class’s Enter method or else you can miss important functionality. In this case, forgetting would mean that the method for adding listeners would not be called. Since I am not using any events it wouldn’t actually be that big of a deal. Still, you should practice good habits and call base anyway – who knows, in the future I might add additional logic in there that WOULD be important for this subclass to get, or I might decide to use events and wonder why the listeners were ignored.

Note that I have prevented the ability to change state during a transition, and both the Enter and Exit methods occur inside of a transition. Therefore in order to get the Init state to change to the next state, I wait one frame by using a coroutine.

Move Target Battle State

Now let’s create the script which will allow you to interact with the board through our Input Controller. Add a new file inside of the Scripts/Controller/Battle States/ folder named MoveTargetState.cs.

using UnityEngine;
using System.Collections;

public class MoveTargetState : BattleState 
{
	protected override void OnMove (object sender, InfoEventArgs<Point> e)
	{
		SelectTile(e.info + pos);
	}
}

This is only a partial implementation of what the state will do in its final version, but I just wanted to show a demo of moving the tile around via Input events.

Scene Setup

Create a new scene in the Scenes folder called Battle. Create an empty GameObject at the root of the scene called Battle Controller and attach the similarly named script to it. This controller will be the main organizing container for all of the game objects in the scene. You can also add an instance of the InputController to this object.

Create a new empty GameObject named Camera Rig and attach the similarly named script to it. Make it a child of the Battle Controller. I want our camera to use an Isometric projection style angle. I find it easy to achieve this using a hierarchy of objects. Create a child object for the Camera Rig’s object named Heading. Reset the transform (zero out the position and rotation, and use all one’s for scale), except the Rotation Y value should be 45. Create another empty GameObject named Pitch as a child of Heading. Reset the transform, except the Rotation X value should be 35.264. Now make the Main Camera scene object a child of the Pitch object. Reset the Main Camera’s transform, except the Position Z should be -10. Set the Main Camera’s Projection to Orthographic and the Size to 5. Here is a screen shot and comp of what your Camera Rig’s hierarchy and transform settings will look like:

Create a new Empty GameObject named Board and attach the similarly named script. Make it a child of the Battle Controller.

Create an instance of our Tile Selection Indicator prefab by dragging it from the project pane into the hierachy pane. Add it as a child of the Battle Controller.

Now our scene holds everything we need for the demo. Make sure to hook up the references for each of the fields of our BattleController. You will also need to set the CameraRig to follow the instance of the tileSelectionIndicator and set the Board‘s reference to the Tile Prefab. You can use any LevelData which you have saved from the Board Generator, or use one from my Repository if you skipped that lesson. They are located in Resources/Levels and you can link directly to the item in the Project pane.

Save your scene. Press Play and use the arrow keys to move the cursor around the board. Enjoy!

Summary

We had a lot to cover again this week. We started out by discussing how a StateMachine design pattern can help to make your code more elegant. Then we implemented the pattern with MonoBehaviours in Unity and created a few sample states to play with. We made the states register for Input Events, and then created a GameBoard and CameraRig which could be controlled by them. Finally we connected everything together in a scene to create a nice demo.

65 thoughts on “Tactics RPG State Machine

  1. I’d just like to thank you for these tutorials – they are a brilliant resource and I’m really enjoying trying to understand all the content, challenging though it is to a beginner! You’ve taken a lot of time on these, so well done and thank you very much.

  2. Hi, thank you so much for your articles, I appreciate you taking the time to write them. ๐Ÿ˜€

    However, IMHO, the wall of text is a big turn off especially since I’m a poor reader. Writing a whole paragraph telling the reader to do something (e.g. “Reset the transform, except the Rotation X value should be 35.264”) could be better off done as a series of screenshots or even in bullet points, but I suppose that’s too time-consuming.

    TL;dr, I think images would really help. (http://www.business2community.com/infographics/images-vs-text-data-winning-visuals-infographic-0887861).

    1. Very true and good point. I’ve been slow to decide how to handle pictures, but I should definitely make more use of them. Out of curiosity what do others out there use for hosting pictures? I would like something that has a lot of space, won’t discontinue my account if I am not “active” and will allow me to update / modify, etc pictures I have posted. And it should be free ๐Ÿ™‚

      1. For hosting pictures, I used to have a Photobucket account… and guess what, I just logged today, after about 7 years of inactivity, and my photos are still there. So I guess that answers your question.

        Also, if you’re looking for more colourful code, try the Crayon Syntax Highlighter plugin on wordpress.

  3. Hello Jonathan,

    You wrote a whole paragraph about the mandatory aspect of calling base.Enter(); when calling Enter on any BattleState.
    I think when something is mandatory it must be obvious in the code. Therefore why not let the base class handle it for us ? Here’s an snipet of what I’m thinking :

    using UnityEngine;
    using System.Collections;

    using UnityEngine;
    using System.Collections;

    public abstract class State :
    {

    public override void Enter()
    {
    base.Enter();
    OnEnter();
    }

    protected virtual void OnEnter()
    {

    }

    /* other State methods */
    }

    public class InitBattleState : BattleState
    {
    public override void OnEnter ()
    {
    StartCoroutine(Init());
    }

    /* other stuff */
    }

    I think like this you reduce the risk of forgetting to call it and reduce the eventuality of a hairy debug session. What do you think about it ?

    PS : Is it possible to format the code in commentary ?

    1. Good questions. Calling base.Enter() is not actually “mandatory” though – at least not in the sense of making the statemachine architecture pattern itself function. Calling “base” is something that should be learned as a practice whenever using inheritance. You can intentionally ignore the base class functionality if you truly wish to change everything about it, but if you do not intend to replace it, and rather just want to append logic to it, you should make it a habit to call the base implementation.

      Note that the example you provided wouldn’t compile. I’m not sure if it is just an incomplete example or if you misunderstand the use of “base” itself. The State class is the lowest level implementation for the “Enter” method and so it can’t call base – you will get an error “error CS0115: `State.Enter()’ is marked as an override but no suitable method found to override”

      Imagine you have three classes: A, B, and C where A is the base class, B inherits from A and C inherits from B.
      * ‘A’ defines a method which subclasses may want to change and or extend
      * ‘B’ can choose to override ‘A’s method. If ‘B’ does not call “base” then ‘A’s implementation will not execute and any subclass of ‘B’ will also not get ‘A’s implementation (even if those subclasses call base)
      * ‘C’ can choose to override ‘B’s method. If ‘C’ does not call “base” it will not get ‘B’s or ‘A’s implementation. If it does call “base” it will get ‘B’s implementation, and if ‘B’ calls base it will also get ‘A’s implementation

      1. You’re right the code won’t compile. It’s a typo. The code of State should be like this :

        public override void Enter()
        {
        // Do the actual job the super class needs to do.
        AddListeners();

        // Let a chance to child class to extend it
        OnEnter();
        }

        I think this implementation is less error prone. The class’ user still have the possibility to completely override the Enter() method when sub-classing (because it is public). But if he just want to extend it, he could just define the OnEnter() method and forget about the rest.

        This is not a big deal, but it’s more userfriendly ๐Ÿ™‚

        1. Great comment. In the event that you had code which absolutely needed to execute, then yes, an extra layer of protection like this could be appropriate. I also agree that you can certainly make the argument that this protection is necessary and more user friendly since I have implemented the overridable methods which would not otherwise be called.

          However, the example provided still doesnโ€™t ultimately โ€œfixโ€ the issue at hand (it merely guards the initial level) โ€“ since OnEnter would become overridable, then subclasses can still potentially miss base class implementations if they do not use proper practices.

      2. Thank you ๐Ÿ™‚

        I agree, this is a good practice to call base class method when overriding. My point was just to simplify the work of the user. We are all humans and it happens that we forgot such simple thing ๐Ÿ˜›

        I’m really looking forward for next Monday !

  4. Hi Jonathan,

    Just finished this part of the tutorial. And the result is as always, awesome. But I have some feedback to improve clarity.
    The final part where you explain how to wire up the Camera Rig is ambiguous. In this sentence “Create a child object for this object named Heading.”, I first though that “this object” refer to the Board Controller so I ended with some camera problem (camera not following the cursor, or culling the board). By using some brain juice I did see where was my mistakes, but it could be easier if you specifically say at which object you refer ๐Ÿ™‚

    Thank you again for your great tuts ๐Ÿ™‚

  5. I can’t seem to link the “level” I created in the Board Creator to the Board script. Drag’n’dropping it into the script’s field doesn’t do anything. Thank you.

    1. After dragging and dropping the “LevelData” onto the correct field in the Inspector you will then need to hit the “Load” button at the bottom. Those buttons are available thanks to the “BoardCreatorInspector” script, so if you don’t see them, make sure you have that script in an “Editor” folder and that it is targeting the BoardCreator with the CustomEditor tag.

      1. Thank you for the reply. I found out my problem was I forgot to attach the BattleController script! Thanks again for the reply. Sorry for wasting your time with a very simple fix to a simple problem. ๐Ÿ™‚

  6. Sorry if this double posted, but how do I attach the level I created with the BoardCreator to the Board script? I tried to drag and drop it but it didn’t work. Thanks for your time and fantastic tutorial. ๐Ÿ™‚

  7. Do I need to be changing state in a coroutine?

    In short, here is the issue I’m encountering:

    – When the user clicks on an object, it goes from Select Target state to Show Unit Options state which highlights moves on the grid and shows the UI overlay for that unit. This works properly for the first unit I select.
    – When the user clicks on another object, it still shows the movement highlight and options for the first unit selected, not the second. I’m printing a debug message every time I go into the Show Unit Options state and it only happens once, so this state is never being entered again even though it should when a new unit is selected.

    I checked and confirmed my activeUnit property is updating correctly. I also printed debug logs before and after the battleController.changeState() call and both are appearing correctly. It seems that after I show the unit options once, I can never show them again.

    Alternatively, any tips on how to debug the state machine would be greatly appreciated!

    1. It isn’t necessary to change state from a coroutine, however you can’t change state within the Exit or Enter methods of the State or in any way cause another state change while the state machine is transitioning. If you look at some of my examples like the Init Battle State you will see that I start a coroutine so that I can wait one frame, thus allowing the transition to complete before changing state to something else.

      A different script could tell the state machine to change to one state, and then on the next line to change to a different state, because this would still allow all of the transition process to complete.

      1. That fixed it! Turns out I was trying to change state in an Enter method and I needed to use a coroutine instead ๐Ÿ™‚

  8. Hi, Thanks for the amazing tuts. I had a bug while creating the battle scene using the last paragraph’s instructions. When i run the game and press arrow keys then nothing happens, but if I move the tileSelectionIndicator object manually the camera follows properly. I also tried to debug using logs and found that the Init coroutine in the InitBattleState script never runs. Have a look at my code for that and correct me if I’m wrong and solve this bug:
    public class InitBattleState : BattleState {

    public override void Enter() {
    base.Enter();
    StartCoroutine(Init());
    }

    ///
    /// We have prevented the ability to change state during a transition, and both the Enter and Exit methods
    /// occur inside of a transition. Therefore in order to get the Init state to change to the next state,
    /// we wait one frame by using a coroutine.
    ///
    IEnumerator Init() {
    Debug.Log(“Check 1”);
    board.Load(levelData);
    Point p = new Point((int)levelData.tiles[0].x, (int)levelData.tiles[0].z);
    SelectTile(p);
    yield return null;
    owner.ChangeState();
    }
    }

    Thanks again!

    1. The code for the state looks fine, but if you are not reaching the Debug statement it means your State is not being “entered” and the problem is farther up the chain. I would check the “BattleController” script and make sure that the “Start” method is being triggered and that the “ChangeState” is set to the correct state.

      A lot of people forget steps such as making sure to add the BattleController script to the scene and connect its references. That could also be your “bug”. Did the Console window list any errors or warnings?

    2. I just had a similar problem, but I found a solution. In the Battle Controller I was setting my Tile Selection to the prefab in my assets folder and not to the instance that was in the scene. Once I plugged the one from the scene into Tile Selection Indicator field everything work perfect. Hopefully that helps you or someone else.

  9. I can move in the board and somehow see the tile selection indicator when im in the upper border of the board but i cant see it in other tiles. With this code should i see the tile indicator always?

    1. Yes you should be able to see it. Verify that you are using an instance of the tile selection indicator in your scene rather than linking to the project’s prefab asset itself. If you are familiar with source control you can checkout the commit (b8b5a94) for this lesson so that you can know for sure what it would look like.

      1. I already found the problem but didnt know how to delete the comment, hadnt reset BattleController position -____- Thanks for answering and for this tutorial, really wanted to learn to make a tactics game ๐Ÿ™‚

  10. So I’m able to link the level to the battle controller, but the tiles don’t draw or render or whatever the proper terminology here is; the tile selector is present and moves appropriately (even seemingly following the height of the tile its supposedly on), but the tiles themselves are not drawing.

    I went back to my board creator scene, and they render/draw/appear there just fine, but in the battle scene they do not.

    Thoughts on what might be causing that?

    1. Is the Board script in the Battle scene pointing to a different tile prefab than the Board Creator script in the editor scene? Note that you can left click on the reference in the inspector and it will highlight the item in the project pane so you can be sure.

      1. It turned out that I hadn’t set the position for… something. The tiles were showing up, they just weren’t visible. I noticed that when I paused the game, the tiles were present, just much further “down” than I’d expected – once I found that, I went through things and found that some element or other was not where it was supposed to be.

        I’d be more specific but as this was awhile ago, I’ve forgotten the specifics, and I’ve made a bunch more progress since then.

        Thanks for the rapid response, though!

  11. Hey Jon,

    So I’ve been thinking of ways to add useful features to your tutorials, and I thought of one that was relevant to the game I had in mind: with these isometric 3D games, it can be really difficult to see tiles that are surrounded by high pillars, walls, etc (even when rotating the camera, sometimes), so I tried to tweak the BattleState script to hide any objects that are “in the way” of the selected tile. I actually got it to work, too! At least I think it works. Posting code in these comments is very unwieldy, so I just put the script in a github repository, if anyone wants to copy it or give feedback. (Note that you should first create a board with very high walls to be able to see the effect. Also try changing the heading and pitch of the camera rig and then moving to different positions.)

    https://github.com/ahampe/TRPG-tweaks/blob/master/BattleState.cs

    I don’t really have any experience with game architecture, but I’m not so sure that I should be keeping these functions in the BattleState script, though I can’t think of any other place they should be right now. Advice on this would be awesome.

    Also, in an ideal world, I would be able to make objects temporarily translucent instead of simply disabling the renderer, but this is actually kind of complicated, so it seems (I originally tried changing the shaders of the base materials to support transparency, but this causes objects to unexpectedly disappear at certain angles, and you have to mess with the sorting order or something, and it’s very headache-inducing). One possible way to do this that I haven’t tried: you could try copying the material attached to the obstructing object during run-time and changing the shader of the copy to something transparent, then applying that material to the object. Then, later, you would have to change the material back to the original and destroy the translucent copy. This seems like a bigger waste of resources than it’s worth, though.

    1. Okay, I can see now that this has problems. I will definitely not need a while loop now that I know about Physics.RaycastAll, and I will probably need to end up doing some tag comparisons to only hide objects tagged as tiles. I want to actually get through this tutorial series, so I’ll return to this later. Hope this wall of text is not such an eyesore, in the meantime.

      1. No worries, I am glad to see you trying and learning. I meant to take a look at what you had implemented and got busy and forgot. Sorry about that, but it looks like you’ve already got some new ideas anyway.

  12. Hello! Sorry about asking for help so soon, but I’m sort of confused about this battle scene thing.

    So, I set everything up, and when I press play the game starts normally, and the camera rig moves very smooth, but it doesn’t follow the Tile Selection prefab, and I can’t move. I also get this error:

    “The referenced script on this Behaviour (Game Object ‘Tile’) is missing!
    UnityEngine.Object:Instantiate(GameObject)
    Board:Load(LevelData) (at Assets/Scripts/ViewModelComponent/Board.cs:12)
    c__Iterator0:MoveNext() (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:12)
    UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
    InitBattleState:Enter() (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:8)
    StateMachine:Transition(State) (at Assets/Scripts/Common/Statemachine/StateMachine.cs:40)
    StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/Statemachine/StateMachine.cs:9)
    StateMachine:ChangeState() (at Assets/Scripts/Common/Statemachine/StateMachine.cs:24)
    BattleController:Start() (at Assets/Scripts/Controller/BattleController.cs:12)”

    Was I supposed to attach a script onto the Tile or Tile Selection prefab, and I just missed that part in the tutorial?

    1. It looks like you forgot to add the “Tile” script to the “Tile” prefab. Sometimes the console messages look pretty cryptic but they generally do tell you exactly what the problem is if you follow along. Usually what you will need appears right at the beginning. In this case it says that while it was trying to use the “Load” method of the “Board” class on line 12, that it tries to Instantiate a GameObject (our tile prefab). It then expects to find a “Tile” script on the instantiated copy, but that the script was missing.

      1. Huh. There is a tile script on my tile prefab. When I remove it, I’m unable to build any new stages until I place the script back on the prefab.

        I’m going to try and rewrite the scripts, and see if that works, but on my end it seems like theres something wrong with the Tile Selection thing and not the Tiles themselves.

      2. Oh, I’m now getting another error too. It seems similar, but I’ll post this one too just in case.

        “he referenced script on this Behaviour (Game Object ”) is missing!
        UnityEngine.Object:Instantiate(GameObject)
        Board:Load(LevelData) (at Assets/Scripts/ViewModelComponent/Board.cs:12)
        c__Iterator0:MoveNext() (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:12)
        UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
        InitBattleState:Enter() (at Assets/Scripts/Controller/Battle States/InitBattleState.cs:8)
        StateMachine:Transition(State) (at Assets/Scripts/Common/Statemachine/StateMachine.cs:40)
        StateMachine:set_CurrentState(State) (at Assets/Scripts/Common/Statemachine/StateMachine.cs:9)
        StateMachine:ChangeState() (at Assets/Scripts/Common/Statemachine/StateMachine.cs:24)
        BattleController:Start() (at Assets/Scripts/Controller/BattleController.cs:12)”

        1. First of all, an excellent tutorial, thank you very much for these good lessons.
          My doubt is that when creating the battle scene, the previously created board appears invisible and I’ve been looking at it for a while but I don’t know why.
          If you can help me I would appreciate it very much.
          Greetings

          1. Are there any warnings or errors in the Console? Otherwise, I am not sure what step you might have missed. My best suggestion is to carefully follow along with each step of the tutorial. Pay extra careful attention to making sure that you put the correct script into the correct place, and make sure to connect any references that need to be connected. You can always download the complete samples and compare against your own. Good luck!

  13. I know this is an pretty old tutorial to be following along to, but I’ve been getting a lot out of it so far and hit a bit of a snag, so I thought I’d take a chance and ask for some help.

    Like a couple of others have asked in the past, I am unable to move about in on the loaded battle stage. When I run the code, my loaded level stage does load properly, the camera is properly focused but my inputted commands do not move the tile selector. Using debug.log in InitBattleState I see the coroutine Init does run once, but no more.

    I have properly referenced my prefabs from within the scene, so I’m a little lost as to where I might be going wrong with my code. Is it potentially due to obsolete code now that Unity has gone through updates? I did try running your completed game from the repository, and the same error did come up (along with other broken pieces, but hey, the music still ran!), so I’m leaning towards that being the issue. I would really appreciate any advice though!

    1. What version of Unity are you using? Ive tested it as recently as 2018.3.1 and it still works. My guess with the copy you downloaded from my repo is that you may not have run the MenuItem -> “Pre Production/Parse Jobs”.

      Your own version sounds like a problem with input specifically so you may want to use breakpoints or console logs in the User Input Controller script to make sure things there are geting properly triggered and that its notifications are being observed.

  14. Hi Jon, I’m new in this website and I found some interesting topics about programming games and mecanics. Now I know there is a lot to learn about controllers and other aspects I have to see. But I express my gratitude to share this tutorial, It’s been very helpful.
    I have a doubt, is it normal my tile selection indicator skips a tile inside the board? I’m trying to figure it out but when I’m debugging the function Update from InputController, it executes twice, so that is how it skips a tile every time I press a key to move.

    Did I miss something?

    1. Forget my comment, I see I used the input controller script in MainCamera, so it had been executing the code twice. Anyways, thank you for your concern. I’ll be checking your tutorials regularly.

  15. Five years later, and this is still by far the best resource on creating tactics games that I’ve found on the Internet. Thanks so much for writing up this tutorial! The code is clean, and as a beginner, it really helps me not only to achieve the result from the lesson, but also learn about code/project architecture in a cleaner way than I have so far.

  16. Hi Jon,

    I followed your tutorial up to this point. I have an issue with the tile selection indicator. When I play the scene, TSI is not centered on the selected tile and is way off the center. How can that be fixed?

    1. I would check to see how you constructed the TSI prefab. It sounds like you built it off-center. Make sure to reset everything to the origin and build around that instead.

  17. Hi, thanks for the amazing work this tutorials are far better than any other resource not only for tactics games but for all the info that you give about functions, classes and Unity behaviors.
    I have one question, I modified the code so I can use an array of different tiles in my board creator, the problem as I see it is tha I save the ma but this map only works when I have my board creator scene open, when I load the map in the battle scene, it doesn’t load my tiles, I thought it was because I don’t specify the tile’s prebaf that it should use but I made the same array but still doesn0’t load my map, do you know how can I solve it?

    1. Once you go off on your own, its impossible for me to say what the issue is with so little details. If you want to post more in my forums, including code snippets or anything else that might be useful, I would have a better chance at helping you.

  18. Hey (Admin), I’m super grateful for this in depth tutorial on one of my favorite game genres. I’ve been able to follow along so far, but I’m hung up at the end of this lesson.

    Another poster mentioned the arrow keys not moving the TileSelectionIndicator once the game was running. I’ve performed the following tests:

    The cursor instance moves when changing the position of the Battle Controller GameObject’s Transform Component.

    The cursor doesn’t move when changing the Pos field on the Battle Controller’s Script Component.

    Using Debug.Log, I found that the InputController is registering key presses correctly, but strangely, once the moveEvent method is fired with the InfoEventArgs(new Point(x, y)), it shows up in the MoveTargetState script with the value 0,0.

    I’m sure there’s something I’m not understanding about the state machine or how arguments are passed, or maybe the state isn’t updating? Thanks so much if you’re still checking this blog, if not much thanks anyways for putting this out.

    1. Hey Jackson, you’re welcome, I’m glad you are enjoying the series and getting something out of it.

      The Tile Selection Indicator is parented to the Battle Controller, and thus inherits its position changes, but that is not what you should be using to move.

      Changing the “Pos” field in the inspector is not set up to update the position of the Tile Selection Indicator. The two are synchronized inside of the base class BattleState’s SelectTile method. Note that within that method there are a few early exit conditions, such as if you try to select a tile that is already selected, or if the board doesn’t contain a tile at the position you are trying to select. Otherwise, it will update the “Pos” and the location of the Tile Selection Indicator at the same time. Even in the demo project which currently works, if I set the Pos in the inspector to a value way off the board, then try to move the cursor, nothing will happen, because SelectTile will abort since there is no tile to select.

      Regarding the Debug.Log in your MoveTargetState script, were you printing the value of “e.info” when you mentioned (0, 0)? If so, then that would explain why nothing moves, because adding 0 to anything returns the original number, which was an abort condition inside SelectTile.

      Here are things to check/try:
      1. Make sure InitBattleState is run, a board is loaded, and that “Pos” is set to a valid pos (one of the tile’s positions in the board)
      2. Make sure OnMove is passed an “e.info” that is non-zero. (Maybe print x and y just before posting the event inside InputController)
      3. Make sure OnMove is Selecting a tile position that adds the BoardController’s position with the offset.
      4. Make sure the base BattleState class is wrapping both the get and set of BoardController’s pos.
      5. In the BattleState’s SelectTile method, you could try printing the current “pos” and the passed “p” parameter before the early exit conditions to see what they say.

      Hope something in there helps!

      1. Ah! Thanks so much for the speedy reply, it’s awesome that you’re still fostering this community.

        I went through your suggestions, I checked all my scripts against the Repo. It turned out in the previous tutorial, when writing the InfoEventArgs class, the method public InfoEventArgs had no body, so the value was forgotten there. I added in the this.info = info assignement and it all worked.

        I do feel like I understand the state and event systems better after rooting around and dropping a bunch of flags.

        Thanks again!

  19. Hello Jon, I love your tutorials. I’ve been trying to code for years and your tutorials are the first ones that I could understand and follow.
    I have a problem while moving the tile selector across the level, it skips one tile when i move it. For example, the tile selector is on an A tile, and when I press a key to move it, the tile selector moves to a C tile, in stead of moving to B. I already check if I made a mistake somewhere, but haven’t found anything.
    Is this normal?

    1. Hey Omar, I’m glad you’ve found value in my tutorial!

      Skipping a tile is not normal, so you’ve got to do a little hunting. You might try downloading the finished project from my site and comparing it against your own for differences. If the code is identical it could come down to something like having two copies of the same script in the scene somewhere by mistake. Otherwise, there are several scripts that could be at fault. For example, in “BattleState” make sure you are not adding more than one listener to the moveEvent by mistake – the second one is for the fireEvent.

      Sometimes it helps to add Debug.Log statements in each place that is relevant. Does the InputController’s moveEvent only get triggered once as you would expect? Does it send the value you expect? Does the MoveTargetState’s notification handler only get triggered once? Does it receive the value you expect?

      I hope that helps, good luck!

      1. It’s finally working!
        Took me some days, but finally is working as expected. I never knew where the mistake was, but I deleted everything after the board creator tutorial and started over.
        Thank you for the help ๐Ÿ˜€

  20. Here is a diagram for the final state machine (copy paste into https://dreampuf.github.io/GraphvizOnline)

    digraph G {

    InitBattleState -> CutSceneState -> SelectUnitState -> CommandSelectionState
    CommandSelectionState -> MoveTargetState
    CommandSelectionState -> AbilityTargetState
    CommandSelectionState -> EndFacingState

    EndFacingState -> CommandSelectionState [label="right click"]
    EndFacingState -> SelectUnitState

    AbilityTargetState -> ConfirmAbilityTargetState
    AbilityTargetState -> CategorySelectionState

    CategorySelectionState -> CommandSelectionState [label="cancel"]
    CategorySelectionState -> AbilityTargetState [label="attack"]
    CategorySelectionState -> ActionSelectionState [label="set category"]

    ActionSelectionState -> AbilityTargetState
    ActionSelectionState -> CategorySelectionState [label="cancel"]

    ConfirmAbilityTargetState -> PerformAbilityState
    ConfirmAbilityTargetState -> AbilityTargetState

    MoveSequenceState -> CommandSelectionState

    MoveTargetState -> MoveSequenceState
    MoveTargetState -> CommandSelectionState

    PerformAbilityState -> CutSceneState [label="battle over"]
    PerformAbilityState -> SelectUnitState
    PerformAbilityState -> EndFacingState
    PerformAbilityState -> CommandSelectionState

    CutSceneState -> EndBattleState

    }

Leave a Reply to kafecieste Cancel reply

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