StateMachineBehaviour - 2 versions of messages and AnimatorControllerPlayable

So I’m only now really starting to play around with StateMachineBehaviour’s

And in doing so I’m noticing that for every message (OnStateEnter, OnStateUpdate, OnStateExit) there are 2 overloads of it:

OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)

And

OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)

Now the part I’m not understanding here is that one or the other gets called, not both.

So I have my own project, and I have another project with the 3DGameKit project imported into it. Both using version 2020.3.8f1.

In my project the overload of the method that does NOT contain a AnimatorControllerPlayable arg is the one that gets called.

But in 3DGameKit the overload that DOES contain a AnimatorControllerPlayable arg is the one that gets called.

Furthermore I notice the documentation for StateMachineBehaviour only lists the one without the AnimatorCOntrollerPlayable arg:

(as of today, future readers may see something different)

Honestly the documentation for this is pretty darn bare.

So my question is.

What causes one or the other to get called?

Does anyone know of a location of documentation that covers the specifics of the 2 different overloads of the same messages?

I think the one with the AnimatorControllerPlayable parameter will be called if the animator is used by a Timeline.

I’m not sure about that since it doesn’t appear these ‘Chompers’ in the 3DGameKit are using any timeline thing. They’re just an Animator with animations from their fbx files attached.

So… I figured out what it is.

Unity expects you to only override 1 of the 2 options. It will call the one you’ve overloaded.

If you override both, it’ll call the one that does not have the AnimatorControllerPlayable.

This is why in the 3DGameKit they have this class:

    //This class repalce normal StateMachineBehaviour. It add the possibility of having direct reference to the object
    //the state is running on, avoiding the cost of retrienving it through a GetComponent every time.
    //c.f. Documentation for more in depth explainations.
    public abstract class SealedSMB : StateMachineBehaviour
    {
        public sealed override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }

        public sealed override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }

        public sealed override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { }
    }

I find it funny they put this comment on it which actually doesn’t describe this class but rather the ‘SceneLinkedSMB’ which inherits from this class. And they do NOT offer up any explanation as to why this class exists.

The first time I looked at this class I was just like “why not seal these methods in SceneLinkedSMB? Why have 2 classes like this???”

Well it appears to force the smb to have the overload that does contain the AnimatorControllerPlayable to be called no matter what.

These are the types of things Unity does that drive me mad.

Document… come on guys… document.

Like… this isn’t normal C# behaviour. Clearly you’re doing your own thing. So like… document that behaviour!?

Sorry… correction, they don’t necessarily call the one without the AnimatorControllerPlayable argument.

I figured out what it is.

Even though they have virtual methods for all these messages.

I think they only exist for our benefit of having an easy way to type in the signature. Unlike MonoBehaviour where you just have to know the name/args of the messages it receives. Since these are more complicated the overrides exist so I don’t have to remember them (note - I know VS has a plugin to assist here, but not everyone has those tools available to them).

Thing is… I think they still call them following the same rules as MonoBehaviours.

That is they search up the class hierarchy starting from the bottom and call the first one they find.

So it’s your version of “OnStateEnter” that you override in the lowest down class, and shows up first in that class.

So like:

    public class a_OnEnterState : StateMachineBehaviour
    {

        [SerializeField]
        private SPAnimatorStateMachineEvent _onEnter;

        //this one is called
        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)
        {
            if (_onEnter.HasReceivers) _onEnter.ActivateTrigger(animator, null);
        }

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            if (_onEnter.HasReceivers) _onEnter.ActivateTrigger(animator, null);
        }

    }

In this one the one with the AnimatorControllerPlayable gets called.

    public class a_OnEnterState : StateMachineBehaviour
    {

        [SerializeField]
        private SPAnimatorStateMachineEvent _onEnter;

        //this one is called
        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            if (_onEnter.HasReceivers) _onEnter.ActivateTrigger(animator, null);
        }

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)
        {
            if (_onEnter.HasReceivers) _onEnter.ActivateTrigger(animator, null);
        }

    }

        {
            if (_onEnter.HasReceivers) _onEnter.ActivateTrigger(animator, null);
        }

    }

