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:

TacticsRPG_CameraRigHierarchy_zpswtm0p34p

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.

38 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.

Leave a Reply

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