Unity — uEvent & ScriptableObjects
Unity3D is a powerful suite of tools (Project IDE, Code IDE, and run-time) for game development. Unity supports several languages, but the community consensus is to use only C#.
▶ Enjoying The Content?
I am a Unity Game & Tools Developer with over 20 years of game development experience. I am available for hire (Remote, Contract).
The Observer Pattern
What is an observer pattern? It is a code set up that defines a one-to-many relationship such that when one object changes state, the others are notified and updated automatically.
Terms
- Subject — The object being observed
- Observer — The object listening to and reacting to any event about the Subject
- Event — The notification saying “something changed”
- Invoke — The action of sending the event
- AddListener — The action to listening for the event
Key Concepts
- Decoupling — The Subject does not ‘know’ that it is being observed. It says “something changed, here are the details if anyone cares!”.
UEvent
Based on years of architecture experience in Unity Projects (See my Architecture articles Part 1 and Part 2) I have seen communication between runtime objects as a notorious cause of scalability and maintainability issues within a codebase. Elegantly solving communication in a game project is worth its weight in gold.
Here is the latest and greatest library I created using the aforementioned Observer Pattern, SOLID, KISS, and the unique benefits of Unity’s ScriptableObject
and Unity’s UnityEvent
. It is called UEvent
and contains the UEvent
namespace. As the “U” hints, this library is designed specifically for use with the Unity platform.
Example #1 : UnityEvent
Let’s start with a vanilla Unity example without my custom library.
This is not a strict usage of the observer pattern because here the Subject’s Inspector UI ‘knows’ which Observer is listening. Still, this is a good start for decoupling. It is also a good starting point for comparison to the more complex examples further below.
Subject — Inspector
Subject — Code
namespace RMC.Core.UEvents.Examples
{
public class UEventDemoSubject : MonoBehaviour
{
[SerializeField]
private UEvent _onAwaked = new UEvent(); protected void Awake ()
{
_onAwaked.Invoke(null);
}
}
}
Observer— Inspector
Observer — Code
namespace RMC.Core.UEvents.Examples
{
public class UEventDemoObserver : MonoBehaviour
{
public void SayHelloWorld()
{
Debug.Log($"{this.GetType().Name} Hello World!");
}
}
}
Example #2 : UEventDispatcher
Here we replicate the use case from above with a few specific improvements.
- Subject and Observer are decoupled
- Subject has no serialized reference to the Observer, and vice-versa
- The
UEventDispatcher
is a ‘global’ bus for all events in the project - The
UEventData
is the optional payload sent along with the event. Want to send custom data? Easy! Create a new class of typeIUEventData
Subject — Inspector
Subject — Code
namespace RMC.Core.UEvents.Examples
{
public class UEventDispatcherDemoSubject : MonoBehaviour
{
protected void Start()
{
UEventData uEventData = new UEventData();
UEventDispatcherSingleton.Instance.Invoke<UEvent>(uEventData);
}
}
}
Observer — Inspector
Observer — Code
namespace RMC.Core.UEvents.Examples
{
public class UEventDispatcherDemoObserver : MonoBehaviour
{
protected void Start()
{
UEventDispatcherSingleton.Instance.AddEventListener<UEvent>(
UEventDispatcherSingleton_OnSampleEvent);
} private void UEventDispatcherSingleton_OnSampleEvent(
IUEventData uEventData)
{
Debug.Log($"{this.GetType().Name} OnSampleEvent()...\n
uEventData='{uEventData}'.");
}
}
}
Example #3 : UEventAsset
Ok, now let’s really crank up the power to 11.
Here we replicate the use case from above with a few more specific improvements
- The
UEvent
concept is wrapped in aScriptableObject
of typeUEventAsset
Each separate event concept now sits in the Unity Project Window ready to be reused via drag-n-drop. Awesome!
With this pattern, a mini-library of events is created for unique use within the needs of the game project. Easy to organize, name, locate, and use. Nice!
Subject — Inspector
Subject — Code
namespace RMC.Core.UEvents.Examples
{
public class UEventAssetDemoSubject : MonoBehaviour
{
[SerializeField]
private UEventAsset _uEventAsset = null; protected void Start ()
{
_uEventAsset.Invoke(new UEventData());
}
}
}
Observer — Inspector
Observer — Code
namespace RMC.Core.UEvents.Examples
{
public class UEventAssetDemoObserver : MonoBehaviour
{
[SerializeField]
private UEventAsset _uEventAsset = null; protected void OnEnable()
{
_uEventAsset.AddListener(OnEvent);
} protected void OnDisable()
{
_uEventAsset.RemoveListener(OnEvent);
} private void OnEvent(IUEventData uEventData)
{
Debug.Log($"{this.GetType().Name} OnEvent()
uEventData='{uEventData}'. Null is ok.");
}
}
}
Why “UnityEvent”?
The UEvent
solution leans heavily on the built-in Unity UnityEvent
class.
Benefits
- Consistency with Unity’s first-party patterns
- Built-in rendering to the Unity Inspector
- Concise, purposeful API (Mimicked in the
UEvent
library)
Alternatives exist such as the C# event
keyword, a raw C# solution based on System.Object
, or something else. While a 2016 study shows that UnityEvent
operations are relatively slow, I find that the speed is acceptable. Since that time, its likely that speed improvements have been made with Unity updates. I have not retested the speeds recently.
Bonus: The UEvent
library is architected with interfaces and refactoring out UnityEvent
is possible if your team prefers an alternative.
References
- Unite Austin 2017 — Game Architecture with Scriptable Objects (Video)
- ScriptableObject Events With Custom Data — Unity — Part 1 & 2 (Video)
- ScriptableObjects in Unity by Brackeys (Video)
Downloads
Complete source-code and examples are provided for you. Enjoy!
- UEventDispatcher (Github)
More By Samuel Asher Rivello
- Unity — Game Architectures — Part 1
- Unity — Game Architectures — Part 2
- Unity — C# Coding Standards
- Unity — Project Structure Best Practices!
Available For Unity Hire: Remote, Contract
20 years of game development experience. SamuelAsherRivello.com
Comments?
Let me know! Twitter.com/srivello