Pixi'VN Storage
When to use this skill
Use this skill whenever a task involves storing game state that must persist
across steps and saves: flags ("has the player met Liam?"), counters (gold,
affection points), arbitrary objects, or a custom class whose instances need
to survive save/load (e.g. a quest-tracking model). It does not cover
characters specifically (see the characters skill, which is itself built on
top of the pattern described here) or how the history/go-back system is
implemented internally (see the history skill).
Official docs (authoritative for terminology and recommended patterns):
storage,
flags,
storage classes,
temporary storage,
save & load.
ts
import { storage, StoredClassModel } from "@drincs/pixi-vn";
(Also available from the narrower
subpath — see
for when to prefer that.)
is a ready-made singleton (
) — you never instantiate
yourself.
1. Simple variables: /
ts
storage.set("test", "test1");
storage.get("test"); // "test1"
storage.set("Test", "test2"); // keys are case-sensitive, "Test" !== "test"
storage.get("nope"); // undefined
storage.set("variable1", { test: "test", test2: 1 }); // objects/arrays are fine too
storage.set("variable6", null);
storage.set("variable7", undefined); // undefined removes the key entirely
Allowed value types (
, from
src/storage/types/StorageElementType.ts
): strings, numbers, booleans,
/
, plain objects and arrays of these — no functions, class
instances, or Maps/Sets. For anything richer, use a
(below).
- — deletes a variable.
storage.flags.set(key, value: boolean)
/ — a
dedicated boolean-flag store (docs), kept
separate from /; flags default to when never set. Keys are
case-sensitive here too. The docs recommend flags over for
booleans specifically because of save size: internally all active flags are
kept as a single array of names, which is much lighter to serialize than one
storage entry per boolean. A documented pattern is exposing a class boolean
property that's backed by a flag name (a string) instead of a literal
, so the getter/setter transparently reads/writes
/.
storage.default = { key: value, ... }
— sets the starting values used
when the game boots or after ; if you later a
key (or it was never ), reading it falls back to this default instead
of .
- — wipes all stored variables (used by tests/new-game
flows), after which reads fall back to .
Temporary (label-scoped) variables
See
pixi-vn.com/start/temp-storage.
storage.temp.set(key, value)
behaves like
for reads —
checks temp variables first, then falls back to the
permanent store — but the value is deleted once the
it was set in
closes. It's for scratch state that should reset once the player moves on:
ts
storage.temp.set("counter", counter + 1);
// ... reads still go through storage.get("counter")
storage.temp.remove("counter");
Per the docs' own description of the lifecycle: if the label that set the
temp variable calls another label (nested call, e.g.
), the variable
stays accessible from that child label too — it's only deleted once the
originating label itself closes. But if control moves on via
(closing the current label and starting a new one instead of nesting into
it), the temp variable is gone immediately, since it belonged to the
now-closed label. Implementation-wise this is tracked as a deadline against
how many labels are open on the call stack at the moment
runs — see
,
setTempVariable & getTempVariable
,
where a temp
set inside a called label keeps incrementing across
steps but is reset once
narration.labels.closeCurrent()
/
unwind the label stack. Don't rely on temp variables for
state that must outlive the label that set it — use
for that.
2. Custom persisted classes:
See
pixi-vn.com/start/stored-classes.
For a richer object whose instances need to persist (survive save/load) and
whose fields should be individually get/set, extend
instead of storing a plain object with
. Each instance is
identified by a
(shared by all instances of that class) plus a
per-instance
; properties are read/written with the protected
/
helpers:
ts
import { StoredClassModel } from "@drincs/pixi-vn";
interface QuestProps {
title: string;
}
class QuestModel extends StoredClassModel implements QuestProps {
constructor(id: string, props: QuestProps) {
super("quest", id); // categoryId, instance id
this.defaultTitle = props.title;
}
readonly defaultTitle: string;
get title(): string {
return this.getStorageProperty<string>("title") ?? this.defaultTitle;
}
set title(value: string) {
this.setStorageProperty("title", value);
}
get completed(): boolean {
return this.getStorageProperty<boolean>("completed") ?? false;
}
set completed(value: boolean) {
this.setStorageProperty("completed", value);
}
}
const findLiam = new QuestModel("find_liam", { title: "Find Liam" });
findLiam.completed = true; // persists under the "quest" category
The constructor arg passed in (e.g.
) becomes the
default, kept
in memory on the instance;
writes an override into
storage that takes priority when reading. Passing
to
removes that property from storage (falling back to the
in-memory default on the next read). This is exactly the pattern
uses for
/
/
— see the characters
skill (the docs' own example uses a plain category name like
; the
library's built-in models happen to fence theirs as
to
avoid any accidental collision, but that underscore style is just a
convention, not a requirement — a plain string like
or
works the same way).
3. Reserved keys
Do not manually set/read storage keys that collide with the engine's own
bookkeeping. The docs' rule of thumb (
storage
docs) is: system
variables use a
-fenced prefix, so avoid that prefix in your own keys.
The full reserved key list lives in the
SYSTEM_RESERVED_STORAGE_KEYS
constant (
) — in practice most entries are plain namespaced
strings without the
fence (
,
,
,
,
,
,
,
, etc.), with
being the one
that actually uses the fenced style. Either way, treat every value in that
constant as off-limits for your own
/flag keys, to avoid
confusion when inspecting exported save data.
4. Relation to save/load and history
Building save/load itself (the
/
flow, and how to
persist the result) is covered by
— you generally don't touch storage directly for
that; every variable set through
is included automatically. The same is true for
step-by-step rewind: a snapshot of storage is captured at each recorded history step, so
variables participate in "go back" too, not just save/load — see
.
resets variables back to whatever was set via
, which is what tests use to get a
clean slate between scenarios.
Related skills
pixi-vn-getting-started, pixi-vn-saves, pixi-vn-characters, pixi-vn-history, pixi-vn-narration