Some may recall the blog post about the mecanim updates in 5.0 with state machine behaviours and how it’s usefull to create state machines everywhere. For example a menu:
I gave this a try and got to a point where it makes more effort to use a state machine than the benefits it may give. So I’m in need of good advice. Here is a short step by step guide with the following menu as example:
The first thing to do intuitively would be to create a new State Machine Behaviour (SMB) like this:
using UnityEngine;
using System.Collections;
public class SMB_Options : StateMachineBehaviour
{
public RectTransform optionsPanel;
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
optionsPanel.SetActive(true);
}
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
optionsPanel.SetActive(false);
}
}
Does not work unfortunately because you can’t assign scene objects to a SMB, only assets:
No problem! I use singletons and have all menu objects already assigned to it. Static references all the way:
using UnityEngine;
using System.Collections;
public class SMB_Options : StateMachineBehaviour
{
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
MenuSingleton.optionsPanel.SetActive(true);
}
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
MenuSingleton.optionsPanel.SetActive(false);
}
}
But wait, I now created a SMB for only ONE menu state (Options). Do I really have to create one SMB per state? This gonna be suboptimal with about 50 states. What if I define static methods in one class that can be dragged and dropped to the SMB similar to the UnityEngine.Button.OnClick() ? The SMBs are serialized in the animator anyway, so they can hold the information.
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
public class SMB_Extended : StateMachineBehaviour
{
[Serializable]
public class SmbEvent : UnityEvent { }
[SerializeField]
private SmbEvent m_OnEnter = new SmbEvent();
[SerializeField]
private SmbEvent m_OnUpdate = new SmbEvent();
[SerializeField]
private SmbEvent m_OnExit = new SmbEvent();
protected SMB_Extended()
{ }
public SmbEvent onEnter
{
get { return m_OnEnter; }
set { m_OnEnter = value; }
}
public SmbEvent onUpdate
{
get { return m_OnUpdate; }
set { m_OnUpdate = value; }
}
public SmbEvent onExit
{
get { return m_OnExit; }
set { m_OnExit = value; }
}
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
m_OnEnter.Invoke();
}
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
m_OnUpdate.Invoke();
}
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
m_OnExit.Invoke();
}
}
Glory! Let’s assign a script and test it:
public static class MenuFunctions
{
public static void SetMainMenu (bool active)
{
MenuSingleton.mainMenuPanel.SetActive(active);
}
public static void SetOptions (bool active)
{
MenuSingleton.optionsPanel.SetActive(active);
}
}
Not what i wanted. It seems any script is a MonoScript asset and does not care about its content. Weird. But I remembered an exeception: Prefabs! So I wrap the no more static class in a MonoBehaviour and put it on an empty prefab:
using UnityEngine;
public class MenuFunctions : MonoBehaviour
{
public void SetMainMenu (bool active)
{
MenuSingleton.mainMenuPanel.SetActive(active);
}
public void SetOptions (bool active)
{
MenuSingleton.optionsPanel.SetActive(active);
}
}
Although it can be assigned and all the defined functions show up, it is missing an actual instance of the prefab. But this is an asset, there will be no instance!
So has anybody an idea how to make actual use of the state machine in the menu without creating a SMB for each seperate state? Or is the current behaviour bugged and I should file a report?