Evan's Dev Blog 3/14 - BetterEvents and an Introduction to Odin
Hello, everyone! In my last post, I figured I would talk a bit about Odin Inspector this week. While Odin Inspector has been an extremely helpful tool for further development of "Random Encounter," it is Odin's Serialization that has helped clean up one of the most gunky aspects of the project's codebase; the Effect system. However, this cleanup created a host of new challenges for me to overcome.
Originally, all of an Effect's behaviors were handled by Unity Event calls. This worked when the project was smaller, but as the game grew, two drawbacks to this system became very apparent: the number of functions in the Effect class became ridiculous, with many functions performing almost the exact same tasks, and only single parameters could be passed through.
In this example, the function ApplyEffectToUser exists to, as the name implies, apply the effect to the user of the spell that called the effect. However, say we wanted to apply the effect to the target of the spell instead. For this to work, we needed to create a separate function that performs the exact same task, just targeting a different entity. The other function, ModifyUserMPFromDamageDealt features the targeting issue, but also shows the weakness of Unity Events, with the function ideally needing three values to control how much MP is restored. To cheat this, we need to parse a string, which is inefficient.
I decided to add Odin Inspector to the project to create tools to speed up the spell creation process, and while these tools are in and work phenomenally, Odin Inspector's Community Submissions held the solution to all the problems I had been having with Unity Events: enter the BetterEvent.
BetterEvents are not only more performant than standard UnityEvents, they also support multiple parameters, and allow function lookup by name! With multi-param support present, I refactored the Effect script so that only a single function is used for each action, meaning there is now only one ApplyEffect function, with a parameter indicating if this should affect the user or the target. This cleanup extended to stat modifiers, animation application, and more. With the better event, the following issues were completely eradicated:
Difficulty finding a specific function due to long list of functions.
String parsing created unnecessary drops in performance
However, one critical flaw presented itself when I rebuilt "Random Encounter" for Android: Effects no longer functioned as intended! Everything behaved properly in the editor, but it turns out that BetterEvents, due to their use of Odin's serialization system, would have their event callbacks completely wiped during the compilation process. This problem is due to certain Odin functions being pulled out during the build process for AOT platforms, including Android x64. While Odin has a pre-made fix for this issue, it did not resolve the problem with BetterEvents.
The issue was resolved when I dug deeper into how the Effect class was being serialized. A BetterEvent, at its core, is simply a list of Delegates, called BetterEventEntries. The BetterEvent class features an invoke button and a custom add function, neither of which I wanted for Effects. Instead, I had set up Effects to have a list of BetterEventEntries with my own custom add function. This solution worked well, but caused issues with serialization. While all callbacks in a BetterEvent are serialized through Odin, lists are serialized through Unity. As a result, I had to change the properties of each list so that they were serialized through Odin instead of Unity, requiring a second rebuild of each Effect. However, this paid off, and now BetterEvents work correctly on platform!
Odin Inspector is an extremely valuable tool for any Unity Developer. Next post, I will talk more about it and how it helped with Editor tools. Until next time!