In-game State machine to sync with Mecanim?

Okay, so I’ve been bending backwards thinking about this. I want a system that’ll sync it’s state based on some “triggers” from completing animations. I also want to wrap these classes properly so that it would be transparent from the code. I don’t really know how to approach this so here are a few questions:

  • How to detect animation end?
  • Where is that UnityGems advanced state machines tutorial code?
  • Any other resources, tips or guidance?

Well, you know information about what state the animator is currently in (GetCurrentAnimationClipState and GetCurrentAnimatorStateInfo) and, if you’re in transition (IsInTransition) then you can find out information about the state you’re transitioning to (GetNextAnimationClipState and GetNextAnimatorStateInfo).

You can also create arbitrary AnimationEvents to send messages back to your state machine when a certain animation frame occurs, such as when an animation ends.

No idea about Unity Gems. Google-fu tells me that it’s an old site that is no longer active, but there’s an archived article here: Finite State Machine Tutorial #1 | Unity Gems - is that what you meant?

Unitygems is for the time being pending as to reopen or not. Owner of the domain name promissed he would take care of it but seems to be busy with personal matters. (I am one of the admin but was not taking care of the domain).

You can get it from the provided link in the other answer (the videos should still be on Youtube), there is also someone on Facebook claiming to have the tutorial on his HD, so you can ask nicely,

Finally, I have my own version of a StateMachine available:

