Variables
StoryFlow variables are the backbone of dynamic storytelling. They store state that persists throughout your story - tracking player choices, inventory, stats, and any other data your narrative depends on. This guide covers how to interact with them from Unity using C#.
Variable Types
Every variable in a StoryFlow project carries a set of core properties:
- Id - A generated hash identifier (e.g.,
var_A3F8B21C). Used internally by the runtime. - Name - The human-readable display name. This is the string you pass to getter/setter methods from C#.
- Type - One of the
StoryFlowVariableTypeenum values. - Value - The current value, stored as a
StoryFlowVariant. - IsArray - Whether this variable holds a single value or an array of values.
- EnumValues - For Enum-type variables, the list of valid option strings.
StoryFlowVariableType Enum
The plugin defines the following variable types:
| Type | Category | Description |
|---|---|---|
Boolean | Core | True or false flags (quest completed, has item, etc.) |
Integer | Core | Whole numbers (gold, health, counts) |
Float | Core | Decimal numbers (percentages, multipliers) |
String | Core | Text values (names, messages, locations) |
Enum | Core | One value from a predefined set of named options |
Image | Asset reference | Reference to an image asset (used internally by the runtime) |
Audio | Asset reference | Reference to an audio asset (used internally by the runtime) |
Character | Asset reference | Reference to a character definition. From C#, GetCharacterArrayVariable reads character arrays as List<string> of character paths. See the multi-character section in the Characters guide. |
Map | Container | Ordered key/value entries with any key and value type combination. Added in v1.2.0. See Maps below. |
Core, Asset and Container Types
The five core types (Boolean, Integer, Float, String, Enum) are the ones you will interact with most often from C#. The asset reference types (Image, Audio, Character) are managed internally by the StoryFlow runtime to resolve media references during dialogue execution. You typically do not need to read or write asset-type variables directly. Containers hold multiple values: any type can be stored as an array (IsArray = true) and the Map type holds ordered key/value entries. See Arrays and Maps below.
StoryFlowVariant
StoryFlowVariant is the type-safe value container used throughout the plugin. It holds one value at a time along with its type tag, so you always know what kind of data you are working with.
Getter methods return a default if the variant holds a different type:
| Method | Return Type | Default |
|---|---|---|
GetBool(defaultValue) | bool | false |
GetInt(defaultValue) | int | 0 |
GetFloat(defaultValue) | float | 0f |
GetString(defaultValue) | string | "" |
GetEnum(defaultValue) | string | "" |
GetArray() | List<StoryFlowVariant> | Empty list |
GetMap() | List<StoryFlowMapEntry> | Empty list |
Setter methods update the value and its type tag:
SetBool(bool value)SetInt(int value)SetFloat(float value)SetString(string value)SetEnum(string value)SetMap(List<StoryFlowMapEntry> entries)- Added in v1.2.0.
Factory methods create a variant from a typed value:
// Create variants from typed values
StoryFlowVariant boolVal = StoryFlowVariant.Bool(true);
StoryFlowVariant intVal = StoryFlowVariant.Int(42);
StoryFlowVariant floatVal = StoryFlowVariant.Float(3.14f);
StoryFlowVariant strVal = StoryFlowVariant.String("Hello");
StoryFlowVariant enumVal = StoryFlowVariant.Enum("Hard");
// Read values back
bool b = boolVal.GetBool(); // true
int i = intVal.GetInt(); // 42
float f = floatVal.GetFloat(); // 3.14
string s = strVal.GetString(); // "Hello"
// ToString() for display / logging
string display = intVal.ToString(); // "42"
// Array support
var items = new List<StoryFlowVariant>();
items.Add(StoryFlowVariant.Int(1));
items.Add(StoryFlowVariant.Int(2));
StoryFlowVariant arrayVal = new StoryFlowVariant();
arrayVal.ArrayValue = items;
List<StoryFlowVariant> retrieved = arrayVal.GetArray(); Local vs Global Scope
StoryFlow variables exist in one of two scopes, and the scope determines their lifetime and visibility:
| Property | Local Variables | Global Variables |
|---|---|---|
| Defined in | Per-script (.sfe file) | Project level (global-variables.json) |
| Lifetime | Copied fresh when a script starts executing | Shared across all components for the entire session |
| Visibility | Only accessible within that script | Accessible from any script, any component |
| Storage | StoryFlowComponent instance | StoryFlowManager.Instance |
| Cross-component | No - each component has its own copy | Yes - changes in one component are visible to all others |
Scope Determines Behavior
The global parameter (default false) on every Get/Set method determines search behavior. When false, the method searches local variables first, then falls back to global variables if no match is found. When true, only global variables are searched. Local variables are per-component and reset on each script load, while global variables persist and are shared across every StoryFlowComponent via the StoryFlowManager.
// Reading a local variable (scoped to this component's current script)
bool hasKey = component.GetBoolVariable("hasKey", global: false);
// Reading a global variable (shared across all components)
int playerGold = component.GetIntVariable("playerGold", global: true); Reading Variables
The StoryFlowComponent exposes typed getter methods for reading variable values:
| Method | Parameters | Return Type |
|---|---|---|
GetBoolVariable | string name, bool global = false | bool |
GetIntVariable | string name, bool global = false | int |
GetFloatVariable | string name, bool global = false | float |
GetStringVariable | string name, bool global = false | string |
GetEnumVariable | string name, bool global = false | string |
GetArrayVariable | string name, bool global = false | List<StoryFlowVariant> |
GetMapVariable | string name, bool global = false | List<StoryFlowMapEntry> |
Each method looks up the variable by its display name in either the local (component) or global (manager) store, then returns the value cast to the appropriate type. If the variable is not found, the type's default value is returned.
For GetStringVariable and GetEnumVariable, values are resolved through the project's string table automatically. You get the actual display text, not internal keys like str_0. The same applies to string and enum elements returned by GetArrayVariable.
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Local variables
bool questComplete = sf.GetBoolVariable("questComplete", global: false);
string playerName = sf.GetStringVariable("playerName", global: false);
// Global variables
int gold = sf.GetIntVariable("playerGold", global: true);
float reputation = sf.GetFloatVariable("reputation", global: true);
string difficulty = sf.GetEnumVariable("difficulty", global: true);
// Array variables — works for any array type (string, int, float, bool, enum, etc.)
var inventory = sf.GetArrayVariable("inventory");
foreach (var item in inventory)
Debug.Log(item.GetString());
// Single-line with string.Join
Debug.Log(string.Join(", ", sf.GetArrayVariable("inventory"))); Variable Names
Variable names are the human-readable display names you assign in the StoryFlow Editor (e.g., playerGold, questComplete). You pass these names directly to getter/setter methods - no need to look up internal IDs. The global parameter disambiguates local and global variables that share the same name.
Setting Variables
The component also exposes typed setter methods:
| Method | Parameters | Returns |
|---|---|---|
SetBoolVariable | string name, bool value, bool global = false | void |
SetIntVariable | string name, int value, bool global = false | void |
SetFloatVariable | string name, float value, bool global = false | void |
SetStringVariable | string name, string value, bool global = false | void |
SetEnumVariable | string name, string value, bool global = false | void |
Setting a variable updates the value in the appropriate scope and fires the OnVariableChanged event (see below). This means your game logic can react immediately to any change, whether triggered by a node in the StoryFlow graph or by your own C# code.
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Set a local boolean (e.g., after player picks up a key)
sf.SetBoolVariable("hasKey", true, global: false);
// Set a global integer (e.g., award gold for completing a quest)
sf.SetIntVariable("playerGold", 500, global: true);
// Set a global enum (e.g., change difficulty at runtime)
sf.SetEnumVariable("difficulty", "Hard", global: true); OnVariableChanged Event
Every time a variable changes - whether from node execution inside the StoryFlow graph or from a direct C# call - the OnVariableChanged event fires on the component.
Signature:
public event Action<StoryFlowVariable, bool> OnVariableChanged; Parameters:
- StoryFlowVariable - The full variable object containing
Id,Name,Type,Value, and other metadata. - bool isGlobal - Whether the change occurred in the global scope.
Subscribing in C#:
void Start()
{
var sf = GetComponent<StoryFlowComponent>();
sf.OnVariableChanged += HandleVariableChanged;
}
void OnDestroy()
{
var sf = GetComponent<StoryFlowComponent>();
if (sf != null)
sf.OnVariableChanged -= HandleVariableChanged;
}
void HandleVariableChanged(StoryFlowVariable variable, bool isGlobal)
{
if (variable.Name == "playerGold")
{
int newGold = variable.Value.GetInt();
UpdateGoldUI(newGold);
}
// You can also check by type or internal Id:
// variable.Id - internal hash identifier
// variable.Type - StoryFlowVariableType enum
} UnityEvent Alternative
The component also exposes Inspector-assignable UnityEvent fields (OnDialogueStartedEvent, OnDialogueUpdatedEvent, OnDialogueEndedEvent). For variable changes specifically, use the C# event Action approach shown above, as it provides the full StoryFlowVariable object and scope information.
Variable Interpolation in Dialogue
Dialogue text in StoryFlow can reference variables using the {varname} syntax. When the runtime builds the dialogue state, it automatically replaces these tokens with the current variable values.
// In the StoryFlow Editor dialogue node text:
"You have {playerGold} gold coins, {playerName}."
// At runtime, if playerGold = 500 and playerName = "Alice":
"You have 500 gold coins, Alice."
Interpolation is resolved automatically when the OnDialogueUpdated event fires. You do not need to perform any manual string replacement - the text in the dialogue state is already fully resolved.
Live Re-rendering on Variable Change
When a Set* node (setBool, setInt, setFloat, etc.) changes a variable and has no outgoing edge, the runtime returns to the current dialogue node and re-renders it with the updated variable values. This means the OnDialogueUpdated event fires again with the new interpolated text. Your UI simply needs to respond to OnDialogueUpdated as usual - the updated text arrives automatically.
Arrays
Variables can be arrays (IsArray = true). An array variable holds an ordered list of StoryFlowVariant values, all of the same type. Arrays are supported for Bool, Int, Float, String, Image, Character and Audio variables. The C# helpers also cover enum arrays for programmatically created variables, but the editor does not offer them.
Array Operations
The StoryFlow node graph provides a full set of array manipulation nodes. These operations execute as part of the story flow and update the variable automatically:
| Operation | Description |
|---|---|
| Get / Set Array | Read or replace the entire array |
| Get / Set Element | Read or write a single element by index |
| Add | Append an element to the end of the array |
| Remove | Remove an element by value or index |
| Clear | Remove all elements from the array |
| Length | Get the number of elements in the array |
| Contains | Check if a value exists in the array (returns boolean) |
| FindIn | Get the index of a value (-1 if not found) |
| GetRandom | Retrieve a random element from the array |
The forEach loop node iterates over all elements in an array, executing the connected subgraph once per element. This is useful for processing inventories, applying effects to party members, or evaluating a list of quest objectives.
Arrays in C#
From C#, use GetArrayVariable(name) on the StoryFlowComponent to read an array variable's contents. It returns a List<StoryFlowVariant> — call GetString(), GetInt(), GetFloat(), or GetBool() on each element. String and enum values are resolved through the string table automatically. Array operations from the node graph fire OnVariableChanged just like scalar changes.
Maps
Added in v1.2.0. Map variables hold an ordered list of key/value entries. Any key and value type combination is supported and insertion order is always preserved, both in the graph and through save/load. Maps can be declared at the project level or per script and the importer parses their default entries from synced data automatically.
No Map Character Variables Yet
Map variables can be declared on projects and scripts but not on characters in this release. Characters are getting a redesign soon and maps will come to them with it.
Map Operations
The node graph provides a full set of map manipulation nodes. The write nodes (setMap, setMapValue, removeMapKey, clearMap) and the forEachMap loop execute as part of the story flow, while the read nodes (getMap, getMapValue, hasMapKey, mapSize, mapKeys, mapValues) evaluate lazily on demand:
| Node | Description |
|---|---|
getMap | Read a map variable as a source for other map nodes |
setMap | Point a map variable at another map (see aliasing below) |
getMapValue | Read the value for a key. Exposes an isValid output alongside the typed value |
setMapValue | Write the value for a key, adding the entry if the key is new |
hasMapKey | Check whether a key exists (returns boolean) |
mapSize | Get the number of entries in the map |
mapKeys | Produce an array of all keys, ready to plug into the existing array nodes |
mapValues | Produce an array of all values, ready to plug into the existing array nodes |
removeMapKey | Remove the entry with the given key |
clearMap | Remove all entries from the map |
Read nodes (getMapValue, hasMapKey, mapSize, mapKeys, mapValues) evaluate lazily inside condition and value chains, just like the array read nodes.
The forEachMap loop node iterates over the map's entries with key and value outputs on each iteration. The loop walks a snapshot of the entries taken when it starts, so mutating the map inside the loop body does not affect iteration.
setMap Aliasing
setMap points the target variable at the source variable's live map storage rather than copying it, matching the editor runtime. After setMap(b ← getMap(a)), the mutator nodes (setMapValue, removeMapKey, clearMap) modify the origin map in place, so every aliased variable and downstream chain observes the change. A later clearMap(a) also empties b. The one exception is runScript output sources, which are snapshotted instead and never aliased.
Maps in C#
From C#, use GetMapVariable(string name, bool global = false) on the StoryFlowComponent to read a map variable's entries in insertion order. It returns a List<StoryFlowMapEntry>, where each entry exposes a Key and a Value, both of type StoryFlowVariant:
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Read a map variable (entries arrive in insertion order)
List<StoryFlowMapEntry> stats = sf.GetMapVariable("playerStats", global: true);
foreach (var entry in stats)
{
string statName = entry.Key.GetString();
int statValue = entry.Value.GetInt();
Debug.Log($"{statName}: {statValue}");
}
String, enum, image, audio and character values are resolved through the string table automatically, mirroring GetArrayVariable. Map keys are raw identifiers and are never localized. The returned entries are deep copies, never the live storage: because map variables can share storage with each other through setMap aliasing, handing out the live entry list would let game code corrupt every aliased variable at once.
Typed Map Access in C#
When you know a map's key and value types ahead of time, the typed accessors let you read and write it as a native C# Dictionary<TKey, TValue> instead of iterating StoryFlowMapEntry objects and calling GetInt() / GetString() on each variant. They follow the same naming pattern as the typed array setters and live on the StoryFlowComponent.
Map keys collapse to two native C# types - string (which also covers enum keys, since enum keys are stored as strings) and int - and values collapse to four - bool, int, float, and string (which also covers enum, image, audio and character values, all stored as strings). That gives eight typed getter/setter pairs:
| Getter | Setter | Dictionary Type |
|---|---|---|
GetStringToBoolMap | SetStringToBoolMap | Dictionary<string, bool> |
GetStringToIntMap | SetStringToIntMap | Dictionary<string, int> |
GetStringToFloatMap | SetStringToFloatMap | Dictionary<string, float> |
GetStringToStringMap | SetStringToStringMap | Dictionary<string, string> |
GetIntToBoolMap | SetIntToBoolMap | Dictionary<int, bool> |
GetIntToIntMap | SetIntToIntMap | Dictionary<int, int> |
GetIntToFloatMap | SetIntToFloatMap | Dictionary<int, float> |
GetIntToStringMap | SetIntToStringMap | Dictionary<int, string> |
Every getter takes (string variableName, bool global = false) and returns the dictionary. Every setter takes (string variableName, Dictionary<TKey, TValue> values, bool global = false) and returns void. The global parameter behaves exactly like the scalar accessors: when false the lookup searches local variables first then falls back to global, and when true it targets only the global scope. Each accessor guards the map's declared key and value types - if they do not match the method you called (for example, calling GetStringToIntMap on a map with float values), it logs a warning and returns an empty dictionary, and setters log a warning and no-op.
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Read a string-keyed, int-valued map as a native dictionary
Dictionary<string, int> stats = sf.GetStringToIntMap("playerStats", global: true);
int strength = stats.TryGetValue("strength", out var s) ? s : 0;
// Mutate it and write the whole dictionary back
stats["strength"] = strength + 1;
stats["charisma"] = 7;
sf.SetStringToIntMap("playerStats", stats, global: true);
// String values are resolved through the string table on read
Dictionary<string, string> titles = sf.GetStringToStringMap("npcTitles");
foreach (var pair in titles)
Debug.Log($"{pair.Key} => {pair.Value}"); Dictionary Order Is Not Guaranteed
StoryFlow preserves map entry order everywhere else (the graph, save/load, and the mapKeys / forEachMap projections), but a C# Dictionary<TKey, TValue> does not contractually preserve insertion order. The typed getters build the dictionary from the ordered storage for ergonomic access, but that view may reorder entries. When you need the authoritative order, call GetMapKeysInOrder(string variableName, bool global = false). It returns a List<string> of the keys in insertion order - string and enum keys verbatim, integer keys stringified via int.ToString() - so you can index back into the dictionary in the correct sequence.
// Iterate a typed map in the authoritative insertion order
Dictionary<string, int> stats = sf.GetStringToIntMap("playerStats", global: true);
foreach (string key in sf.GetMapKeysInOrder("playerStats", global: true))
Debug.Log($"{key}: {stats[key]}");
Like the typed array setters, the map setters broadcast OnVariableChanged and re-render the current dialogue node after writing, so option visibility and {varname} text interpolation refresh automatically - you do not need to trigger anything by hand. A null dictionary is treated as an empty map and clears the variable's entries.
Resetting Variables
The plugin provides methods to reset variables back to their initial values as defined in the StoryFlow project. This is useful for restarting a story, resetting a scene, or clearing state for a new game.
| Method | Class | What It Resets |
|---|---|---|
ResetVariables() | StoryFlowComponent | Resets local variables on this component to their initial values from the script definition |
ResetGlobalVariables() | StoryFlowManager | Resets global variables to their project defaults |
ResetAllState() | StoryFlowManager | Resets global variables, characters, and once-only option tracking - a full session reset |
// Reset just this component's local variables
component.ResetVariables();
// Reset global variables across all components
StoryFlowManager.Instance.ResetGlobalVariables();
// Full state reset (globals + characters + once-only options)
StoryFlowManager.Instance.ResetAllState(); Reset Scope
ResetVariables() only affects the local variables of the specific StoryFlowComponent you call it on. If you have multiple components in your scene (e.g., multiple NPCs), each must be reset individually. For a full new-game reset, call ResetAllState() on the manager and then ResetVariables() on each active component.