Unity containers for values and events with change/invoke notifications, as plain C# objects or asset-backed shared instances. Events can be invoked from the inspector.
- Value references (
Ref<T>,SharedRef<T>) with change notifications - Event references (
EventRef,SharedEventRef) with invoke notifications - Shared references as
ScriptableObjectassets - Custom property drawers for transparent editing and inspector invoke buttons
- Unity 2022.2+
- Resettables
- In Unity Editor click Window → Package Manager.
- Click + (top left) → Install package from git URL….
- Enter this repository URL with
.gitsuffix and click Add:https://github.com/darksailstudio/refs.git
Plain C# classes wrapping values (Ref<T>) or events (EventRef), with custom property drawers for transparent inspector editing and event wiring.
Ref(T initialValue)constructor.Value { get; set; }(thread-safe).Changedevent (raised onValuechange).- Inspector drawer shows only
Value, raisesChangedon edit.
Invoke()method.Invokedevent (raised onInvoke).- Inspector drawer shows an
Invokebutton.
var playerHP = new Ref<int>(10);
var playerDied = new EventRef();
playerHP.Changed += playerHP => {
if (playerHP <= 0) playerDied.Invoke();
};
playerDied.Invoked += () => Debug.Log("Game over");
playerHP.Value -= 10; // Triggers game overAsset-backed references (SharedRef<T>, SharedEventRef) for sharing state and events across objects and scenes, implemented as ScriptableObject assets, runtime-only even in the editor (state reset via the Resettables package). Inspired by Game Architecture with Scriptable Objects talk by Ryan Hipple at Unite Austin 2017.
Shared references are created via Assets → Create → Shared References → [Type].
Abstract base for asset-backed values. Same API as Ref<T> (except the constructor), but wrapped in a ScriptableObject asset.
Requires derived types for values due to Unity serialization constraints (assets can't be generic types). Implementations provided for common C# and Unity primitives:
int,bool,string,floatVector2,Vector3,Quaternion,Color,GameObject
Extend to support custom types:
using DarkSail.Refs;
using UnityEngine;
[CreateAssetMenu(menuName = "My Project/My Custom Shared Ref")]
class MyCustomSharedRef : SharedRef<MySerializableType> {}Same API as EventRef, but wrapped in a ScriptableObject asset.
using DarkSail.Refs;
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField] SharedIntRef playerHP;
[SerializeField] SharedEventRef playerDied;
public void TakeDamage(int damage)
{
playerHP.Value -= damage;
if (playerHP.Value <= 0) playerDied.Invoke();
}
}using DarkSail.Refs;
using UnityEngine;
public class HealthUI : MonoBehaviour
{
[SerializeField] SharedIntRef playerHP;
void OnEnable()
{
playerHP.Changed += OnPlayerHPChanged;
OnPlayerHPChanged(playerHP.Value);
}
void OnDisable()
{
playerHP.Changed -= OnPlayerHPChanged;
}
void OnPlayerHPChanged(int hp)
{
// Render player hit points value
}
}using DarkSail.Refs;
using UnityEngine;
public class GameOverUI : MonoBehaviour
{
[SerializeField] SharedEventRef playerDied;
void OnEnable()
{
playerDied.Invoked += OnPlayerDied;
}
void OnDisable()
{
playerDied.Invoked -= OnPlayerDied;
}
void OnPlayerDied()
{
// Render game over screen
}
}Unique and shared references implement the same interfaces.
Immutable access, preserves encapsulation.
private Ref<int> playerHP = new Ref<int>(10);
public IReadOnlyRef<int> PlayerHP => playerHP;void Subscribe(IReadOnlyEventRef playerDied)
{
playerDied.Invoked += () => Debug.Log("Game over");
}
var unique = new EventRef();
var shared = ScriptableObject.CreateInstance<SharedEventRef>();
Subscribe(unique); // ok
Subscribe(shared); // okMutable access, source-agnostic.
void Heal(IRef<int> playerHP) => playerHP.Value += 10;
var unique = new Ref<int>(0);
var shared = ScriptableObject.CreateInstance<SharedIntRef>();
Heal(unique); // ok
Heal(shared); // okvoid SubscribeAndPublish(IEventRef playerDied)
{
playerDied.Invoked += () => Debug.Log("Game over");
playerDied.Invoke();
}
var unique = new EventRef();
var shared = ScriptableObject.CreateInstance<SharedEventRef>();
SubscribeAndPublish(unique); // ok
SubscribeAndPublish(shared); // ok