Greetings,
I’m finding myself very frustrated with mecanim in 2018.4. I have an “event source” behaviour that listens for OnStateTYPE events, and then converts them to C# events to be picked up by an “event receiver” monobehaviour. This is my workaround to allow scene-object binding into the FSM.
(code includes attributes from Sirenix.OdinInspector)
public delegate void StateMachineStateEvent(Animator animator, AnimatorStateInfo stateInfo, int layerIndex);
[SharedBetweenAnimators]
public class StateMachineStateEventSource : StateMachineBehaviour
{
public static event StateMachineStateEvent Enter;
public static event StateMachineStateEvent Update;
public static event StateMachineStateEvent Exit;
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
base.OnStateEnter(animator, stateInfo, layerIndex);
Enter?.Invoke(animator, stateInfo, layerIndex);
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
base.OnStateUpdate(animator, stateInfo, layerIndex);
Update?.Invoke(animator, stateInfo, layerIndex);
}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
base.OnStateExit(animator, stateInfo, layerIndex);
Exit?.Invoke(animator, stateInfo, layerIndex);
}
}
public class StateMachineEventReceiver : MonoBehaviour
{
[System.Serializable]
public class MapItem {
[HideLabel, Tooltip("State/Machine Name or Tag")]
public string StateId;
[FoldoutGroup("Enter")]
public UnityEvent OnStateEnter;
[FoldoutGroup("Update")]
public UnityEvent OnStateUpdate;
[FoldoutGroup("Exit")]
public UnityEvent OnStateExit;
}
public Animator Filter;
public List<MapItem> Map;
private void OnEnable() {
StateMachineStateEventSource.Enter += StateMachineEventSource_OnStateEnter;
StateMachineStateEventSource.Update += StateMachineEventSource_OnStateUpdate;
StateMachineStateEventSource.Exit += StateMachineEventSource_OnStateExit;
}
private void OnDisable() {
StateMachineStateEventSource.Enter -= StateMachineEventSource_OnStateEnter;
StateMachineStateEventSource.Update -= StateMachineEventSource_OnStateUpdate;
StateMachineStateEventSource.Exit -= StateMachineEventSource_OnStateExit;
}
private void StateMachineEventSource_OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
if (Filter != null && animator != Filter) return;
foreach (var item in Map) {
if (stateInfo.IsName(item.StateId) || stateInfo.IsTag(item.StateId) || stateInfo.shortNameHash == Animator.StringToHash(item.StateId)) {
item.OnStateEnter.Invoke();
break;
}
}
}
// ....
}
To my delight, I discovered that I could add a behaviour to the top-level StateMachine and it would be automatically added to all States under that machine.
However, I have recently started to use SubstateMachines. Only to find that:
- The behaviour scripts from the top-level SM are cloned/attached to states inside a SubstateMachine
- The behaviour scripts from the top-level SM are not cloned/attached to the SubstateMachine nodes
- Any behaviours added to the SubstateMachine nodes are cloned/attached to internal states
Now, all I want to do is raise an event for entering the SubstateMachine (I’m using the Enter/Exit nodes), but I obviously do not want to raise duplicated enter/exit events for internal states.
Please advise on a means of making this happen, or please consider this a feature/issue request.