Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How should I approach finite state machines in Unity?

Discussion in 'Scripting' started by AssembledVS, Oct 17, 2016.

  1. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    Upon animating my character, I realized that I will need to reference a variety of character states, like walking, attacking, idling, etc. To prevent a character from walking while attacking, for example, I would need to know when one or the other is happening.

    Fine state machines in Unity often use the Animator itself. And Unity animation itself is organized via a state machine. But state machines can also be created in pure code without using the Animator.

    What is the recommended Unity approach? Should I have a separate animation Animation Controller and a "state" Animation Controller? Or should they be combined into one? Or should I create a state machine in code instead?

    I'm having a conceptual problem here. It seems to me like the player state and the player's animation state are related but not equivalent. Continuing the example above, a player can transition from walking to attacking and from attacking to walking, but should not be able to walk while attacking. It somehow seems to me that the condition of not walking if attacking should be placed outside of the player's basic Animation Controller where all animations are handled and assigned, either in another controller/state machine or in outside code outside of the Animator.
     
    migsnandez and Evercloud like this.
  2. absolute_disgrace

    absolute_disgrace

    Joined:
    Aug 28, 2016
    Posts:
    253
    It sounds like you already have the right idea.

    My suggestion is to use the Model/View approach. Work out what states are visual states and what states are about the action states. Let your animator handle the states which would be the visual ones (The View). Inside your script attached to your character, use some global booleans to handle the actions states (The Model). Whenever your script needs to perform an action, it can set and check the state values it cares about. The unity engine will handle your visual states based on how you set them up. You may sometimes find there is an overlap of actions and visual states, but that just means you can use your action states to call the animator triggers.
     
    JiaChing and AssembledVS like this.
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,181
    Never, ever, ever use the Animator as an FSM. It's a pain! It's not made for that!

    The Animator models a kind of state machine where the machine can be transitioning between two states. The StateMachineBehaviours sends messages based on the animation state, meaning that you'll get Update calls from both during the transition, and the OnStateEnter call comes a bunch of frames before the OnStateExit call.

    The transitions are also a pain. Using triggers and bools to control your animations is (probably) a design flaw, but you can deal with it. For your FSM, you really want to be in direct control over what state you're in, and not go through the animator's DSL.

    I'm running our AI system on the Animator as a state machine, and it's a hassle. Write a pure code state machine to control your states, and have the state of the animator be a direct consequence of that state machine.
     
    jiraphatK, AngelKraft, X3doll and 4 others like this.
  4. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    Thanks for this explanation. I guess bools are the way to go, more so than enums, which I started with, since there may be multiple true states active at once.

    It is good to hear someone say this. I also didn't like the idea of using the Animator to handle non-animation states. Thanks!
     
  5. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,521
    If you have multiple true states (as you will with a character), this really calls for the layered FSM approach you hinted at, with each FSM in a single state.

    Often you might have layers of FSMs like this:

    [Control]
    |
    [Action]
    |
    [Body], [Head], [Weapon]
    |
    [High Level Animation]
    |
    [Mecanim Animator]​

    where you have a strong separation of duties. For example, the [Mecanim Animator] layer is only concerned with playing animations. The [High Level Animation] layer determines which high level animation states to play according to the [Body] [Head], and [Weapon] layers, and delegates the actual playing of those animations to the [Mecanim Animator]. The [Action] layer receives input from the [Control] layer (e.g, "jump button pressed") and determines what to do on a high level (e.g., if the character is in a state where he can jump, tell the [Body] to jump).

    This keeps each FSM very simple since they're only concerned with their limited role.

    I've usually written these FSMs in text code (C#), but there's no reason you couldn't use something like PlayMaker to make it easy to see their states at runtime.
     
    davidseth8 likes this.
  6. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,840
    A little while back I wrote an article for Gamasutra that touched on this issue: 2D Animation Methods in Unity. The state machine bits apply equally well in 3D. Perhaps you'll find it useful.
     
    Baste and TonyLi like this.
  7. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    My experience is a bit simpler than programming models and structured approaches.

    In my opinion (I stress that strongly- lots of really experienced people posting before me is adding some pressure here ^_~), you should treat Mecanim as if it's a projection of the things being decided by the logic elsewhere, and not as if it's deciding that logic for itself.

    In other words, your animator controller should account for all of the possibilities, moving fluidly from State A to State B, blending this with that in varying amounts, or not blending at all and jumping straight to a particular animation state when needed- but these possibilities should be decided and enforced before the commands even reach the animator. It can broadcast events when necessary, using a combination of curves and state machine behaviors (when the sword in a swing is at the angle where contact should occur, when a foot is coming in contact with the ground, etc...), but it really shouldn't be in charge of determining what to do with that information, or whether to do anything at all- just broadcast it back an event handler or character controller so that they can do with it what they will.
     
    Last edited: Oct 17, 2016
    TonyLi and JoeStrout like this.
  8. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    Thanks for all of these replies. The nested state idea makes a lot of sense - one true state per layer. It's perhaps too complex for what I need now but good to know for the future.

    Lysander - I think that you more or less agree with everyone else about not relying on Mecanim as a FSM and using Mecanim solely as the animation component. I like your approach, thanks!

    What's a reasonable way of figuring out when the player is walking, attacking, etc. if it is based on the animations but I am writing the FSM code in C#? What I mean is, if the player is attacking, how do I determine when the attack ends? Should I just attach a StateMachineBehaviour script to the animation and poll its OnStateExit or use it to directly set the enum/bool for isAttacking in another script?
     
  9. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    An easy way to is just have the attacks be a standardized amount of time and work your animations (and animation speeds, for fine-tuning) to adhere to that restriction. If you feed Mecanim a value for "normal attack time", then the animation states can use that value to manipulate the time taken to swing a sword, or whatever. This makes it easy to predict.

    You can use OnStateEnter/Exit to broadcast state changes back to the controller, or a generic event manager (my favourite), but be careful, because due to animation blending, the given state might not start and end at the points that you think they do.

    This is slightly off-topic, but another thing to note is that you can use curves within the animation importer to adjust variables within the animator controller, or even fire events in a way that supports animation blending. The curves don't affect the animations at all normally, but if, for instance, you make a "LeftFootfall" curve in the importer and have a "LeftFootfall" float value in the animator controller, it'll automatically update that value as the clip is played. If multiple animations are being blended with the same curve name, they will average out. This can be a fantastic way to implement events in situations where animation blending makes it difficult to know when a particular point in an animation is reached.

    Using the LeftFootfall example, if you match up the curve to the walking and running animations' left feet hitting the ground at the highest curve value (+1), then back in the animator (in a State Machine Behavior script), the moment that the blended value reaches an apex (near +1) and starts to drop, you know a footfall just occurred and can broadcast a "play footstep sfx" event, or whatever. This can be fantastically useful.
     
  10. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    Thank you. I have not had the blending become an issue yet (I'm using pixel art and have not manipulated the blend trees beyond four extreme points: 0,1, 1,0, 0,-1, -1,0).

    When you say "broadcast state changes back to the controller," is this my custom controller or something within Mecanim?

    Does this script seem fine? I attach it to every relevant Mecanim state on the player. OnStateEnter manipulates the actual states stored in another script. It works, though I'm unsure of whether I'm setting myself up for problems later. Is referring to the states' string names during runtime too taxing on performance?

    Code (CSharp):
    1. public class PlayerAnimationState : StateMachineBehaviour
    2. {
    3.     private PlayerState playerState;
    4.  
    5.     // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    6.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo,
    7.                                       int layerIndex)
    8.     {
    9.         playerState = animator.transform.gameObject.GetComponent<PlayerState>();
    10.  
    11.         // idle
    12.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("Idle"))
    13.         {
    14.             playerState.ChangePlayerAnimState(PlayerState.PlayerAnimState.idle);
    15.         }
    16.  
    17.         // walk
    18.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("Walk"))
    19.         {
    20.             playerState.ChangePlayerAnimState(PlayerState.PlayerAnimState.walking);
    21.         }
    22.  
    23.         // melee attack
    24.         if (animator.GetCurrentAnimatorStateInfo(0).IsName("Melee Attack"))
    25.         {
    26.             playerState.ChangePlayerAnimState(PlayerState.PlayerAnimState.meleeAttacking);
    27.         }
    28.     }
    29. }
     
  11. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    I haven't dealt with 2D sprites in Mecanim beyond making a canvas group's alpha level animate from 0 to 1, so I can't really comment on that, or how sprites might change the way that the system works.

    What I meant by "controller" is some external character controller or event manager, not something built into Mecanim or anything. "playerState" in your setup works, but you can actually use the "Animator" parameter to find the component (and by extension, GameObject) that's using this animator controller. This makes it a little more versatile, since your player isn't necessarily the only character that needs event callbacks, you know?

    Checking a few string values in a single-fire event like OnStateEnter doesn't seem like a huge deal to me from a performance standpoint (if it were in OnStateUpdate, then maybe), but there's no reason not to use "else if" instead at the very least- it can't be "Melee Attack" if it was already "Idle". Worth noting that there's a mistake there, as you need to put the layername before the statename in the IsName() function, so for instance "Layer1.Idle" instead of "Idle". Also, using string checks is just generally evil, because you might change the name of a state or the layer later and not realize you just broke things.

    Making a different behaviour script for each state type to broadcast back to the controller would mean more leeway if you want to do anything else special for different states- for instance, checking on OnStateUpdate for the walking state won't mean extra pointless calls to OnStateUpdate for your Idle and MeleeAttacking states too. if you do want to have one behaviour for a few different states, then I'd recommend making an enum with the various state names and then just select the proper state from a list in the inspector. So, for instance, select "Idle" from the dropdown list in the inspector for the idle state, then use a switch for the enum value in the OnStateEnter function. Much faster and more efficient, and no string checks.
     
    Last edited: Oct 17, 2016
  12. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,181
    Hey, you wrote that! It's a really good article!

    This one's a bit strange. You could just use a public field for the SMB, set in in the inspector, and send that to the playerState. Changes in red:

    Code (csharp):
    1.  
    2. public class PlayerAnimationState : StateMachineBehaviour
    3. {
    4.     //Assigned in the inspector
    5.     [SerializeField]
    6.     private PlaterState.PlayerAnimState state;
    7.     private PlayerState playerState;
    8.  
    9.     // OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
    10.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo,
    11.                                       int layerIndex)
    12.     {
    13.         // Only do the GetComponent on the first time you enter the state.
    14.         playerState = playerState ?? animator.transform.gameObject.GetComponent<PlayerState>();
    15.  
    16.         playerState.ChangePlayerAnimState(state);
    17.     }
    18. }
    Now, I would also rename ChangePlayerAnimState. You're not changing the animation state, since the message is sent after the state has changed! It should probably be NotifyOfAnimState.
     
  13. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    Thanks for all of this. Yes, the if-else is better in this case. Also, just "Idle" or "Melee Attack" works without the layer name preceding it. I agree about the string checks - this was the biggest question for me, as I try not to check strings when possible.

    I actually started with a different behavior for each state. I thought it may turn into too many scripts, though I suspected that it would be better for performance to not check every state in a single script like I'm doing now. Probably one script per state is ideal.

    Do you think that it will still be ideal once the player has these scripts, then the different NPCs, then the enemies, etc. Or am I somehow reusing these same scripts for similar functionality?

    Yes, I realized this last night, thanks! Somehow, I didn't think that I could use accessible variables that would show up in the inspector for StateMachineBehaviour scripts. I see that they do.

    EDIT: Actually, I didn't realize this. This is great. Thanks.

    I see what you're saying. My thinking was not that I was changing the actual animation state, but that the state relates to the animation, so I am changing the animation-related state. (There can be other non-action states, like isKillable, which has nothing to do with the animations.) In that ChangePlayerAnimState method, I am actually changing the enum state:

    Code (CSharp):
    1. /// <summary>
    2. /// This method changes the player's animation-related state and stores the previous
    3. /// animation-related state.
    4. /// </summary>
    5.  
    6. public void ChangePlayerAnimState(PlayerAnimState playerAnimState)
    7. {
    8.     // store current state as previous, as it will be changed below
    9.     playerAnimStatePrevious = (PlayerAnimStatePrevious)this.playerAnimState;
    10.  
    11.     // change global player anim state to parameter's value
    12.     this.playerAnimState = playerAnimState;
    13. }
    Though I can see how this can be confusing. I should think more about my naming schemes.
     
    Last edited: Oct 18, 2016
  14. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    That's always a big question, and the answer is always "it depends". If you point to an interface for a character controller instead of the player-character controller (GetComponent works with interfaces, keep in mind), you can just re-use the same statemachinebehaviour scripts for all of them. So, in that case, "ChangePlayerAnimState()" would be defined in the interface (though you'd likely have to rename it to just "AnimStateChanged()" or something), and the player controller would implement that interface, as would the NPC controllers, get it?

    Interfaces are awesome.
     
    JoeStrout likes this.
  15. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    One more thing that I don't think anyone's mentioned: you can add "events" to an animation in the model's animation inspector. For example, add a "OnMeleeHit" event in the sword-swinging animation at the point in the animation where the sword would connect with the enemy, then you can catch that event in code and trigger damaging the opponent or creating particle effects or whatever, and possibly update your FSM as well. I've found that things are generally cleaner if I can use animation events rather than doing checks on the current state or trying to use timers.
     
  16. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    This can actually create problems in situations where animation blending occurs, though that's not likely to be an issue here with sprites instead of models. If both animations being blended have events (walk and run both having "footfall" events, for instance), it'll actually fire multiple times since it counts both animations as playing simultaneously.

    I'm also uncertain how the Events tie into Mecanim- unlike the Curves situation, the documentation doesn't specify that there's a clean way to access the data from an animation controller (changing the target for the SendMessage broadcast). If you have to specify the receiving object in the inspector on the animation import screen, that severely limits its usability in situations where multiple characters can use the same animations.
     
  17. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Hadn't thought of that; I'm only using it in cases where it wouldn't be blending two animations with the same events. I can see how that would be messy; curves are probably better for that.

    It's actually pretty simple. It just calls the method named whatever the event name is on all components attached to the same game object as the animator. So if you name the event "OnFootstep", it will call every "void OnFootstep()" method on all attached components.
     
  18. AssembledVS

    AssembledVS

    Joined:
    Feb 23, 2014
    Posts:
    248
    Thank you. I still have not really looked at interfaces. This makes a lot of sense, though I'm going to hold off on a lot until I get a prototype that I'm happy with.

    Thank you. I know about them but have not used them. Your example about the sword swing is good to know.
     
  19. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    35
    I am researching this approach of linking different FSM's together to handle a more complex setup (complex to me anyways). I was wondering if you could point me to a code example of how you tie the different state machines together into a cohesive unit.

    Right now I am building out an enemy and wanted to experiment with separating a movement state machine with an attack state machine. I was starting to go down a path of looking into Behaviour Tree's but I don't know if this is something I want to tackle atm.

    Any example you can point to would be amazing so I can take a peak at how things are connecting to eachother.
     
  20. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,521
    In academic AI, it's generally called subsumption. Here's a powerpoint that I searched up real quick: Subsumption Architectures. I only skimmed through it, but it looks good.

    In AI Game Programming Wisdom 3, Igor Borovikov (at SCEA at the time, now at EA on The Sims) wrote a good article on what he called Orwellian State Machines. Here's an audio recording of his talk at GDC 2005 if you can't get your hands on AI Wisdom 3.

    I've attached a presentation/example scene I did for a local developer group a while ago. The example's FSMs are purpose-built in code, so you won't be able to lift them as-is. And since they were part of a presentation, they may be partially incomplete in their current state. But, if you're curious, feel free to take a look to get a rough idea.
     

    Attached Files:

  21. adamgffrd

    adamgffrd

    Joined:
    Sep 26, 2018
    Posts:
    35
    Yeah so everything about your reply was amazing. I am super pumped right now, thank you SO MUCH for replying to me so quickly and with all of that amazing information. The power point gave me a good understanding, and then I realized you provided a project and after reviewing the different state scripts it's EXACTLY the direction I wanted to move in. I understand the Blackboard concept, a place to store variables so we can access them from the states, but I am still a somewhat new programmer so this at least gives me something to chew on and experiment with for the remainder of the week.

    Attached is the FSM I currently have employed with my boss enemy guy .... thing. I am VERY excited to dice mine up and make it more modular, less like the spaghetti that it is now. :)
     

    Attached Files:

    TonyLi likes this.