using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class StateMachine : MonoBehaviour {
    public event Action<object, string, string> OnStateTransition = 
        (object behaviour, string oldState, string newState) => { };

    class State {
        public string name;
        public List<string> transitions;
        public bool allowAnyTransition;
        public Action updateMethod = ()=>{};
        public Action<string> enterMethod = (string oldState) => { };
        public Action<string> exitMethod = (string oldState) => { };

        public State(string name) {
            this.name = name;
            this.allowAnyTransition = false;
            this.transitions = new List<string>();
        }
    }

    private Dictionary<string, State> states;

    private State currentState = null;
    private bool inTransition = false;    
    private bool initialized = false;
    private bool debugTransitions;

    private Action OnUpdate = () => { };

    public string CurrentState {
        get { return currentState.name; }
    }

    protected void Initialize( bool debug ) {
        if (initialized) {
            Debug.LogWarning( GetType().ToString() + " is trying to initialize statefulness multiple times." );
            return;
        }

        this.states = new Dictionary<string, State>();

        this.AddState( "InitialState" );
        State initial = states["InitialState"];
        initial.allowAnyTransition = true;
        currentState = initial;
        inTransition = false;
        transitionSource = null;
        transitionTarget = null;
        initialized = true;
        debugTransitions = debug;
    }

    protected bool IsLegalTransition(string sourceState, string nextState) {
        if (states.ContainsKey(sourceState) && states.ContainsKey(nextState)) {
            if (states[sourceState].allowAnyTransition || states[sourceState].transitions.Contains(nextState)) {
                return true;
            }
        }
        return false;
    }
    
    private void TransitionTo(string nextState) {
        transitionSource = currentState;
        transitionTarget = states[nextState];
        inTransition = true;

        currentState.exitMethod(transitionTarget.name);
        transitionTarget.enterMethod(currentState.name);
        currentState = transitionTarget;
    }

    private void FinalizeCurrentTransition() {
        if (transitionTarget == null || transitionSource == null){
            Debug.LogError(this.GetType().ToString() + " cannot finalize transition; source or target state is null!");
            return;
        }
        OnStateTransition( this, transitionSource.name, transitionTarget.name );

        inTransition = false;
        transitionSource = null;
        transitionTarget = null;
    }
    
    protected bool RequestState(string newstate) {
        if (!initialized) {
            Debug.LogError( this.GetType().ToString()+ " requests transition to state " + newstate + " but statefulness is not initialized!");
            return false;            
        }
        if (inTransition) {
            if (debugTransitions)
                Debug.Log(this.GetType().ToString() + " requests transition to state " + newstate +
                          " when still transitioning to state " + transitionTarget.name);
            return false;
        }   
        if( IsLegalTransition( currentState.name, newstate )) {
            if (debugTransitions) {
                Debug.Log( this.GetType().ToString() + " transition: " + currentState.name + " => " + newstate );
            } 
            TransitionTo(newstate);
            FinalizeCurrentTransition();
        } else {
            Debug.LogError( this.GetType().ToString() + " requests transition: " + currentState.name + " => " + newstate + " but it is not a legal transition!" );
            return false;
        }
        OnUpdate = null;
        OnUpdate = currentState.updateMethod;
        return true;
    }

    protected void DeclareState(string newstate) {
        State s = new State( newstate );
        System.Type ourType = this.GetType(); 

        MethodInfo update = ourType.GetMethod("Update" + newstate, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        MethodInfo enter = ourType.GetMethod("Enter" + newstate, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        MethodInfo exit = ourType.GetMethod("Exit" + newstate, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
        
        if (update != null) {
            s.updateMethod = (Action)Delegate.CreateDelegate(typeof(Action), this, update);
        }
        if (enter != null){
            s.enterMethod = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), this,enter);
        }
        if (exit != null){
            s.exitMethod = (Action<string>)Delegate.CreateDelegate(typeof(Action<string>), this, exit);
        }
        this.states.Add( newstate, s );
    }

    protected void DeclareStateWithTransitions(string newstate, string[] transitions) {
        AddState(newstate);
        State s = states[newstate];

        foreach (string t in transitions) {
            s.transitions.Add( t );
        }
    }

    protected virtual void StateUpdate() {
        OnUpdate();
    }
}

You use it like this:

public class Controller:StateMachine
{
void Awake(){
Initialize(true); // false if you don’t want the debug

    // The first parameter declares the state,
    // the array of string declares the legal transitions from that state
    // Idle can get to Walk and Die, Die can only move to Idle
    // Requesting from Die to Walk would print an error and would be discarded
    DeclareStateWithTransitions("Idle", new string[]{"Walk, Die"});
    DeclareStateWithTransitions("Walk", new string[]{"Idle, Die"});
    DeclareStateWithTransitions("Die", new string[]{"Idle"});
    RequestState("Idle");
}
void EnterIdle(string previous){
    Debug.Log("Entering Idle state");
}
void UpdateIdle(){
    Debug.Log("Idle update");
    if(specialCondition)
        RequestState("Walk");
}
void ExitIdle(string previous){
    Debug.Log("Exit idle");
}
// Same for other states, only add method if you need it

void Update(){
    // StateUpdate is needed to run the update of each state
    StateUpdate();
    // Rest of the Update code
}

}

This state machine does not allow to change state while in a transition, that is if you are changing state from Idle to Walk and in the same frame, you are about to die, it won’t do, I am thinking of fixing since when I have time, maybe someone wants to work it out and improve it for some wiki state machine.

Shoot if you have any questions.

That’s a really general question. Typically you’d set a variable to control the animation so for example I have a controller script that checks if the player is grounded if not and if we’re past the end time for a normal jump it updates a bool in mecanim isFalling. I also check is we are in water and if we are is the player so deep swimming.

Now imagine the player jumps off a rock I pass isJumping to the Animator which calls the jump animation. In the script I record the start time with a float jumpStart that I set to Time.time if it goes past the time that the jump should end (jumpLength)

if(isJumping && Time.time > jumpStart + jumpLength)
{
    anim.SetBool("isJumping", false);
    anim.SetBool("isFalling", true);

So the falling animation loops, now we hit the water and go below the level where swimming is possible. So now we set the isFalling to false and isSwimming to true (I set the isSwimming = true in the swimming controller (SBUnderwater.cs)

if(SBUnderwater.isSwimming == true)
		{
			if(anim.GetBool("isFalling"))
			   anim.SetBool ("isFalling", false);

Really I think you’re thinking of this the wrong way round the script should control the Animator but it is possible to get the bools and other Parameters from the Animatior.

UnityGems? No idea, appears to be a bad link.

Other resources YouTube search for:

unity mecanim finite state machine