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
ScriptableObjectasset. Shared by everything that references it. Useful for "global player stats", project-wide settings. - DataStorageComponent — a
MonoBehaviourinstance. 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
ScheduledOperationEntrywith aWriteFloatoperation, 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
DataStorageCommandwith a Clear operation on scene start (or a Setter entry withOnceOnInitialize) 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
DataStorageCommandwithSetFloatops, fired by damage / heal events. - Reader: UI binder →
ObservableFloatValueFromKey(PlayerStats, Health)→ fill amount.
Inventory count¶
- Storage:
Inventory. - Key:
Coins(int). - Writer:
IncrementIntop 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¶
- Setter tutorial — writing into DataStorage.
- Reactor tutorial — reacting to DataStorage changes.
- Measurer tutorial — auto-populating measurement keys.
- Data overview — system context.
- DataStorageAsset / DataStorageComponent references.