Pine is being created using Unity3D. This blog post will describe one of the systems we have created for use in Unity.
For Pine, we are treating each character, (the player and the species he may encounter) as Organisms. An Organism contains multiple components that give them their appearance and how they behave. For this blog post, we are going to talk about our state machine system, which is located inside the brain component of each Organism.
Our state machines govern how an Organism behaves and reacts to the world. Here is a quick overview to how a state machine works:
Each state has the functions
OnExit() functions are called when the state machine transitions to and from this state, respectively. While a state is entered, it calls the
Process() function each frame.
As an example, let's say that in the above image, the Organism is in the state labelled Idle, and wants to transition to Attack. Due to how the nature of state machines work, in order to successfully transition to the new state, it needs to move up the hierarchy, calling the
OnExit() function on each state as it moves up, before then calling
OnEnter() on each state as it moves down. The flow looks something like this,
Idle --EXIT-> Passive --EXIT-> Root --ENTER-> Combat --ENTER-> Attack.
Each state represents a certain behaviour of the Organism, so for example inside the Attack state, we:
- play the attack animation
- play sounds
- turn on the hitboxes of the Organism's weapons
- listen for input so we can transition to the next attack
Because almost every state needs some sort of visual representation, the animations of the Organism are quite closely tied to the state it is currently in. In Unity, character animations can be defined using an Animator Controller component. The Animator Controller itself is a state machine for animations only, and we often found ourselves with an Organism state machine that was out of sync with the Animator Controller's state machine.
A small, simple Animator Controller.
This could sometimes lead to situations where the Organism's state machine moved to a different state, while the Animator Controller's state machine was not yet ready - causing the animations to get stuck as the Organism's state machine would not be triggering the correct animator transitions. The fact that these two state machines were so similar and needed to rely on each other, lead us to try using our state machine to control the flow of the Animator Controller. This would mean getting rid of all the transition lines inside the Animator Controller, and letting the Organism's state machine handle all the transition logic as well.
Luckily, Unity provides editor functionality to generate an Animator Controller through code, as well as manually transitioning between animation states. This would then let us automatically generate an Animator Controller based on the layout of our Organism's state machine, as well as manually force animation transitions when a state is changed. This completely eliminated any chance of the two state machines going out of sync, as the Animator Controller was now completely dependent on the Organism's state machine. This also had the added benefit of reducing our iteration time whenever we added new behaviours, as we did not need to fiddle around with the Animator Controller and manually set up all possible transitions.
An example of us creating a new state in code, and updating the Animator Controller from that code.
In Pine, as a player you are allowed to attack from many states, for example from multiple Idle, Movement, and Attack states. Without our system of generating the Animator Controller and transitioning in code, we would need to manually set up each possible transition from every state to every other state. This would make it very possible for us to forget to set up a transition in the Animator Controller, causing the two state machines to once again fall out of sync when that transition is supposed to occur.
An example of how the Animator Controller would look like if you would only have a few states, but still want to transition between most of them.
As a bonus to us treating the player the same as any other Organism, we can easily swap out the processing logic inside each state to specific AI/player-controlled versions. For example in a player-controlled version of a state we would poll for input to decide where to transition next, while in an AI-controlled version of the same state we could then let the AI decide where to transition next. This allows us to swap between player-controlled and AI-controlled Organisms at runtime.
Thanks for reading our high-level overview about our state machines for Pine, if you have any comments/questions please stop by our subreddit at /r/pinegame!