Skip to content

Tutorial: Typed Key-Value Storage with DataStorage

Store and look up runtime values by Key — gameplay stats, UI state, save data, anything that lives as named typed entries. The DataStorage system gives you typed access to a shared key-value bag without coupling readers to writers.

By the end of this tutorial you'll have a PlayerStats storage with health, score, and a current weapon, accessed safely from multiple unrelated systems.

Prerequisites: None — this is foundational.


What Is a DataStorage?

Two kinds of host:

  • DataStorageAsset — a ScriptableObject asset. Shared by everything that references it. Useful for "global player stats", project-wide settings.
  • DataStorageComponent — a MonoBehaviour instance. Lives on a specific GameObject. Useful for "per-NPC stats", per-instance data.

Both wrap a DataStorage instance — a typed dictionary keyed by Key ScriptableObjects.

Each entry is stored under a typed Key. Reading a key returns its current value; writing sets it.


1. Create the Storage

Right-click in the Project window → Create → Jungle → Data → Data Storage. Name it PlayerStats.

The asset's Inspector shows its current entries (empty for now). Entries are added at edit time or runtime.


2. Create Keys

Each typed slot needs a Key. Keys are ScriptableObjects — refactor-safe identifiers.

  • Create → Jungle → Data → Key named Health.
  • Repeat for Score and CurrentWeapon.

The Key itself has no type — the type comes from how it's used (whichever typed source/sink references it). The same Key can be reused across multiple storages if you want.


3. Author Initial Values

In the PlayerStats asset, add entries:

Key Type Initial value
Health float 100
Score int 0
CurrentWeapon GameObject (reference to default weapon prefab)

Save the asset.


4. Read a Key

Any "value from key" provider can read from the storage. Common ones:

Source What it reads
FloatFromDataStorage One float entry.
IntFromDataStorage One int entry.
BoolFromDataStorage One bool entry.
GameObjectFromDataStorage One GameObject entry.
ObservableFloatValueFromKey One float entry with change notifications.

Drop one of these into any SmartFloat, SmartInt, etc. field. Set its storage to the PlayerStats asset, its key to the Health Key. The field now reflects the live value.

Conditions can read too — a FloatAboveCondition reading Health < 25 becomes "low health".


5. Write a Key

Write via:

  • A Setter entry — a ScheduledOperationEntry with a WriteFloat operation, scheduled on whatever cadence makes sense.
  • A Measurer — for "measure something → write into a key" of a single storage.
  • A DataStorageCommand — for one-off ops triggered by other processes (set, increment, clear).
  • Direct code — dataStorage.SetFloat(key, value).

The point is: every reader/writer agrees on the Key. Multiple writers / multiple readers, all decoupled.


6. Local vs Global Storage

Asset (DataStorageAsset):

  • Persists between scene loads.
  • Shared by every GameObject that references it.
  • Edit-time values become runtime starting values.
  • Important: edits to the asset during play mode are preserved into edit mode unless you reset. Add a DataStorageCommand with a Clear operation on scene start (or a Setter entry with OnceOnInitialize) to reset.

Component (DataStorageComponent):

  • Per-instance.
  • The Inspector shows the live values during play, scoped to that GameObject.
  • Resets automatically when the GameObject is disabled and the scene reloads.

Pick the asset when you want "one game, one stats bag". Pick the component when each prefab instance has its own.


7. Identifiable Components

A IdentifiableComponent (where present in your install) maps a Key to a GameObject — so when you write CurrentWeapon = "Pistol", a system using that Key can resolve which scene GameObject corresponds to the "Pistol" identity. Useful for inventory systems where the same key needs to refer to the same prefab/instance across scenes.


8. Listening for Changes

Wrap the read in an Observable source — ObservableFloatValueFromKey, ObservableIntValueFromKey, etc. — and pass it to:

  • A Reactor target for typed reactive bodies.
  • A UI binder that updates a label on change.
  • A Condition that polls.

The observable fires only on actual change, not every frame.


9. Composite Values from Multiple Keys

The shipped providers include compositors:

Provider What it computes
FloatSumFromKeys Sum of multiple float entries.
FloatMinFromKeys / FloatMaxFromKeys Min / max of multiple entries.
IntSumFromKeys Sum of multiple int entries.
TrueFlagsCountInt Number of true bool entries.
IntAtLeastBool True when an int ≥ threshold.
FloatInRangeBool True when a float is in [min, max].
AllFlagsTrueBool / AnyFlagTrueBool Multi-key AND/OR.

These let you derive a single typed value from many keys without writing a single line of code.


10. Common Patterns

Health bar

  • Storage: PlayerStats.
  • Key: Health (float).
  • Writer: A DataStorageCommand with SetFloat ops, fired by damage / heal events.
  • Reader: UI binder → ObservableFloatValueFromKey(PlayerStats, Health) → fill amount.

Inventory count

  • Storage: Inventory.
  • Key: Coins (int).
  • Writer: IncrementInt op on pickup events.
  • Reader: condition IntAtLeastBool(Coins, 100) gates the "buy" button.

Save game

  • Storage: any number of DataStorageAssets.
  • Reader: at game start, hydrate UI / world state from each key.
  • Writer: at game end / interval, snapshot the current values into a save file.

Next Steps