Newbie State Machine Behaviours with Events and Delegates Questions

Hi,

The event is sending and I am seeing the debug lines perfectly but I have 1 yellow error and 1 red that I am having trouble understanding. Basically I am trying to use the state machine behavior to listen & send an event when it starts to play an shoot animation and when it ends.

This is my state machine behavior script:
credit goes to: Reddit - Dive into anything

using UnityEngine;
using System.Collections;

public class StateMachineEvent: StateMachineBehaviour
{
	public delegate void StateEventHandler(Animator animator, AnimatorStateInfo stateInfo, int layerIndex);

	public event StateEventHandler OnStateEntered;
	public event StateEventHandler OnStateExited;

	public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
	{
		base.OnStateEnter(animator, stateInfo, layerIndex);

		if (OnStateEntered != null)
		{
			OnStateEntered (animator, stateInfo, layerIndex);
		}
	}

	public virtual void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
	{
		base.OnStateExit(animator, stateInfo, layerIndex);

		if (OnStateExited != null)
		{
			OnStateExited (animator, stateInfo, layerIndex);
		}
	}
}

This is my the script I have on my gameobject:

using UnityEngine;
using System.Collections;

public class AIController: MonoBehaviour
{
	private Animator animator;

	void Awake()
	{
		animator = GetComponent<Animator>();
	}

	void OnEnable()
	{
		animator.GetBehaviour<StateMachineEvent>().OnStateEntered += RifleOneShotStart;
		animator.GetBehaviour<StateMachineEvent>().OnStateExited += RifleOneShotEnd;
	}

	void RifleOneShotStart(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
	{
		Debug.Log("Rifle one shot - animation START");
	}

	void RifleOneShotEnd(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
	{
		Debug.Log("Rifle one shot - animation END");
	}

	void OnDisable()
	{
		animator.GetBehaviour<StateMachineEvent>().OnStateEntered -= RifleOneShotStart;
		animator.GetBehaviour<StateMachineEvent>().OnStateExited -= RifleOneShotEnd;
	}
}

This is the yellow error that shows up when I am in the editor:

Assets/Misc/StateMachineEvent.cs(21,29): warning CS0114: `StateMachineEvent.OnStateExit(UnityEngine.Animator, UnityEngine.AnimatorStateInfo, int)' hides inherited member `UnityEngine.StateMachineBehaviour.OnStateExit(UnityEngine.Animator, UnityEngine.AnimatorStateInfo, int)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword

This is the red error that happens only when I hit stop. Not during play mode ever, only shows up after I press stop:

NullReferenceException: Object reference not set to an instance of an object
AIController.OnDisable () (at Assets/Misc/AIController.cs:31)

Can anyone explain these errors in layman’s terms?

Any help is appreciated, thank you.

The warning means that your essentially redefining what “OnStateExit” is (from a method to event) since that name is already being used by the class. Your essentially overriding function with your own event. Unity isn’t sure if this is what you’ve intended because now Unity can’t properly access the old method so its issuing a warning (not an error) to let you know. While this will compile (it is a warning and valid code) I’m certain its not what you want as now the OnStateEnter and OnStateExit functions are getting overshadowed and won’t get called.

also don’t have your AI Controller keep track of the StateMachineBehavior. from my experience the state of these Statebehavior scripts can desync from the MonoBehavior scripts leaving your Mono script none the wiser. The reason why this doesn’t work is that GetBehaviour() returned a reference in OnEnable that won’t always be the “same” reference when you called it again in OnDisable().

I’m not entirely certain, but my guess is that the various sub assets that make up the animator are getting created, destroyed, and changed throughout its lifetime. So, events might not be the best way to pass info to and from the AI controller.

Instead have your StateMachineBehavior script to keep track of the AI script:

public class AIController: MonoBehaviour
{
    void OnEnable()
    {
        MyStateMachine[] msm = animator.GetBehaviours<MyStateMachine>();

        for (int i=0; i<msm.Length; i++)
        {
            msm *.Script = this;*

}
}
public void RifleShotStart(){}
public void RifleShotEnd(){}
}
then in your StateMachince Script…

public class MyStateMachine: StateMachineBehaviour
{
public virtual MonoBehavior Script { get; set; }

public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
base.OnStateEnter(animator, stateInfo, layerIndex);
if (Script != null)
Script.RifleShotStart();
}

public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
if (Script != null)
Script.RifleShotEnd();
base.OnStateExit(animator, stateInfo, layerIndex);
}

}
This is the technique that I use and it has proven to be pretty solid.
Alternatively if you really don’t like exposing those methods you can keep the events (but please rename them so that they aren’t masking the functions!) and then have the StateMachine script remove all listeners onDisable(). Since StateMachineBehavior inherits from ScriptableObject, it also comes with OnEnable,OnDisable and OnDestroy. However, I’m a little wary of this since I’m not fully aware as to when a StateMachineBehavior will have its OnDisable called so you could end up with your events suddenly breaking.