The idea
Shared variables, typed events and runtime sets live in assets. A health bar does not need a reference to
the player: both point at the same Player_Health asset. Systems stay decoupled, designers can
wire things up in the inspector, and scenes load in any order.
Variables can persist between sessions with one toggle, and references switch between a constant and an asset, so prototypes can start simple and grow.
// Gameplay code only holds references to assets.
[SerializeField] FloatVariableSO shotsFired;
[SerializeField] BoolVariableSO playerVisible;
[SerializeField] VoidEventSO onTeleport;
void Shoot()
{
// Passing "this" fills the Invoked by table.
shotsFired.SetCurrentValue(
shotsFired.CurrentValue + 1, this);
playerVisible.SetCurrentValue(false);
}
Debuggable by design
The usual complaint about this pattern is that it hides control flow: an event fires and nobody knows from where. Ripple answers that directly in the inspector. During play, every asset lists who invoked it, how many times and with what value.
"Scan listeners" goes the other way: it finds everything that reacts to an asset, across serialized event calls, runtime subscribers, and listener components in every scene and prefab.
Player_Shoot: the gun in the scene and in the player prefabTools for the whole team
The Ripple Wizard lists every Ripple asset in the project by type, to browse and edit them in one place. The event logger shows each event the moment it fires, with its sender, which makes bug reports from playtests far easier to follow.
All diagnostics compile out of player builds, and the runtime is IL2CPP-safe, so the tooling costs nothing in a shipped game.
Proven in production
Ripple started as the backbone of my own games and runs in all of my recent ones: Ryze, Shroomer and This Side Up.