Reputation: 5
I'm struggling with this for a couple of weeks and need help with understanding and building a state machine for my game.
What I need is something like this:
Basically, FSM class is just for changing states:
public class FSM: MonoBehaviour
public StateHandler currentState { get; private set; }
private void Start()
{
currentState.OnEnterState();
}
public void ChangeState(StateHandler newState)
{
currentState.OnExitState();
currentState = newState;
currentState.OnEnterState();
}
State class is something like this:
public class StateHandler : FSM
public virtual void OnEnterState()
{
}
public virtual void OnExitState()
{
}
public virtual void StateLogicUpdate()
{
}
public virtual void StatePhysicsUpdate()
{
}
void Update()
{
StateLogicUpdate();
}
void FixedUpdate()
{
StatePhysicsUpdate();
}
State Attack class or any other states are the same as State class but with override methods and actual code and no update methods.
I have several questions that I don't understand yet:
And if you don't mind, answer like I'm very stupid. :) Thanks a lot!
Upvotes: 0
Views: 765
Reputation: 11281
I don't know unity, but I know state machines.
First things first, your state machine diagram looks off. "states" can be considered objects, but one of your rectangles says "abstract class". You can never have an object of an abstract class.
Anyhow. Like the word says, a "state" stores the state of an object or system. It's a form of memory. Based on that state and external variables things can happen
The simplest form of a state machine in code uses something like an enum
using System;
State currentState = State.Idle;
while (true)
{
switch (currentState)
{
case State.Idle:
Console.WriteLine("Doing Nothing");
if (/* some external trigger is */true) currentState = State.Loading;
break;
case State.Loading:
Console.WriteLine("Loading");
if (/* loading issue is true, but now*/false)
{
Console.Error.WriteLine("Loading failed.");
currentState = State.Idle;
}
if (/*loading finished is */true) currentState = State.Saving;
break;
case State.Saving:
Console.WriteLine("Saving");
// etc
if (/*saving finished is */true) currentState = State.Idle;
break;
}
}
enum State
{
Idle,
Loading,
Saving,
}
However, it seems you want a more complex system, where things can happen when entering or exiting a specific state. That requires a more complex state type, which can be defined as an abstract class
(It might even be error prone)
internal abstract class State
{
public virtual void OnEnter() { } //optional
public abstract void LogicUpdate(); // required.
public abstract void PhysicsUpdate(); // required.
public virtual void OnExit() { } // optional
}
Each of your states can then be implementations of this type. E.g.:
internal sealed class IdleState : State
{
public override void OnEnter()
{
Console.WriteLine("IdleEnter");
}
public override void LogicUpdate()
{
Console.WriteLine("IdleLogic");
}
public override void PhysicsUpdate()
{
Console.WriteLine("IdlePhysics");
}
// public override void OnExit() <-- not used in his example, as its optional
}
internal sealed class SaveState : State
{
public override void LogicUpdate()
{
Console.WriteLine("SaveLogic");
}
public override void PhysicsUpdate()
{
Console.WriteLine("SavePhysics");
}
public override void OnExit()
{
Console.WriteLine("SaveExit");
}
}
You got your FSM almost right in this case
internal class FSM {
private State _currentState = new IdleState();
public void Handler() // called from main loop
{
_currentState.LogicUpdate();
_currentState.PhysicsUpdate();
if (_currentState is IdleState)
{
if (/*a certain condition is*/ true) ChangeState(new SaveState());
// remember to call ChangeState.. dont set _currentState directly
// (might be solved with better encapsulation...)
}
else if (_currentState is SaveState)
{
if (/*a certain condition is*/ true) ChangeState(new IdleState());
}
}
public void ChangeState(State newState)
{
_currentState.OnExit();
_currentState = newState;
_currentState.OnEnter();
}
}
And then you only have to add a main loop:
var fsm = new FSM();
while (true)
{
fsm.Handler();
}
Upvotes: 0