Animator For State Machines and (Probably Ignorantly) Breaking Encapsulation
Before I go too much into detail about things, I have to warn you that what you're about to read may be incredibly stupid. I am in no way trying to teach anything by sharing this information (Except for maybe the State Machine, as I would consider it a work of art, personally), I'm simply amused at myself and would like to share a bit about it!
So, I was on the market for a State Machine. I didn't want to make my own, although I knew it's not difficult. But, I wanted an editor (whether I had to buy it or not) that would make the creation of FSM's throughout my project much easier. I'm a State Machine ADDICT when it comes to programming games. Is the game paused? The gameplay is in the paused state. Are you in a different play style? That's a FSM state. Is the player in a certain state? That's an FSM state, Vehicle? etc.
As you can probably guess, it's incredibly important that I have an easy and clean method of handling Finite State Machines. So, I went to Google first. And this article really peaked my interest https://medium.com/the-unity-developers-handbook/dont-re-invent-finite-state-mac...
If you haven't read the article, the writer essentially formulated the idea of using the Animator as a FSM. Which, seems like an incredible idea, in theory. But, there are some MAJOR drawbacks at first glance. The first being that, you can't actually get the name of the current state in the Animator. Trust me, I've tried.
So, obviously you can't get the name of the state through the Animator's type, but can you get the name through a State Behavior Script? Also, no.
This functionality is incredibly important to me at the moment. I've spent so much time working on my project, I don't really want to migrate it all to the State Behavior just yet, and even if I did, I don't really like the idea of keeping all of the scripts for each State controlled Object in the state itself, it just didn't seem very clean to me. That's thousands of lines of code for various different aspects of the object. I'm one of those guys that likes to separate code into different files based on their use... I mean, that is one the biggest reasons for OOP, right?
So, the issue of getting the State's name isn't actually that hard to fix, surprisingly. Inside the State Behavior script, we have a Callback for the Animator that this State belongs to. Which means, we also have access to other scripts added to the object.
So, I created a State Machine script that extended MonoBehavior, and created and a Public List for the names of the States. Then, I created a State class and simply added this:
Whenever the object enters this state, we get the State Machine script. As you can see, it's really easy to get the Name of each State by simply adding this Script. But, that leaves us with another problem. So, we can get the name of the current state? Okay, but the Animator doesn't allow the use of string conditions as transitions.
The problem is easily solved with the aforementioned States List in the State Machine. We simply loop through all the names in the list, and get the index that name is at in the list. Likewise, if we want to go to a state (using a "GoToState" function) we can add transitions from Any State in the Animator for each index:
This is done, easily, by Setting a State Integer as a Parameter inside of the Animator and then using a GoTo trigger to brute force going to the state from any other state.
Take a look, here, at what the SetStateIndex actually does:
To get the Index in the State Machine, we simply get the integer.
Now, we can look inside the Editor inspector and see that since the variables have been made public, we can easily track the status of the state at runtime:
In the bottom right you can see all the information about the Player's State Machine! Awesome!
So, it's entirely possible that you may be reading this and thinking I'm a moron for wanting so badly to brute force the State of the Machine. It is bad practice, after all, to not use the transitions of the FSM. That's the whole point. But, the reason why I do this is actually because of the incredibly important Disable state. It is the only State that has no code. As in, it isn't active in the scene. I want all Gameplay objects to switch to this state when the game is paused, and then switch back to the Previous State (Prev State) whenever the game is unpaused. (OnStateEnter, OnStateExit of the Paused State in the Engine's State Machine).
I'm sure you can see now how well these State Machines can be stringed together to simplify hierarchies of states. I may elaborate more on it in the future, but I'm very happy about where I am with it right now.
There's one last thing, though! We have to go all the way to the beginning, with the State Behavior that I called State. It's important to note that for each State, you should actually Extend a new State Behavior from the original State script (abstracting away the State name information in OnStateEnter), like this:
Oh, did you catch that? What is that method... "DeclareOn"? And why?
Well, I'm glad you asked. Oh, you didn't? Well, I'll tell you anyway.
EDIT: I made some little changes after initially writing this, firstly OnStateEnter must not override the original in State, or it won't be called. This can be fixed by calling the function once immediately after OnStateEnter.
Breaking Encapsulation (Sorry Ma)
I'm about to tell you how I cheated the system of OOP as we know it! And after reading this, I'm eager to hear how you think that it's probably the worst idea since consumable Tide Pods!
DeclareOn is an Extension Method of a string that calls the Static Event Function "Message". On the left is the name of a Function, on the right is the given ID of the object to send the Message to.
And then, for each object in Unity that receives messages, a Script is added that extends the Messenger Script. The Messenger Script receives the "Message" event, checks if the object it's attached to has the ID given to the Message function, and if it does, Invoke the function in from the Message parameter.
So, whenever we extend Messenger (IE Player Messenger), all we have to include in the code of that script is the name of the function to invoke.
Badda Bing, Badda Boom!
Alright, so it might seem kinda silly, but what this did is allow me to call object related functions within the object itself, rather than externally. Also, we don't have to deal with the complexities of Finding an object based off it's tag and name and all that, and then calling the function. Instead, it's all just wrapped into one line:
Gotta love that simplicity. That said, you might be wondering, what if we want parameters or the ID of the calling object so they can communicate? And, to that, I have one easy answer: Optional parameters. I do intend on adding both of these things as optional parameters. So, when we call the Invoke method, we also store the additional Parameters in variables, and when the Function is called, obtain that information and store it in a local variable inside of the function.
If that doesn't work, then another option could be creating a list in the Messenger of called functions and their parameters and use that to keep track of said information. I'll have more to share on that when the time comes!
Thanks for reading this article! If you're an expert programmer, you may be able to see that I'm pretty new to coding, especially in C# with Unity. It's entirely possible that I'm not doing things in the best way possible, but I'm still ecstatic to share my experience with the world, because I find this stuff so fun!
Until next time, smell ya later!
Leave a comment
Log in with itch.io to leave a comment.