Context State Machine

Introduction

This tutorial teaches how to use contexts as a state for another context. This is one of the powerful features of IoC+ to isolate complexity from game states to the states of an epic end-boss battle. If you’ve never head about state machines before, we’d recommend reading into it first. Sourcemaking is a great place to start.

This tutorial starts with a fresh new project. If this is the first time using IoC+, we suggest to read the introduction first.

Log Context Command

In this tutorial we’re going to setup a state machine of a game that can go from a menu state to a level state and back again. In IoC+, contexts can function as states for another context. Let’s start by creating a command that logs the context type that it is in. Create a new script called “LogContextCommand.cs” and add the following code.

using IoCPlus;
using UnityEngine;

public class LogContextCommand : Command {

    [Inject] IContext context;

    protected override void Execute() {
        Debug.Log(context.GetType());
    }

}

Simple. The command injects the current context and logs its type on execution.

Context States

Now that we can log our context, let’s add a menu state to our game. Create a new script called “MenuContext.cs” and add the following code.

using IoCPlus;

public class MenuContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        On<EnterContextSignal>().Do<LogContextCommand>();
    }

}

The menu context performs the LogContextCommand when it is entered, triggering it to log the MenuContext type in the debugger of Unity.

Context State Machine

Let’s create the state machine context: the game context. This context will set its state to the menu context state once it is entered. Create a new script called “GameContext.cs” and add the following code.

using IoCPlus;

public class GameContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        On<EnterContextSignal>().GotoState<MenuContext>();
    }

}

When the game context is entered, it triggers the GotoState<T>() to go to the menu context state. This will instantiate the menu context, add it as a child context to the game context and set it as the game context’s state.

Note that if we would use the GotoState<T>() later to go to another state, the current state would be left, effectively calling its LeaveContextSignal. To prevent the EnterContextSignal and LeaveContextSignal from being dispatched, the SwitchState<T>() method can be used instead.

To test the code that we have set up we will need a root context for the game context. Create a new script called “GameRoot.cs” and add the following code.

using IoCPlus;

public class GameRoot : ContextRoot<GameContext> { }

Add the game root as a component to a new gameobject in your scene and hit play. The Unity console should now log “MenuContext” as the state is entered on entering the game context.

Switching States

States are only interesting when there is more than just one! Let’s create a second state that would represent the level of our game. Create a new script called “LevelContext.cs” and add the following code.

using IoCPlus;

public class LevelContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        On<EnterContextSignal>().Do<LogContextCommand>();
    }

}

Much like the menu context, the level context logs its type on enter.

Now that we have two context states, we’ll need some signals to dispatch to which state we want to go. Create a new script called “StartLevelSignal.cs” and add the following code.

using IoCPlus;

public class StartLevelSignal : Signal { }

Let’s also create a signal to go back to the menu. Create a new script called “OpenMenuSignal.cs” and add the following code.

using IoCPlus;

public class OpenMenuSignal : Signal { }

Let’s open up the game context and use these signals to go to the according state.

using IoCPlus;

public class GameContext : Context {

    protected override void SetBindings() {
        base.SetBindings();

        Bind<StartLevelSignal>();
        Bind<OpenMenuSignal>();

        On<EnterContextSignal>().GotoState<MenuContext>();
        On<OpenMenuSignal>().GotoState<MenuContext>();
        On<StartLevelSignal>().GotoState<LevelContext>();
    }

}

We inject the StartLevelSignal and OpenMenuSignal, and use the GotoState<T>() to go to the according state as a response to the signals.

The IoC+ Moniter

You can expect a level selector view in the menu that will dispatch the StartLevelSignal and a pause menu view that can dispatch the signal to go back to the menu context. But instead of filling this tutorial with creating views and mediators, let’s test our state machine via the IoC+ Monitor. Go ahead and open it up!

Notice how the child context is marked by a green line? This means it is the state of the context! Use the interaction features of the IoC+ Monitor to dispatch the StartLevelSignal and OpenMenuSignal. Doing this while being in delay mode shows us that the LeaveContextSignal of the state that is being left is dispatched. We could use this to for example destroy the views of that context.

Nested States

States are incredibly powerful in IoC+, as they allow us to isolate the complexity of the contexts. We could even turn our state into a state machine itself! This could be useful when we the level of our game could be in multiple states as well.

To test a future state machine context, its also possible to right-click on a context in the IoC+ Monitor. Simply go to “Goto State” and select the context that needs to be the new state of that context. Go ahead and try to set the state of the level context.

Conclusion

In this context we’ve learned how to set the state of a context via the GotoState<T>() method. To prevent the EnterContextSignal and LeaveContextSignal of the state context from being dispatched, we could use the SwitchState<T>() method instead. Using the IoC+ Monitor we can easily see, modify and test our current state. Lastly, we’ve learned that states can have states as well, giving us the opportunity to infinitely split up the complexity of our project.