SOAR is an implementation of Scriptable Object Architecture. Scriptable Object Architecture intends to provide clean and decoupled code architecture. SOAR is implemented based on Ryan Hipple's talk at Unite Austin 2017.
SOAR is an event-based system that encourages the use of the Pub/Sub pattern.
Its fundamental principle involves treating the ScriptableObject instance (with its name/guid property) as a 'Channel' or 'Topic'.
The pairing between a publisher and a subscriber is established through references to each of SOAR's instance.
SOAR is developed and designed to be extensible with Reactive Extensions library R3, a feature-rich, modern Reactive Extensions for C#. SOAR wraps and utilizes R3's feature within the Scriptable Object Architecture. SOAR can function independently, but its implementation provides only basic functionality. It is highly recommended to use SOAR in conjunction with R3.
- R3 - The new future of dotnet/reactive and UniRx.
- Kassets - SOAR's predecessor. Scriptable Object Architecture extensible with UniRx and UniTask.
For further details, see Documentation
- Unity 6.0 or later
Note
This repository is a Unity Package, not a standalone Unity Project. It cannot be opened directly in the Unity Editor. Please follow the Installation instructions to add it to your project.
Add from OpenUPM | Import via scoped registry.
To add OpenUPM to Package Manager:
- open
Edit/Project Settings/Package Manager - add a new Scoped Registry:
Name: OpenUPM
URL: https://package.openupm.com/
Scope(s):
- com.ripandy
- com.cysharp.r3 (Recommended)
- click Save
- Open
Window/Package Manager - Select
My Registriesin top left dropdown - Select
SOARand clickInstall - Select
R3and clickInstall(Recommended) (see: Note)
Add from GitHub | Use GitHub link to import.
Add Package directly from GitHub.
- Open
Window/Package Manager - Click the
+icon - Select the
Install package from Git URLoption - Paste the following URL:
https://github.com/ripandy/SOAR.git - Click
Add
To install specific version, refer to SOAR's release tags.
For example: https://github.com/ripandy/SOAR.git#1.0.0
Clone to Local Folder | Manages changes independently.
- Clone this repository to local directory.
- Open
Window/Package Manager - Click the
+icon - Select the
Install package from diskoption - Select the
package.jsonfile from the cloned directory. - Click
Add
The package will be added to manifest.json as a local package (file://).
Source codes can then be modified from containing Unity Project.
Changes can be managed with git as usual.
Package path can be changed to relative path as alternative of the default absolute path.
{
"dependencies": {
"com.ripandy.soar": "file:../path/to/your/local/SOAR"
}
}Clone to Packages Folder | For development or contribution purpose.
Clone this repository to Unity Project's Packages directory: YourUnityProject/Packages/.
Unity will treat the project as a custom package. Source codes can then be modified from containing Unity Project. Changes can be managed with git as usual. SOAR can also be cloned as Submodule of a git repository.
Note
Installation of R3 in Unity requires installation of its NuGet version. See R3 Unity Installation for further detail.
SOAR's instances can be created from Create context menu or from Assets/Create on the menu bar.
Right-click on the Project window and select instance to create.
For GameEvent instances, select any of event types from Create/SOAR/Game Events/ menu.
To raise an event from Unity UI's Button, assign created GameEvent instance to the Button's OnClick event. Every time the button is clicked, the event will be published and all subscribers will be notified.
Unity Event Binder is a custom implemented Unity Component that forwards events raised by a GameEvent into UnityEvent.
This is also known as an EventListener in Scriptable Object Architecture terminology.
To use them, add the component to any GameObject and assign the GameEvent instance to the Game Event To Listen field.
Upon raising the event, actions assigned to On Game Event Raised will be invoked by UnityEvent.
To raise an event from script, use the GameEvent instance's Raise() method.
Upon raising the event, all subscribers will be notified.
// File: GameEventPublisherExample.cs
using Soar.Events;
using UnityEngine;
public class GameEventPublisherExample : MonoBehaviour
{
[SerializeField] private GameEvent gameEvent;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
gameEvent.Raise();
Debug.Log($"Game Event {gameEvent.name} Raised.");
}
}
}To subscribe to an event from script, use the GameEvent instance's Subscribe() method.
Upon subscribing, the provided callback will be invoked when the event is raised.
// File: GameEventSubscriberExample.cs
using System;
using Soar.Events;
using UnityEngine;
public class GameEventSubscriberExample : MonoBehaviour
{
[SerializeField] private GameEvent gameEvent;
private IDisposable subscription;
private void Start()
{
subscription = gameEvent.Subscribe(OnGameEventRaised);
}
private void OnGameEventRaised()
{
Debug.Log($"Game Event {gameEvent.name} Received.");
}
private void OnDestroy()
{
subscription?.Dispose();
}
}To raise an event from the inspector window, select a GameEvent instance, then press the Raise button in the inspector.
This will invoke the event and notify all subscribers.
Due to the decoupled nature of SOAR, subscribers do not need to be aware of the source of the event.
This allows for easy testing and debugging of events without the need for a specific publisher.
Note
By default, event listeners are not invoked in Edit Mode. Subscribers need to handle running in Edit Mode manually.
To set up a subscriber to run in Edit Mode, use the ExecuteAlways or ExecuteInEditMode attribute on the subscriber class.
This will allow the subscriber to be invoked in Edit Mode.
// File: GameEventSubscriberExample.cs
using System;
using Soar.Events;
using UnityEngine;
[ExecuteAlways]
public class GameEventSubscriberExample : MonoBehaviour
{
[SerializeField] private GameEvent gameEvent;
private IDisposable subscription;
private void OnEnable()
{
// Subscribe in both Edit and Play modes
SubscribeToEvent();
}
private void OnDisable()
{
// Unsubscribe in both Edit and Play modes
UnsubscribeFromEvent();
}
private void SubscribeToEvent()
{
if (gameEvent == null) return;
// Dispose previous subscription if it exists
subscription?.Dispose();
// Create new subscription
subscription = gameEvent.Subscribe(OnGameEventRaised);
}
private void UnsubscribeFromEvent()
{
subscription?.Dispose();
subscription = null;
}
private void OnGameEventRaised()
{
Debug.Log($"Game Event {gameEvent.name} Received on {(Application.isPlaying ? "Play" : "Edit")} mode.");
}
}Command is an implementation of Command pattern, utilizing ScriptableObject as an alternative to an Interface.
The Command class is an abstract, requiring concrete implementation.
This pattern is useful for one-way executions, such as logging.
Event represents an occurrence within the program execution that requires a specific response. Each event must have at least one publisher and one subscriber.
Variable is data stored in a ScriptableObject that can be manipulated.
In SOAR, Variable is derived from GameEvent and triggers a value-changed event upon modification.
Collection is a data structure that can hold multiple items.
The Collection class serves as a base for SoarList and SoarDictionary.
Collection offers additional events, triggered when an item is added, removed, or updated, or when the collection is cleared.
Collection implements common interfaces like ICollection, IList, and IDictionary to ensure compatibility with LINQ.
Transaction is a two-way event involving requests and responses. When a request is sent, a registered response event processes it and returns the result to the requester. Only one response event can be registered at a time. This is useful when an operation needs to wait for an event to complete.
SOAR provides default base classes that are usable immediately.
They can be accessed from the Create > SOAR context menu or the Assets > Create > SOAR menu bar item.
Note that Base Classes use a different assembly definition file (.asmdef).
A manual reference to Soar.Base may need to be added in the project's Assembly Definition (.asmdef) files.
Unity Event Binder is a custom implemented Unity Component that forwards events raised by a GameEvent into UnityEvent.
This is also known as an EventListener in Scriptable Object Architecture terminology.
SOAR implements an Editor tool to convert SOAR Variable's data into a JSON string or a local JSON file.
This functionality can be accessed from SOAR Variable's Inspector window.
Available only for classes that derives from JsonableVariable<T>.
To use Reactive Extensions Library R3 with SOAR, simply import R3 to the project.
Upon import, SOAR will adjust its internal integration using SOAR_R3 scripting define, which should be automatically defined when R3 is imported via package manager.
If the scripting define SOAR_R3 somehow is undefined, add it to Scripting Define Symbols on Project Settings.
By importing R3, SOAR has additional features:
-
Internal Event Handling with R3
SOAR is developed and designed to be extensible with Reactive Extensions library R3. It simply wraps
Observable, and utilizes them as internal event handlers. -
Async Event handling with ValueTask
With R3 imported, SOAR's event handlers can be used with
ValueTaskandIAsyncObservable.// File: GameEventAwaitExample.cs using System.Threading.Tasks; using Soar.Events; using UnityEngine; public class GameEventAwaitExample : MonoBehaviour { [SerializeField] private GameEvent gameEvent; private async void Start() { // Await the game event in Start await AwaitGameEvent(); } private async ValueTask AwaitGameEvent() { Debug.Log("Waiting for game event..."); await gameEvent.EventAsync(); Debug.Log("Game event received!"); } }
-
Conversion Methods
Importing R3 enables SOAR's instance to be convertible to
Observablefor R3,IObservablefor (Uni)Rx, andIAsyncObservablefor (Uni)Task. This allows SOAR's instance to utilize the functionality of each respective library. After conversion, refer to the documentation of each library for more details.// Implemented on SOAR's GameEvent<T> AsObservable() // Converts SOAR's instance to R3's Observable<T> AsUnitObservable() // Converts SOAR's instance to R3's Observable<Unit> AsSystemObservable() // Converts SOAR's instance to System's IObservable<T>. Can be extensible further with UniRx. ToAsyncEnumerable() // Converts SOAR's instance to System's IAsyncEnumerable<T>
The Publisher/Subscriber (Pub/Sub) pattern is a messaging pattern where:
- Publishers emit messages/events without direct knowledge of which components will receive them
- Subscribers register interest in certain events without knowing which components will produce them
- A message broker or event channel mediates between them, decoupling the components
In SOAR's implementation:
- SOAR's instances serve as the message channels or topics
- Publishers call the Raise() method on a GameEvent to broadcast messages
- Subscribers use Subscribe() to register callbacks that execute when events are raised
Though there's no explicit message broker implementation, the nature of ScriptableObject instances that persists across scenes, enables communication between components that might never directly reference each other. This pattern provides excellent decoupling in Unity, allowing components to communicate without direct dependencies.
SOAR fits well with the Model-View-Presenter (MVP) pattern.
SOAR's GameEvent or Variable instance can be utilized to wrap the Model.
As a wrapper, SOAR does not interact with the Model directly.
Model could be on a different namespace or assembly.
Then, use classes that inherits from MonoBehaviour as the Presenter or Controller.
These classes would subscribe to the GameEvent or Variable instance, then process the Model received from the event.
Finally, use Unity's components or UI elements as the View, which would be updated by presenter using the processed Model data.
- Unite2017 Scriptable Object Architecture sample project
- Unite2017 Game Architecture with Scriptable Objects on Slideshare
- R3 — A New Modern Reimplementation of Reactive Extensions for C#
- Kassets (SOAR's predecessor). Scriptable Object Architecture extensible with UniRx and UniTask.
- SOAR is Licensed under MIT License
- R3 is Licensed under MIT License
- Kassets (SOAR's predecessor) is Licensed under MIT License