And this one the one without the AnimatorContollerPlayable gets called.

And if I did this:

    public class BaseSMB : StateMachineBehaviour
    {
        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
        {
            //does nothing
        }
    }

    public class a_OnEnterState : StateMachineBehaviour
    {

        [SerializeField]
        private SPAnimatorStateMachineEvent _onEnter;

        //this one is called
        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex, AnimatorControllerPlayable controller)
        {
            if (_onEnter.HasReceivers) _onEnter.ActivateTrigger(animator, null);
        }

    }

The one in a_OnEnterState is called because it’s the lowest in the class hierarchy.

This is all fine and dandy. They’re following some sort of rules that I’m familiar with. It’s the same rules MonoBehaviour messages do.

BUT, they’re slightly off from MonoBehaviour messages because they have the virtual methods (which MB’s don’t). Which is why I didn’t originally think to make this assumption.

But the thing is… this behaviour of MonoBehaviour messages isn’t exactly well documented either. It’s just knowledge I’ve gathered over the years of using Unity, and I suspect others who know it also just figured it out over time. And I know many don’t know it, cause it’s a quark about MB’s that I’ve had to explain here on the forums.

I just wish this sort of stuff was like… written down… somewhere easily noticeable.

Like right here in the StateMachineBehaviour api documentation:

It’s like the first place I would have thought to looked. But no, it doesn’t even show that there are 2 overloads! Let alone explain why there are, or which gets called when?

Come on Unity… I like your engine. I like it a lot. I’ve been using for a long time now! But I have gripes about it… as I do any software I use. And I suspect people have gripes with my software.

But the one thing I think y’all really need to get on top of… documentation.

It’s bizarre how large your documentation is… yet it says so little about the “magical behaviour” of your engine. Magical behaviour being things like this where it’s not obvious to know what’s actually happening since the actual rules of it exist behind closed source on the C++/internal side of the engine and never really been disclosed.

Black box behaviour.

There you go, better term for it.

Becuase this isn’t stuff that some newb developer messes up. Someone not understanding ref types or value types or what a class is. Well MSDN and swaths of books, tutorials, articles, and people in general have that covered. It’s well documented and well known stuff. Those users are just… new… and haven’t learned it.

This on the other hand is behaviour that took a developer with decades of experience to poke/prod/pry at, and reflect against existing knowledge of the quarky ways Unity does things, and suss it out.

It’s not something a beginner is going to figure out easily.

they’re not making these kinds of changes for beginners. beginners are just fine getting lost in the mundane stuff already. if you want them to get lost a little bit more, just throw some coroutines at them and watch 'em swell on that Dunning-Kruger uphill part. I mean who else is gonna pay for every little thing on the Asset Store only to never use it?

the farther I got into Unity, the more it was apparent that the point of developing an ecosystem isn’t exactly to devaluate market price of those who actually invested themselves for decades. at some point you, as someone who is knowledgeable on many quirks of Unity, and agile to act upon them, are the “scarce commodity,” and the valuable asset in the marketplace. and you’re not easy to just move on either, so there is a mutual investment.

this is the natural microbubbly™ economy of the modern framework ecosystems. the thing is, it cannot burst because there aren’t large investments or large promises, we’re all just individuals investing our personal time in discovering new tools of trade and new market opportunities. there is nothing to pull out with once you get disappointed. thus no bubble to burst.

obviously they are extremely careful to never disappoint a large demography, and so as long as there are new things on the horizon, and we can’t possibly catch up with all of it, the value can only go up. this also explains why it is always more important to shove in new features, then to remedy the broken ones from the past. time will inevitably wash all kinds of things away.

yes I’m a programmer, but even I had to stop fooling myself into believing that the world strives to be a perfect place. or that it should but doesn’t. no the perfect world is the one that strikes a perfect balance between what is beautiful and what is utterly nonsensical. Unity documentation included. sure, it’s a fine line, one we could talk about for the rest of our lives, so it must be worth living for just as well.

tl;dr without a looming threat of Mordor, Middle Earth is a worthless piece of #$##.

btw I’m glad you discovered all that. good story.