Search Unity

A better alternative to StateMachineBehaviours

Discussion in 'Animation' started by Zalo, Nov 4, 2018.

  1. Zalo

    Zalo

    Joined:
    Sep 20, 2012
    Posts:
    8
    Hi, guys

    After working with FSMs in several projects I have come to the conclussion that using the Animator and StateMachineBehaviours complicates things too much for something that should actually be really simple. All those transitions doesn't seem to help when really some parameters need to be properly set before entering some states

    I have written a post on my blog http://zalods.blogspot.com/2018/11/simple-yet-powerful-fsm-implementation.html. of what I think is a better alternative

    Here is the implementation that I am using right now for FSM

    Code (CSharp):
    1. public class FSM2 : MonoBehaviour {
    2.     //Base class for all states, only the required methods need to be overriden
    3.     public class StateBase {
    4.         [HideInInspector]public FSM2 fsm;
    5.  
    6.         public virtual void Update(){}
    7.         public virtual void Exit()  {}
    8.     };
    9.  
    10.     public class State : StateBase { public virtual void Enter() {} }
    11.     public abstract class State1Param  < T >         : StateBase { public abstract void Enter(T p); }
    12.     public abstract class State2Params < T0, T1 >    : StateBase { public abstract void Enter(T0 p0, T1 p1); }
    13.     public abstract class State3Params < T0, T1, T2 >: StateBase { public abstract void Enter(T0 p0, T1 p1, T2 p2); }
    14.  
    15.     //Current state being handled in this FSM
    16.     public StateBase currentState { private set; get;}
    17.  
    18.     bool ChangeStateBase(StateBase _newState) {
    19.         //Exit the current state
    20.         if(currentState != null)
    21.             currentState.Exit();
    22.  
    23.         //Change to the new state
    24.         currentState = _newState;
    25.  
    26.         if(_newState != null) {
    27.             _newState.fsm = this;
    28.             return true;
    29.         }
    30.         return false;
    31.     }
    32.  
    33.     public void ChangeState(State _newState) {
    34.         if(ChangeStateBase(_newState)) _newState.Enter();
    35.     }
    36.  
    37.     public void ChangeState< T >(State1Param< T > _newState, T p) {
    38.         if(ChangeStateBase(_newState)) _newState.Enter(p);
    39.     }
    40.  
    41.     public void ChangeState< T0, T1 >(State2Params< T0, T1 > _newState, T0 p0, T1 p1) {
    42.         if(ChangeStateBase(_newState)) _newState.Enter(p0, p1);
    43.     }
    44.  
    45.     public void ChangeState< T0, T1, T2 >(State3Params< T0, T1, T2 > _newState, T0 p0, T1 p1, T2 p2) {
    46.         if(ChangeStateBase(_newState)) _newState.Enter(p0, p1, p2);
    47.     }
    48.  
    49.     void Update() {
    50.         //Update the currentState
    51.         if(currentState != null)
    52.             currentState.Update();
    53.     }
    54. }
    Here is how you can declare an State with 3 enter parameters
    Code (CSharp):
    1. [System.Serializable]
    2. public class CarPieceState : FSM2.State3Params< int, PieceSelected, int> {
    3.     int vehicleIdx;
    4.     PieceSelected piece;
    5.     int defaultIndex;
    6.     public override void Enter(int _vehicleIdx, PieceSelected _piece, int _defaultIndex) {
    7.         //Select _defaultIndex of piece _piece and vehicle _vehicleIdx
    8.     }
    9. };
    And this is how you can Change to that state
    Code (CSharp):
    1. fsm.ChangeState(carPieceState, 0, PieceSelected.Piece0, -1);
    so far this has worked pretty for me, what do you guys think?
     
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Using StateMachineBehaviours for anything else than modifying the animation while playing turned out to be the worst idea ever. That's no fault of the StateMachineBehaviours, they were not meant for anything else. They were poorly named, that's all.

    Anyways, having a custom FSM solution is for sure the way to go.

    Your solution... isn't really a fsm? You can enter the same state in several different ways, since the states' Enter methods have parameters. Which means that you have infinite possible states. That's not necessarily a problem - ironically you'll probably be able to keep the number of states down by not having to repeat similar states.

    I'm very sceptical about the fsm runner handling switching to a null state. If you want a state that doesn't do anything, you should have a special "do nothing" state, and then instead throw exceptions when it's asked to switch to null. Or just let it nullreference.

    You could really post this in the Scripting forum as it's not very animation relevant.
     
  3. Zalo

    Zalo

    Joined:
    Sep 20, 2012
    Posts:
    8
    Well, for what I've seen there are lots of people using animators as FSMs and also encouraging others to do so (even tweets from the unity staff), it is not just a matter of a poor name choosing, I think the initial idea was to expand the animator so it could be actually used as an FSM but it just doesn't work

    No, it doesn't work like that. You need to inherit from one of the States depending on how many parameters you need on your enter state in order to create a new one. Then you can only enter this state if you pass these exact parameters. Since in C# there is no multiple inheritance you cannot have multiples Enter methods

    Not sure why that will be better... just a simple check to null and I don't need to create an empty state. The main reason why I have that code is because when the FSM is created it doesn't have any state assigned yet, that also let people change to an empty state, but I don't see any problems with that (although, yeah, it is weird)

    You are right, I am gonna do it now
     
  4. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    That's a very interesting approach.

    I included my own state machine system which has a few differences from yours in one of the demo scenes of my Animancer plugin:
    • It's basically a dictionary that keeps track of the active state so you can use an enum to identify states.
    • It asks states if they can enter and can exit before doing a transition. Idle might be happy to get interrupted at any time, but an Attack can only be interrupted by high priority actions like Flinch.
    • The base state is an interface so states can be MonoBehaviours or ScriptableObjects if you want.
    • That also means the state machine and base interface don't need an Update method. If a state wants to be updated, it can be a MonoBehaviour and have its own Update method. Which also means it's not limited to only Update; states can have FixedUpdate, LateUpdate, OnCollisionEnter, or whatever else they need.
    Your approach is actually very similar to what I've done with my Animancer plugin itself. Instead of pre-defining a state machine you just pass in the animations you want to play when you want to play them. I had considered doing something similar for my general purpose state machine but couldn't really see a significant advantage over my dictionary approach.

    Something I never considered though is the ability to make states which require parameters to be activated. That would be really great to make it clear upfront that an Attack needs a target rather than only having a runtime error if you try to attack without the Creature's Target property being set or worse yet, having nothing happen if you didn't put in that error message.

    One limitation of that approach is that I can't look at your call to fsm.ChangeState(carPieceState, 0, PieceSelected.Piece0, -1); and know what those parameters are actually for (particularly the two ints). Intellisense will only show the parameter names of ChangeState, not of CarPieceState.Enter and the quickest way to get there is to go to the definition of carPieceState then to the definition of its type then find the Enter method. As a result I'd be inclined to only have states with 0 or 1 parameters so if someone needs more they need to make a simple struct.

    I also wouldn't call it State1Param<T>. Types can share the same name if they have a different number of generic parameters so you can just call it State<T>.
     
  5. Zalo

    Zalo

    Joined:
    Sep 20, 2012
    Posts:
    8
    Well, I am not sure why you need a Dictionary but using them always have a little overhead. I am passing the states that I have in variables so no searching is required at all. Besides I can assign fields to my states in the editor, but you cannot display dictionaries on it

    I was expecting an IDE like VS to be able to show them.... but you are right, it seems that it can't and it's actually a little bit annoying. I like your solution a lot, it is much better. Creating different constructors for the struct also let's you enter the same State with different enter inputs

    Yeah, much better!

    Thanks a lot!