How to Check the CurrentState?

I have been playing with implementing the State Pattern which works well but I suspect it might be overkill for some applications when the Finite State Machine would suffice.

However I was wondering what the recommended method for finding out the current state is?

I have implemented it this way but think using Strings may be prone to typo’s and error’s and not the best method.

public interface BinLorryState
{
void Forward(BinLorryContext context);
void Reverse(BinLorryContext context);
void Brake(BinLorryContext context);
string ShowState(BinLorryContext context);
}

public class DriveForward : BinLorryState
{
public void Forward (BinLorryContext context)
{

}

public void Reverse (BinLorryContext context)
{
    context.SetState(new DriveBackwards());
}

public void Brake(BinLorryContext context)
{
    context.SetState(new Stop());
}

public string ShowState(BinLorryContext context)
{
    return ("DriveForward");
}

}

and then in my main class i have been using

if (ShowState() != “DriveForward”)
{
audioSource.Stop();
audioSource.clip = lorryClip;
audioSource.Play();

        }

I was thinking I must be able to do something like

If (currentstate == Forward )

In general you would not check state. Doing so would tend to create a dependency.

The pattern is normally used to send messages blindly. The best example is jumping. You hit the jump key and send a jump message. The state machine receives the jump message and responds based on the current state. If you’re in the air already nothing happens. If you’re on the ground you jump.

The jump message doesn’t care what state you’re currently in. It will always just blindly send a jump message. You don’t check. You just send. The state machine figures out what to do with that message including ignoring it.

You can always create more states if you need to perform a more specific action, like jumping while a power up is active. The whole idea and reason this pattern exists is to decouple the code. The jump message should not need to know anything about state.

In your case with playing a sound based on state you can do a few things including starting and stopping the sound inside the state machine.

If you don’t want sound in the state machine you can create an event listener pattern. Observer pattern? The audio player can subscribe to the event and it will receive a message fired by the state machine. It all works the same way; the event doesn’t care who is listening or subscribed it just sends out messages blindly. You would then create another state machine for the sound player.

Whether you should introduce more patterns or just play the sounds inside the state machine, to me, is determined based on how much this code needs to be reusable, generic, and scalable. If this is only happening for the one main player character and nothing else it might be a lot quicker and less complex to just play sounds in the state machine.

I’m looking at this pattern myself and as a solo developer the more complex state machine is not a good choice for me; I just stick with finite state machines and add empty cases. The interface forces implementation but that’s only generally useful when you’re working with a team or there is some chance new functionality will be built on top of your old functionality by someone else. This just doesn’t usually make sense for a solo developer and all these patterns do in these cases is obfuscate and make the code more verbose.

1 Like

Thanks really useful, I am now starting to get my head around it.

The bit that hasn’t managed to sink in is if I was using a Finite State Machine it would manage the state but also have a switch statement that checks the state, changes the state and then actions some code based on the state.

I have a working state machine which transitions as expected but as I will be playing a forward, backward or stop sound the individual classes do not know about the audio. So to achieve this with the state machine as suggested sounds like the correct place to do it.

How does the state machine know which one without checking the state like it does with a switch statement in the finite state machine?

Am I being a bit thick / missing something?, here is my state machine.

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

public class Driver : MonoBehaviour, BinLorryContext
{

[SerializeField] float steeringSpeed = 0.1f;
[SerializeField] float lorrySpeed = 0.1f;

BinLorryState currentstate = new Stop();

void Update()
{
    float steerAmount = Input.GetAxis("Horizontal") * steeringSpeed * Time.deltaTime;
    float driveAmount = Input.GetAxis("Vertical") * lorrySpeed * Time.deltaTime;

    if (driveAmount < 0)
    {
        steerAmount = -steerAmount;
        Reverse();
    }
    else if (driveAmount > 0)
    {
        Forward();
    } else
    {
        Brake();
    }

    transform.Translate(new Vector3(0, driveAmount, 0.0f));
    transform.Rotate(new Vector3(0, 0, -steerAmount));

    //Debug.Log(currentstate);
}

public void Forward() => currentstate.Forward(this);
public void Reverse() => currentstate.Reverse(this);
public void Brake() => currentstate.Brake(this);

void BinLorryContext.SetState(BinLorryState newstate)
{
    currentstate = newstate;
}

}

public interface BinLorryContext
{
void SetState(BinLorryState newstate);
}

public interface BinLorryState
{
void Forward(BinLorryContext context);
void Reverse(BinLorryContext context);
void Brake(BinLorryContext context);
}

public class DriveForward : BinLorryState
{
public void Forward (BinLorryContext context)
{
Debug.Log(“Still Driving Forward”);
}

public void Reverse (BinLorryContext context)
{
    context.SetState(new DriveReverse());
}

public void Brake(BinLorryContext context)
{
    context.SetState(new Stop());
}

}

