SupriseMechanics
SupriseMechanics

Reputation: 31

How to Implement an Event Driven State Machine in Unity?

I've implemented a simple finite state machine in Unity which I've used in a few projects, but I keep running into the same issue: how do I communicate with an object's state from an external script?

Say I have a Player class that has an Idle and a PoweredUp State. I want to send an event from another class that has different outcomes depending on which state the player class is in (e.g. the damage manager sends an "ApplyDamage" event, which kills the player if it is in the Idle state and sends them into Idle if they're in the PoweredUp State).

There was a scripting language at a company I worked for that did this through events, where you could broadcast events to a state script with any number of parameters and they would behave differently depending on what state the script was in. Is this possible in C#? Is there a better approach? What would be the best way to design this?

State Machine script:

public class StateMachine {

private State previousState;
private State currentState;
public State CurrentState { get { return currentState; } set { currentState = value; } }

public void ChangeState(State newState)
{
    if (currentState != null)
    {
        currentState.Exit();
    }
    previousState = currentState;
    currentState = newState;
    currentState.Enter();
}

public void ExecuteStateUpdate()
{
    if (currentState != null)
    {
        currentState.Execute();
    }
}

public void ReturnToPreviousState()
{
    currentState.Exit();
    currentState = previousState;
    currentState.Enter();
}
}

State script:

[System.Serializable]
public abstract class State {
    public virtual void Enter() { }
    public virtual void Execute() { }
    public virtual void Exit() { }
}

Example script:

public class Player : MonoBehaviour
{
    StateMachine stateMachine = new StateMachine();
    Idle idleState => new Idle(this);
    PoweredUp poweredUpState => new PoweredUp(this);

    private void Start()
    {
        stateMachine.ChangeState(idleState);
    }
    private void Update()
    {
        stateMachine.ExecuteStateUpdate();   
    }

    // -----------------------------------------------------
    public abstract class Base : State
    {
        protected Player owner;
        public Base(Player owner) { this.owner = owner; }
    }
    // -----------------------------------------------------
    public class Idle : Base
    {
        public Idle(Player owner) : base(owner) { }

        public override void Execute ()
        {
            // do update stuff
        }
    }
    // -----------------------------------------------------
    public class PoweredUp : Base
    {
        public PoweredUp(Player owner) : base(owner) { }
        public override void Enter()
        {
            // play power up animation
            // play power up sound
        }
    }
}

So far I've tried adding Event(string eventName) to the State class and then running a switch function on eventName in my states, but this does not allow me to pass parameters with the event and relies on strings which gets messy fast.

I've also just added functions outside the states that check what the currentState is set to and then behave accordingly, but that just defeats the whole point of a state script altogether.

Any help is much appreciated!

Upvotes: 2

Views: 3183

Answers (1)

TJHeuvel
TJHeuvel

Reputation: 12618

You can use C# events for this, or Unity's own UnityEvent if you want them serialized in the Editor. They allow you to pass arguments.

In the case of a powerup you could emit an event that the player listens to, and using the event arguments you can decide for how long to be in the state etc. Alternatively you can have the player emit an event for when the state changes, and the powerup listens to that.

Upvotes: 0

Related Questions