Zack Bloundele - Games Developer
Scriptable Object Variable and Event System
Unity Asset Project | Unity Asset Store | Roles: Programming
Make your code more modular with this Scriptable Event Management System. By using Scriptable Object Events you can quickly create, subscribe objects and test events, without writing a line of code! You can also easily manage your events through the Event Manager window; which helps visualize how your events are being used within the current scene. It also lets you trigger any of the events from the window - making debugging & testing the events nice and easy.
This project was inspired by the Unite 2017 talk by Ryan Hipple, that talks about how Scriptable Objects can be used to build more extensible systems and data patterns. Using them as variables, that multiple objects can access – like player health, thus reducing the reliance/need for manager objects. This makes testing/debugging a much easier process and doesn’t require rebuilding the entire scene to test one small part of it. You can also use Scriptable Objects to build an Event System, one that requires minimal coding to use, and is much more visual/designer friendly to use. Upon implementing the Variable and Event System, I found that there were several features I could add to both, that would add more functionality for designers to use. This included adding an Event Manager Window and making the Variables have specific functionality based on what type it was. I will explain more specifically what I did below.
Event Manager Window
So, all the Event Logic was already implemented, however I felt that there should be an easy way to view what events are being used within the current scene. One issue with Event Systems is it can be hard to visualise how everything is connected – especially when it’s all in code. Thus, I decided to try and create a window that would alleviate this issue.
The first step was getting all the GameObjects in the scene that have the Event Listener component (part of the Scriptable Object Event System pattern). This component has a soGameEvent variable slot (which is public), so I can use a FindAllObjectsOfType<EventListener> and store all those objects into a list. Next, I can store the GameEvent and GameObject information from each list value into a Dictionary (one which takes a GameEvent and a GameObject and making sure there are no doubles of the GameEvent). This means I now have all the Events being used in the scene, and each object that uses them.
Next, I had to visualise this information into the Event Manager window. This just involved going through each Key in the Dictionary, then looping through each Value and outputting them to the window GUI – then moving to the next Key. To make sure all the information had equal size, I get the current width of the Window, divided it by four and set each label/button/field to that value. I also added a column that displays a Raise button for each Event. These Buttons appear when the Scene is being played and make it able to Raise the Events whenever you want to – perfect for debugging events, and the corresponding behaviour.
I realised in my early implementations of the window, that it wouldn’t update when the Scene changes, and would cause errors when trying to Raise the events (as the objects no longer exist). This meant I would need a way to update the Dictionary with the new Scene values. Initially I implemented a Button that would refresh the list but meant the User would have to keep pressing it, which made it just less intuitive to use. This led to me looking into Scene change events, and finding there was an activeSceneChanged event, for both in-editor and in-scene, that I could subscribe to. This meant I no longer needed the Button and the window would automatically update when the scene was changed.
Towards the end of the project I realised that I could add Description fields, for both the soGameEvents and the EventListener component. This means when they are created/added, you can provide a brief explanation of what should happen – either when the Event should be triggered, or what will happen when this Listener hears the event. Though it didn’t add any extra functionality, it just makes it clearer how everything should work or for when working in a team. Lastly, I added a section in the Event Manager window that has the Event description – getting it from the Dictionaries value description variable.
The final addition to the Window, was a tab window that shows any EventListener components with an unassigned Event. It just required adding a Toolbar element + switch statement to switch between the values. I added another List that just stores all the GameObjects with an EventListener but no assigned Event. Then when I’m getting all the GameObjects in the scene, I just add them to the list, instead of the Dictionary. Finally, when the new tab section is drawn, it just loops through the list and displays each value in it. I make sure this window is Repainted often, as the user is most likely to add an Event or delete the component if unneeded.
Scriptable Object Variable
The second part of this project was adding more efficiency to the Scriptable Object Variable. Their starting implementation just gives a public variable that can be accessed by other scripts, this can be improved. To start with, I made the variable private and created methods that can set/modify the key value. A friend suggested I also have a method for passing another Scriptable Variable value into it. So, I could pass a Scriptable Float value into another Scriptable Float – this just saves a bit of typing in the future.
From the Unite talk he mentioned about being able to reset the values when needed, which was a feature I really liked the idea of. This just provides extra functionality when it comes to testing & tweaking values. To do this I added a second value, so there was a starting and a current value. The current value is not visible in the inspector and only modified in the script. When the Object is deseriallzed, the current value is set to the starting value – this is done by the script implementing the ISerializationCallbackRecevier interface. Most of the Scriptable Variables are all similar in their implementation with a few exceptions. The Int and Float variables both have a method for adding a value, while the Bool variable has a method for toggling its value.
Now all the logic for the Variables was implemented but I wanted to make their editor look nicer/cleaner. For each Variable type I created an editor class for them. The main thing I added was a debug box for each one, this included the reset value button, a label with the current value for the variable. With the bool, I added a toggle button and for the Int + Float, I added a way to modify the current value +/-. To finish the variables, I added a custom icon for each one, this makes it easier to see what’s what in the project view.
With both the Event Manager window and Scriptable Variables sorted, the final part was to create an example project, that shows off how to use the Event Manager and Variables. For this I created a scene with a cube, you can left click to damage the cube and right click to heal it. There is a UI that updates when the player takes damage or heals, and the player will become in active once their health becomes 0. This is a nice example as there are multiple things looking at the player health variable and updating on Damage or Heal events.