In Dead Body Falls project, all engineering architecture related to game events was implemented on state machines using the PlayMaker plugin. The state machines provide great debugging power and their nature makes it much easier to execute sequential actions, which are very present in this project. For this reason, to facilitate the control of these actions, one of the components created was the AnimatorController, which will be explained the operation, integration, as well as its functionalities and limitations found during the project’s development.
AnimatorController was a class created to meet a need for the Dead Body Falls project: start an animation (with a trigger or an Animator bool) and know when it was finished. It is known that there is already a native Unity tool with a similar function called AnimationEvent. This tool allows you to insert a function’s call into any frame of any Animation attending in Animator states, with the condition that this method is part of some component attached in the same GameObject in which the Animator is attached as well.
Although having this Unity’s tool in hand, the custom component has been chosen by the development team because this one was better adapted to the project’s architecture. AnimationEvent was not used in the project because it requires a callback function implementation attached to each animation individually, and in addition, we would need an AnimationEvent for each Animation frame that would be used for the callback, which would not always be used , because there were many animations that need no return in the end. Debugging the game would also be harder to do since editions should be made in the Animation window instead of the Playmaker window.
With the component, the goal was simple: achieve greater flexibility and integration with PlayMaker. What we needed to know was simply whether an animation triggered by a trigger or a bool in Animator had ended toz start another game flow event. To make this possible, an FsmStateAction (class that allows creation of custom PlayMaker actions) was created with the name FSMSetAnimatorTrigger. It does this integration between the AnimatorController component and the state machines and it has the following layout:
Every custom action of PlayMaker must necessarily call a method to inform the state machine that it has finished its action. This method is Finish. The FSMSetAnimatorTrigger can behave like a simple Animator SetTrigger, without the use of a return callback (Asynchronous field set to true), or with the callback (Asynchronous field set to false). The flowchart below illustrates well the operation of this action.
Its working is best taught in the GIF's below. In the first, Action works like a normal SetTrigger, without waiting for the animation to finish, so the transition to the state is instantaneous. In the second GIF, the state change happens only after the animation ends.
During the course of the project, it was noticed the need to perform certain events in a specific frame of the animation, be it in the middle of it, just before finishing, etc. To work around this problem using the component, a Slider (PercentageToFinishAction field) has been added which indicates in which percentage value of the animation if you want to end the action. So, if there was to be some flow event in the middle of the animation, you only had to put 0.5 in this field, which would cause the action to end in half for the event to occur at the correct time.
The action had a field corresponding to the name of the layer that should be listened to, if it was not filled, the default serialized value in the component would be used. It was recommended to put the name of the most used layer to facilitate the implementations of the actions.
- It does not depend on events directly linked to Animator states (such as OnEnter or OnExit).
- Can also be used for boolean parameters.
- You can specify the percentage at which the callback will be called. So if it is necessary that the callback be called not when the animation is finished, but in the middle of the animation, this is possible.
- Works synchronously and asynchronously
To calculate the animation time, the component uses a layer as a parameter, since a trigger can be triggered on more than one layer. After the trigger has been triggered, the component only waits for animation of the next Animator state. So if there is a trigger that, after firing, starts an animation subdivided in more than one state, with or without the use of the Animator substate machine, the callback is triggered at the end of the animation of the first state of the trigger transition.
Destroying or deactivating the GameObject that contains the component may impair its correct operation, since it starts Coroutines to calculate the animation time, the callback call may never be triggered as a result.
Possible Future Improvements
In the project, the AnimatorController component contained only the methods that calculate the animation time. A good optimization would be to exclude the use of the component and pass all responsibility for the custom FSMSetAnimatorTrigger action, and if you need to use it out of action, the use of extensions from C # to Animator may well go down. In either case, this optimization would avoid the problem of disabling the GameObject containing the Animator. Another refinement that could be applied would be to make it work for more than one state, ie instead of listening to a state to calculate the animation time, maybe implement for N states, chosen via editor.
It could be said that the component was of great use for the project. For future projects, it would be interesting to do profile tests in relation to Unity AnimationEvent. Would the component’s simplicity and dynamicity compensate the performance of the game in relation to the AnimationEvent use? Would it be possible to optimize the component in such a way that it is even better than the native Unity tool? These are interesting study and analysis questions for the constant improvement of game development tools in general.