A comprehensive Scriptable Object-based architecture framework for Unity that promotes loose coupling, data-driven design, and event-driven communication in your games.
- Overview
- Features
- Installation
- Getting Started
- Variable System
- Event System
- Helper Tools
- Editor Tools
- API Reference
- Configuration
SO_System is a Unity framework built around ScriptableObjects that helps you create maintainable, scalable game architectures by:
- Decoupling systems through ScriptableObject-based events and variables
- Centralizing shared data in assets rather than singletons
- Simplifying event-driven communication between GameObjects and systems
- Accelerating development with ready-to-use helper components
- 🗂 Type-safe SO Variables — Bool, Int, Float, String, Vector2, Vector3, GameObject, and custom Enum types
- 📢 SO Event System — Raise and listen to events without direct references between objects
- 🔗 UI Binding — Bind SO variables directly to Text, Slider, Toggle, InputField, and TextMeshPro components
- ⏱ Invoke Helpers — Components for delayed, conditional, and lifecycle-triggered invocations
- 🛠 Editor Tools — Event Manager Window and custom property drawers for rapid development
- 🎨 Custom Icons — Distinctive icons for each SO type in the Project view
- 💾 Optional Caching — Persist variable values across play sessions with PlayerPrefs
-
Download or clone this repository:
git clone https://github.com/omar92/SO_System.git
-
Copy the folder into your Unity project's
Assetsdirectory:YourUnityProject/ └── Assets/ └── SO_System/ ← place the contents here -
Open your Unity project. The framework will compile automatically.
Requirements: Unity 2018.4 or later. TextMeshPro is optional (needed only for TMPro UI binding features).
- Create a new ScriptableObject variable via
Assets → Create → SO → Variables → Integer(or any type you need). - Create a new ScriptableObject event via
Assets → Create → SO → Event. - Add an
EventListenerSOcomponent to a GameObject in your scene. - Assign the event asset to the listener and wire up the response using the Unity Events inspector.
- Raise the event from a script or component — the listener will respond.
Right-click in the Project window and navigate to Create → SO → Variables to create a variable asset:
| Type | Menu Path |
|---|---|
| Bool | SO/Variables/Bool |
| Integer | SO/Variables/Integer |
| Float | SO/Variables/Float |
| String | SO/Variables/String |
| Vector2 | SO/Variables/Vector2 |
| Vector3 | SO/Variables/Vector3 |
| GameObject | SO/Variables/GameObject |
| Enum | (See Custom Enum Variables) |
Reference SO variable assets directly in your MonoBehaviour fields:
using SO;
using UnityEngine;
public class PlayerHealth : MonoBehaviour
{
[SerializeField] private IntSO health;
void Start()
{
// Get the current value
int currentHealth = health.GetValue();
// Set a new value
health.SetValue(100);
// Implicit conversion (read-only shorthand)
int hp = health; // equivalent to health.GetValue()
}
}void OnEnable()
{
health.Subscripe(OnHealthChanged);
}
void OnDisable()
{
health.UnSubscripe(OnHealthChanged);
}
void OnHealthChanged(object sender, System.EventArgs e)
{
Debug.Log("Health changed to: " + health.GetValue());
}IntSO and FloatSO support arithmetic operations:
[SerializeField] private IntSO score;
public void AddPoints(int points)
{
score.Add(points); // score += points
score.Sub(points); // score -= points
score.Mull(points); // score *= points
score.Divide(points); // score /= points
}Bind a variable's value to UI components directly from code:
using UnityEngine.UI;
using TMPro;
[SerializeField] private FloatSO playerSpeed;
[SerializeField] private Slider speedSlider;
[SerializeField] private TMP_Text speedLabel;
void Start()
{
playerSpeed.CopyToSlider(speedSlider);
playerSpeed.CopyToTMP_Text(speedLabel);
}You can also use the SetUiText component in the Inspector to bind one or more variables to a formatted text string without writing any code.
Enable allowCache on a variable asset in the Inspector to automatically persist its value in PlayerPrefs across play sessions. The cache key is SOV{VariableName}.
To reset a variable to its default value:
health.ResetValue();Subclass SOEnumVariable<T> to create a ScriptableObject variable for your own enum types:
using SO;
[CreateAssetMenu(menuName = "SO/Variables/GameState")]
public class GameStateSO : SOEnumVariable<GameState> { }
public enum GameState { MainMenu, Playing, Paused, GameOver }Right-click in the Project window and select Create → SO → Event to create an event asset.
Raise an event from any script that holds a reference to the asset:
using SO.Events;
public class EnemyDeath : MonoBehaviour
{
[SerializeField] private EventSO onEnemyDied;
void Die()
{
onEnemyDied.Raise(); // Raise with no value
onEnemyDied.Raise(gameObject); // Raise with a value (object)
onEnemyDied.RaiseValue(10); // Raise with an int
onEnemyDied.RaiseValue(3.5f); // Raise with a float
onEnemyDied.RaiseValue(true); // Raise with a bool
onEnemyDied.RaiseValue("EnemyDied"); // Raise with a string
}
}- Add an
EventListenerSOcomponent to any GameObject. - In the Inspector, assign the
EventSOasset to the Event field. - Add response callbacks under On Event Raised.
using SO.Events;
public class ScoreManager : MonoBehaviour
{
[SerializeField] private EventSO onEnemyDied;
void OnEnable()
{
onEnemyDied.RegisterListener(GetComponent<EventListenerSO>(), HandleEnemyDied);
}
void OnDisable()
{
onEnemyDied.UnregisterListener(GetComponent<EventListenerSO>());
}
void HandleEnemyDied(object value)
{
Debug.Log("Enemy died, received: " + value);
}
}Drag-and-drop components for common Unity invocation patterns — no code required:
| Component | Behavior |
|---|---|
InvokeOnAwake |
Fires a UnityEvent during Awake() |
InvokeOnStart |
Fires a UnityEvent during Start() |
InvokeOnEnable |
Fires a UnityEvent during OnEnable() |
InvokeOnDisable |
Fires a UnityEvent during OnDisable() |
InvokeAfterSeconds |
Fires a UnityEvent after a specified delay |
InvokeAfterRandomSeconds |
Fires a UnityEvent after a random delay within a range |
InvokeOnValueChange |
Fires a UnityEvent whenever a referenced VariableSO changes |
InvokeIf |
Fires a UnityEvent only if a BoolSO is true |
CompareIntegerSOVariables |
Compares two IntSO values with a chosen operator |
The static Z class provides coroutine helpers that work without requiring a MonoBehaviour:
using SO.tools;
// Invoke an action after a delay
Z.InvokeAfterDelay(2f, () => Debug.Log("2 seconds later"));
// Invoke at the end of the current frame
Z.InvokeEndOfFrame(() => Debug.Log("End of frame"));
// Invoke repeatedly while a condition is true
Z.InvokeWhile(() => isAlive, () => Debug.Log("Still alive"));
// Invoke when a condition becomes true
Z.InvokeWhen(() => isReady, () => Debug.Log("Ready!"));CoRef is a persistent singleton MonoBehaviour (survives scene loads) that Z uses internally to run coroutines.
| Component | Description |
|---|---|
SetUiText |
Bind one or more VariableSO values to a formatted Text/TMP string |
ConsoleToGUI |
Render Unity console logs on-screen as an in-game overlay |
Debuger wraps Unity's Debug.Log and is automatically disabled in Release builds, so you can leave debug calls in your code without affecting shipped builds:
using SO.tools;
Debuger.Log("This only appears in development builds");Open via Window → SOEventManager to see a live view of all EventSO assets referenced in the active scene, along with their registered listeners. This helps you quickly identify unconnected events or missing listeners.
All VariableSO and EventSO fields in the Inspector display enhanced drawers that show:
- The current value of the variable
- An Assign button to quickly create and assign a new asset
- A Reset button to restore the default value
- Inline value editing (no need to ping the asset)
Open the settings panel via Edit → Project Settings → SO System (or from the EventManagerWindow). The following options are available:
| Setting | Description |
|---|---|
ShowAssignButton |
Show/hide the Assign button in property drawers |
SOCreatePath |
Default folder for new SO assets |
EventSOCreatePath |
Default folder for new Event assets |
VarSOCreatePath |
Default folder for new Variable assets |
ShowEventDescription |
Display the event description field in the inspector |
EventSOListenerDefaultView |
Expand listeners by default in the event inspector |
AllowEditListenersFromEvents |
Allow editing listener responses from the event inspector |
ShowVarSOValue |
Display the current value in the variable inspector |
Settings are persisted in PlayerPrefs under the key SO_SYS_Settings.
// Value access
T GetValue()
void SetValue(T newValue, bool forceUpdate = false, bool log = false)
void SetValue(VariableSO<T> variableSO)
void SetValue(string value)
void ResetValue()
T GetDefaultValue()
// Change notifications
void Subscripe(EventHandler onValChanged)
void UnSubscripe(EventHandler onValChanged)
void UnSubscripeAll()
// UI binding
void CopyToText(Text textComponent)
void CopyToTMP_Text(TMP_Text textComponent)
void CopyToInputField(InputField inputField)
void CopyToSlider(Slider slider)
void CopyToScrollbar(Scrollbar scrollbar)void Add(T num)
void Sub(T num)
void Mull(T num)
void Divide(T num)// Raising
void Raise()
void Raise(object value)
void Raise(object value, Action preRaise, Action postRaise)
void RaiseImmediately(object value)
void RaiseValue(int value)
void RaiseValue(float value)
void RaiseValue(bool value)
void RaiseValue(string value)
// Listener management
void RegisterListener(EventListenerSO listener, ObjectEvent callback)
void UnregisterListener(EventListenerSO listener)All configuration is done through Unity's Inspector on the relevant assets:
- Variable assets: Set the default value, toggle
allowCache, choose reset behavior. - Event assets: Write a description, toggle debug logging.
EventListenerSOcomponent: Assign the event and response callbacks in the Inspector.- SO_SystemSettings: Accessible from the settings menu; controls Editor UI appearance.
This project does not currently specify a license. Please contact the repository owner before using it in commercial projects.