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
ScriptableObject
assets - 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
.git
suffix 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).Changed
event (raised onValue
change).- Inspector drawer shows only
Value
, raisesChanged
on edit.
Invoke()
method.Invoked
event (raised onInvoke
).- Inspector drawer shows an
Invoke
button.
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 over
Asset-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
,float
Vector2
,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); // ok
Mutable 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); // ok
void 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