public class Stop : BinLorryState
{
public void Forward(BinLorryContext context)
{
context.SetState(new DriveForward());
}

public void Reverse(BinLorryContext context)
{
    context.SetState(new DriveReverse());
}

public void Brake(BinLorryContext context)
{
    Debug.Log("Still Parked");
}

}

public class DriveReverse : BinLorryState
{

public void Forward(BinLorryContext context)
{
    context.SetState(new DriveForward());
}

public void Reverse(BinLorryContext context)
{
    Debug.Log("Still Reversing");
}

public void Brake(BinLorryContext context)
{
    context.SetState(new Stop());
}

}

You’re not being thick, this is tough to get by talking through it. You kind of need to “see” it or implement it yourself in some visual way (my opinion) to really master it. The big difficulty is we’re often trying to implement patterns into already-complex systems rather than barebones simple ones. A barebones simple example would be much easier to understand; implementing in a complex game with lots of existing code makes everything harder to “walk” through in your head. You’re mixing the pattern with your existing complex code and systems (systems that don’t necessarily have anything to do with the pattern directly).

How does the state machine know which one without checking the state like it does with a switch statement in the finite state machine?

Switch statements know and control the state and state transitions (flowing from one state to another). You’re doing this exact same thing with the interface you’re implementing; you’re just further abstracting the code (which decouples it but also makes it harder to understand when you’re learning).

Here is my super duper simple solution in pseudo code. You would need to add the references. Then decide if you want the sound decoupled or not. If you do want it decoupled you could fire Unity Events or C# events whenever the state is changed instead of playing sounds directly.

public class DriveForward : BinLorryState
{
    public void Forward (BinLorryContext context)
    {
        // I am currently moving forward and I received a request
        // to continue moving forward; this will fire repeatedly
        Debug.Log(“Still Driving Forward”);
    }

    public void Reverse (BinLorryContext context)
    {
        // I am currently moving forward and I received a request
        // to move in reverse; this will fire once as we transition away
        // to the reverse state
        StopAllCurrentSounds();
        StartPlayingLoopingReverseSound();
        context.SetState(new DriveReverse());
    }

    public void Brake(BinLorryContext context)
    {
        // I am currently moving forward and I received a request
        // to brake; this will fire once as we transition away
        // to the brake state
        StopAllCurrentSounds();
        context.SetState(new Stop());
    }
}
public class DriveReverse : BinLorryState
{

    public void Forward(BinLorryContext context)
    {
        // I am currently moving in reverse and I received a request
        // to move forward; this will fire once as we transition away
        // to the forward state
        StopAllCurrentSounds();
        StartPlayingLoopingForwardSound();
        context.SetState(new DriveForward());
    }

    public void Reverse(BinLorryContext context)
    {
        // I am currently moving in reverse and I received a request
        // to continue moving in reverse; this will fire repeatedly
        Debug.Log("Still Reversing");
    }

    public void Brake(BinLorryContext context)
    {
        // I am currently moving in reverse and I received a request
        // to brake; this will fire once as we transition away
        // to the brake state
        StopAllCurrentSounds();
        context.SetState(new Stop());
    }
}
public class Stop : BinLorryState
{
    public void Forward(BinLorryContext context)
    {
        // I am currently braking and I received a request
        // to move forward; this will fire once as we transition away
        // to the forward state
        StartPlayingLoopingForwardSound();
        context.SetState(new DriveForward());
    }

    public void Reverse(BinLorryContext context)
    {
        // I am currently braking and I received a request
        // to move in reverse; this will fire once as we transition away
        // to the reverse state
        StartPlayingLoopingReverseSound();
        context.SetState(new DriveReverse());
    }

    public void Brake(BinLorryContext context)
    {
        // I am currently braking and I received a request
        // to continue braking; this will fire repeatedly
        Debug.Log("Still Parked");
    }
}

Thanks for taking the time to write this up.

I have managed to do what I want and it’s super simple.

I added PlayAudio to the interface

public interface BinLorryContext
{
    void SetState(BinLorryState newstate);
    void PlayAudio();
}

I called PlayAudio from the state

public class Stop : BinLorryState
{
    public void Forward(BinLorryContext context)
    {
        context.SetState(new DriveForward());
    }

    public void Reverse(BinLorryContext context)
    {
        context.PlayAudio();
        context.SetState(new DriveReverse());
    }

    public void Brake(BinLorryContext context)
    {
        Debug.Log("Still Parked");
    }
}

Which is a method in the state machine for now but will be separate later.

    void BinLorryContext.PlayAudio()
    {
        audioSource.clip = reverseClip;
        audioSource.Play();
    }

Nice and simple, thanks for your help.

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms