State Machine Behaviours

Проверено с версией:: 5

-

Сложность: Средняя

State machine behaviours are scripts that can be attached to animator states or sub-state machines in an Animator Controller. These can be used to add all sorts of behaviour that is state dependent such as playing sounds whenever you enter a state. They can even be used to make logic state machines which are independent of animation. To add a state machine behaviour to a state or sub-state machine, click the Add Behaviour button in the inspector.

Alt SMB 01

From here you can choose from existing state machine behaviours or create a new one.

Alt SMB 02

New state machine behaviours are created in C#.

All state machine behaviours inherit from the same base class, StateMachineBehaviour. Since they support inheritance, if there is functionality that you wish to add to multiple classes then that can be easily achieved. For more information on inheritance, see the information linked below.

StateMachineBehaviour functions

The core of state machine behaviours are 5 functions that are automatically called whilst in an animator state and 2 functions that are automatically called whilst in a sub-state machine. Commented out versions of some of these functions are added to new state machine behaviours when they are created. These functions are OnStateEnter, OnStateUpdate, OnStateExit, OnStateMove and OnStateIK. The additional functions for sub-state machine transitions are OnStateMachineEnter and OnStateMachineExit.

All of these functions have 3 parameters passed to them: an Animator, an AnimatorStateInfo and the layer index.

Code snippet

override public void OnStateEnter (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
}
  • The Animator parameter is a reference to the specific animator that this state machine behaviour is on. For example, this could be used to set the values of animator parameters that were only necessary in this state, such as those for a blend tree.

  • The AnimatorStateInfo is the current info for the state that the state machine behaviour is on. It is the equivalent of writing animator.GetCurrentStateInfo(layerIndex); This can be useful in operations that involve the normalised time of the clip.

  • The layerIndex is the layer the state machine behaviour’s state is on. For example, zero for the base layer, one for the first, and so on.

As with MonoBehaviours, the functions of a StateMachineBehaviour are called under specific circumstances.

  • OnStateEnter is called on the first frame of the state being played.

  • OnStateUpdate is called after MonoBehaviour Updates on every frame whilst the animator is playing the state this behaviour belongs to.

  • OnStateExit is called on the last frame of a transition to another state.

  • OnStateMove is called before OnAnimatorMove would be called in MonoBehaviours for every frame the state is playing. When OnStateMove is called, it will stop OnAnimatorMove from being called in MonoBehaviours.

  • OnStateIK is called after OnAnimatorIK on MonoBehaviours for every frame the while the state is being played. It is important to note that OnStateIK will only be called if the state is on a layer that has an IK pass. By default, layers do not have an IK pass and so this function will not be called. For more information on IK see the information linked below.

  • OnStateMachineEnter is called on the first frame that the animator plays the contents of a Sub-State Machine.

  • OnStateMachineExit is called on the last frame of a transition from a Sub-State Machine.

Example of using State Machine Behaviours

Consider a beat’em up style game where when you perform special moves, you want particle systems to spawn and follow whichever limb is doing the attacking. This script could be used on the animator state containing the special move’s animation.

Code snippet

using UnityEngine;

public class SpecialAttackParticlesSmb : StateMachineBehaviour
{
    public GameObject particles;            // Prefab of the particle system to play in the state.
    public AvatarIKGoal attackLimb;         // The limb that the particles should follow.


    private Transform particlesTransform;       // Reference to the instantiated prefab's transform.
    private ParticleSystem particleSystem;      // Reference to the instantiated prefab's particle system.


    // This will be called when the animator first transitions to this state.
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // If the particle system already exists then exit the function.
        if(particlesTransform != null)
            return;

        // Otherwise instantiate the particles and set up references to their components.
        GameObject particlesInstance = Instantiate(particles);
        particlesTransform = particlesInstance.transform;
        particleSystem = particlesInstance.GetComponent <ParticleSystem> ();
    }


    // This will be called once the animator has transitioned out of the state.
    override public void OnStateExit (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // When leaving the special move state, stop the particles.
        particleSystem.Stop();
    }


    // This will be called every frame whilst in the state.
    override public void OnStateIK (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
    {
        // OnStateExit may be called before the last OnStateIK so we need to check the particles haven't been destroyed.
        if (particleSystem == null || particlesTransform == null)
            return;
        
        // Find the position and rotation of the limb the particles should follow.
        Vector3 limbPosition = animator.GetIKPosition(attackLimb);
        Quaternion limbRotation = animator.GetIKRotation (attackLimb);
        
        // Set the particle's position and rotation based on that limb.
        particlesTransform.position = limbPosition;
        particlesTransform.rotation = limbRotation;

        // If the particle system isn't playing, play it.
        if(!particleSystem.isPlaying)
            particleSystem.Play();
    }
}

Communication between MonoBehaviours and StateMachineBehaviours

To properly understand the difference between MonoBehaviours and StateMachineBehaviours it is necessary to understand the difference between assets and scene objects.

Scene objects only exist in a single scene. These include GameObjects and their components as well as instances of prefabs. Assets exist in the project and can be referenced in any scene. These include Animator Controllers, prefab assets and StateMachineBehaviours. Since the scene with a specific object might not be loaded, an asset cannot refer to a scene object. Since assets always exist in the project, scene objects can refer to assets.

Remember that since state machine behaviours are Assets, they cannot store references to scene objects. In order to refer to a scene object in a state machine behaviour the reference must be found or passed to the state machine behaviour. For example:

Code snippet

override public void OnStateEnter (Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
    player = GameObject.Find(“Player”);
}

State machine behaviours are not like MonoBehaviours in the way that they are created. Instances of MonoBehaviours are created when they are added to a GameObject and are therefore scene objects. The StateMachineBehaviour class derives from ScriptableObject. As such, state machine behaviours are assets, not scene objects. This means that in order to exist within a scene, instances of state machine behaviours are automatically created at runtime during the Animator’s internal Awake call. This means that finding a reference to them during a MonoBehaviour’s Awake function is not recommended as it will yield unpredictable results.

To get a reference to a StateMachineBehaviour in a MonoBehaviour, you can use either animator.GetBehaviour<>() or animator.GetBehaviours<>(). These functions work in a similar way to GetComponent<>() and GetComponents<>(). GetBehaviour will return the first instance of the specified StateMachineBehaviour found on the animator. GetBehaviours will return an array of all the StateMachineBehaviours of the specified type found. Since it cannot be guaranteed that the state machine behaviours have been instantiated in Awake, these functions should be called in Start.

Here is a short example of finding references between a single StateMachineBehaviour and a MonoBehaviour.

Code snippet

using UnityEngine;

public class ExampleMonoBehaviour : MonoBehaviour
{
    private Animator animator;                          // Reference to the Animator component on this gameobject.
    private ExampleStateMachineBehaviour exampleSmb;    // Reference to a single StateMachineBehaviour.


    void Awake ()
    {
        // Find a reference to the Animator component in Awake since it exists in the scene.
        animator = GetComponent <Animator> ();
    }


    void Start ()
    {
        // Find a reference to the ExampleStateMachineBehaviour in Start since it might not exist yet in Awake.
        exampleSmb = animator.GetBehaviour <ExampleStateMachineBehaviour> ();

        // Set the StateMachineBehaviour's reference to an ExampleMonoBehaviour to this.
        exampleSmb.exampleMb = this;
    }
}

Code snippet

using UnityEngine;

public class ExampleStateMachineBehaviour : StateMachineBehaviour
{
    public ExampleMonoBehaviour exampleMb;
}

Дополнительная документация