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 GDScript.
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 functions from GDScript.
- Type - One of the
StoryFlowTypes.VariableTypeenum values. - Value - The current value, stored as a
StoryFlowVariant. - is_array - Whether this variable holds a single value or an array of values.
- enum_values - For Enum-type variables, the list of valid option strings.
VariableType Enum
The plugin defines the following variable types in StoryFlowTypes.VariableType:
| Type | Category | Description |
|---|---|---|
NONE | Default | No type / uninitialized. A StoryFlowVariant with this type returns false from is_valid(). |
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 (used internally by the runtime) |
MAP | Container | Key-value map stored as a Godot Dictionary with key and value type metadata. See Maps below. Added in v1.2.0. |
Core vs Asset Types
The five core types (Boolean, Integer, Float, String, Enum) are the ones you will interact with most often from GDScript. 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.
StoryFlowVariant
StoryFlowVariant is the type-safe value container used throughout the plugin. It extends RefCounted and 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 |
|---|---|---|
get_bool(default) | bool | false |
get_int(default) | int | 0 |
get_float(default) | float | 0.0 |
get_string(default) | String | "" |
get_array() | Array | Empty array |
get_map() | Dictionary | Empty dictionary. Added in v1.2.0. |
A variant holds either array or map data, never both. Calling set_array(value: Array) clears any map data and calling set_map(value: Dictionary) clears any array data, so the two container slots cannot get out of sync. is_map() reports whether the variant currently holds a map. Both map methods and the static from_map(value: Dictionary) factory were added in v1.2.0.
Factory methods create a variant from a typed value:
# Create variants from typed values
var bool_val := StoryFlowVariant.from_bool(true)
var int_val := StoryFlowVariant.from_int(42)
var float_val := StoryFlowVariant.from_float(3.14)
var string_val := StoryFlowVariant.from_string("Hello")
var enum_val := StoryFlowVariant.from_enum("Hard")
# Read values back
var b: bool = bool_val.get_bool() # true
var i: int = int_val.get_int() # 42
var f: float = float_val.get_float() # 3.14
var s: String = string_val.get_string() # "Hello"
# to_display_string() for display / logging
var display: String = int_val.to_display_string() # "42"
# is_valid() checks if the variant has been assigned a type
var valid: bool = int_val.is_valid() # true
# duplicate_variant() creates a deep copy
var copy := int_val.duplicate_variant()
# Array support
var items: Array = []
items.append(StoryFlowVariant.from_int(1))
items.append(StoryFlowVariant.from_int(2))
var array_val := StoryFlowVariant.from_array(items)
var retrieved: Array = array_val.get_array()
# Map support (added in v1.2.0)
var entries := {
"sword": StoryFlowVariant.from_int(1),
"potion": StoryFlowVariant.from_int(3),
}
var map_val := StoryFlowVariant.from_map(entries)
var is_map: bool = map_val.is_map() # true
var retrieved_map: Dictionary = map_val.get_map() 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 | StoryFlowRuntime autoload |
| Cross-component | No - each component has its own copy | Yes - changes in one component are visible to all others |
Automatic Scope Resolution
Unlike the Unreal Engine plugin, the Godot plugin does not require a is_global parameter on getter/setter functions. When you call get_bool_variable("hasKey"), the component looks up the variable by display name in the local scope first. If no match is found locally, it falls back to the global scope automatically. This means you simply pass the variable name and let the component resolve the scope for you.
# The component resolves scope automatically: local first, then global
var has_key: bool = storyflow.get_bool_variable("hasKey")
var player_gold: int = storyflow.get_int_variable("playerGold") Reading Variables
The StoryFlowComponent exposes typed getter functions for reading variable values by their display name.
| Function | Parameter | Return Type |
|---|---|---|
get_bool_variable | name: String | bool |
get_int_variable | name: String | int |
get_float_variable | name: String | float |
get_string_variable | name: String | String |
get_enum_variable | name: String | String |
Each function looks up the variable by its display name, checking the local scope first and then falling back to global. If the variable is not found, the type's default value is returned.
@onready var storyflow: StoryFlowComponent = %StoryFlowComponent
# Read variables by display name (scope resolved automatically)
var quest_complete: bool = storyflow.get_bool_variable("questComplete")
var player_name: String = storyflow.get_string_variable("playerName")
var gold: int = storyflow.get_int_variable("playerGold")
var reputation: float = storyflow.get_float_variable("reputation")
var difficulty: String = storyflow.get_enum_variable("difficulty") 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 functions - no need to look up internal IDs.
get_string_variable and get_enum_variable route their return values through the project string table using the component's language_code. This means callers always receive localized text rather than raw string-table keys, so the result is ready to render in a UI label without an extra lookup.
Setting Variables
The component also exposes typed setter functions. Like the getters, these resolve scope automatically.
| Function | Parameters | Returns |
|---|---|---|
set_bool_variable | name: String, value: bool | void |
set_int_variable | name: String, value: int | void |
set_float_variable | name: String, value: float | void |
set_string_variable | name: String, value: String | void |
set_enum_variable | name: String, value: String | void |
Setting a variable updates the value in the appropriate scope and emits the variable_changed signal (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 GDScript code.
@onready var storyflow: StoryFlowComponent = %StoryFlowComponent
# Set a boolean (e.g., after player picks up a key)
storyflow.set_bool_variable("hasKey", true)
# Set an integer (e.g., award gold for completing a quest)
storyflow.set_int_variable("playerGold", 500)
# Set an enum (e.g., change difficulty at runtime)
storyflow.set_enum_variable("difficulty", "Hard") variable_changed Signal
Every time a variable changes - whether from node execution inside the StoryFlow graph or from a direct GDScript call - the variable_changed signal is emitted.
Signal signature:
signal variable_changed(info: StoryFlowVariableChangeInfo) StoryFlowVariableChangeInfo properties:
- id (
String) - The internal variable identifier (hash key). - name (
String) - The human-readable display name of the variable. - value (
StoryFlowVariant) - The new value after the change. - is_global (
bool) - Whether the change occurred in the global scope.
Connecting to the signal:
@onready var storyflow: StoryFlowComponent = %StoryFlowComponent
func _ready() -> void:
storyflow.variable_changed.connect(_on_variable_changed)
func _on_variable_changed(info: StoryFlowVariableChangeInfo) -> void:
if info.name == "playerGold":
var new_gold: int = info.value.get_int()
update_gold_ui(new_gold)
# You can also check by scope:
if info.is_global:
print("Global variable '%s' changed" % info.name)
else:
print("Local variable '%s' changed" % info.name) 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 dialogue_updated signal 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 dialogue_updated signal fires again with the new interpolated text. Your UI simply needs to respond to dialogue_updated as usual - the updated text arrives automatically.
Arrays
Variables can be arrays (is_array = 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 GDScript API also exposes set_enum_array_variable for programmatically created variables, but the editor does not offer enum arrays.
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 GDScript
The simplest way to read an array variable from GDScript is get_array_variable(name) on the component, covered in Reading Arrays from GDScript below. Array operations driven by the node graph still emit variable_changed just like scalar changes.
Reading Arrays from GDScript
The StoryFlowComponent exposes get_array_variable(name) which returns an Array[StoryFlowVariant] of element copies. Each element is a fresh StoryFlowVariant, so callers can use the typed getters (get_bool, get_int, get_float, get_string) without worrying about mutating the underlying variable.
String and enum elements are routed through the project string table for the component's language_code, matching the behavior of get_string_variable and get_enum_variable. Image, audio, and character elements pass through unchanged because their stored values are already asset keys or character paths.
# Read an array of strings, already localized
var lines: Array[StoryFlowVariant] = storyflow.get_array_variable("introLines")
for v in lines:
print(v.get_string())
# Read an array of ints
var scores: Array[StoryFlowVariant] = storyflow.get_array_variable("levelScores")
for v in scores:
print(v.get_int()) Character arrays have their own helper
For variables whose element type is character, use get_character_array_variable(name) instead. It returns an Array[String] of character paths ready to pass back into the other character APIs. See the Character Arrays section of the characters page for a full walkthrough.
Setting Arrays from GDScript
Added in v1.2.0. The component exposes a typed setter for each array element type, mirroring the Unity and Unreal plugins. Each function looks up the variable by display name (local scope first, then global) and replaces its elements with the values you pass in.
| Function | Parameters |
|---|---|
set_bool_array_variable | variable_name: String, values: Array[bool] |
set_int_array_variable | variable_name: String, values: Array[int] |
set_float_array_variable | variable_name: String, values: Array[float] |
set_string_array_variable | variable_name: String, values: Array[String] |
set_enum_array_variable | variable_name: String, values: Array[String] |
set_image_array_variable | variable_name: String, asset_keys: Array[String] |
set_audio_array_variable | variable_name: String, asset_keys: Array[String] |
set_character_array_variable | variable_name: String, character_paths: Array[String] |
Writes behave like every other variable change: the variable_changed signal is emitted, and if a dialogue is currently showing, its text live-refreshes with the new values. A few type-specific rules to keep in mind:
- String elements are stored verbatim - no string-table key is created, so they bypass localization and are language-locked to whatever you wrote.
- Enum values are not validated against the variable's option list. Passing a string that is not one of the defined options stores it anyway.
- Image and audio entries are asset keys resolvable through the standard asset pools, and character entries are character paths - the same identifiers the read APIs return.
# Replace an int array (emits variable_changed, live-refreshes dialogue)
storyflow.set_int_array_variable("levelScores", [120, 340, 95])
# Replace a string array - stored verbatim, bypasses localization
storyflow.set_string_array_variable("introLines", ["Hello", "Welcome back"])
# Character entries are character paths
storyflow.set_character_array_variable("partyMembers", ["characters/elder", "characters/scout"]) Maps
Added in v1.2.0. Script and global variables can be maps: ordered key-value collections stored as Godot Dictionary objects with key and value type metadata. Entry order is preserved and matches the editor's serialized order, which is observable through the mapKeys, mapValues and forEachMap nodes.
- Key types: string, integer or enum
- Value types: string, integer, float, boolean, enum, image, character or audio
Script and Global Variables Only
Map variables are script and global only - they cannot be created on characters this release. Characters are getting a redesign soon and maps will come to them with it.
Map Operations
The node graph provides eleven map node types. Like array operations, these execute as part of the story flow and update the variable automatically:
| Node | Description |
|---|---|
getMap / setMap | Read or replace the entire map |
getMapValue / setMapValue | Read or write a single entry by key |
hasMapKey | Check if a key exists (returns boolean) |
mapSize | Get the number of entries in the map |
mapKeys / mapValues | Get the keys or values as an array, in entry order |
removeMapKey | Remove a single entry by key |
clearMap | Remove all entries from the map |
forEachMap | Loop over all entries, exposing Key and Value outputs per iteration |
setMap aliasing: assigning one map variable from another with setMap shares the underlying storage, matching the HTML runtime. After the assignment, a clearMap or setMapValue on one variable is observed through the other. Assignments from read-only sources such as runScript output ports snapshot the entries into a fresh map instead, so sub-script results never alias the caller's variables.
forEachMap iterates a snapshot of the map taken when the loop starts, so mutating the map inside the loop body does not affect the iteration. Each pass exposes the current entry's Key and Value outputs to connected nodes, integrated with the existing loop stack and continuation rules.
Maps in Dialogue Text
Map variables interpolate as an empty string in dialogue text. A {varname} token referencing a map renders nothing - read individual entries with getMapValue and interpolate those instead.
Reading Maps from GDScript
Added in v1.2.0. The StoryFlowComponent exposes get_map_variable(variable_name), which reads a script or global map variable and returns a Dictionary of key to StoryFlowVariant entries in insertion order. The returned dictionary and its values are copies, never the live storage, so game code cannot accidentally corrupt aliased variables.
- Keys are never localized - the runtime-wide map rule is that values localize and keys are identifiers. Keys arrive as raw
int(integer key type) orString(string and enum key types). - String and enum values are resolved through the string table for the component's
language_code, matchingget_array_variableelements. Image, audio and character values pass through unchanged as asset keys or character paths. - Missing or non-map variables return an empty
Dictionary. If the variable exists but is not a map, apush_warningis also emitted.
# Read a map variable: key -> StoryFlowVariant, in insertion order
var inventory: Dictionary = storyflow.get_map_variable("inventory")
for key in inventory:
var count: int = inventory[key].get_int()
print("%s x%d" % [key, count]) Typed Maps from GDScript
Added in v1.2.0. While get_map_variable hands back StoryFlowVariant values you unwrap yourself, the component also exposes a typed getter for each supported key/value combination. Each one validates the variable's key and value types up front and returns a Dictionary of native GDScript values - no StoryFlowVariant unwrapping needed.
The full key/value matrix collapses to native types: enum keys are stored as String, and string, enum, image, audio and character values all serialize to String. That leaves two key families (String, int) and four value families (bool, int, float, String), for eight typed getters:
| Function | Accepts Key Types | Accepts Value Types |
|---|---|---|
get_string_to_bool_map | string, enum | boolean |
get_string_to_int_map | string, enum | integer |
get_string_to_float_map | string, enum | float |
get_string_to_string_map | string, enum | string, enum, image, audio, character |
get_int_to_bool_map | integer | boolean |
get_int_to_int_map | integer | integer |
get_int_to_float_map | integer | float |
get_int_to_string_map | integer | string, enum, image, audio, character |
Every typed getter has the signature (variable_name: String, is_global: bool = false) and returns a Dictionary. The optional is_global flag restricts the lookup to global variables; leave it at its false default to use the standard local-first, then-global resolution. The same rules as get_map_variable apply to the entries:
- Keys are returned raw, never localized -
intfor integer-keyed maps,Stringfor string and enum-keyed maps. - String and enum values are resolved through the string table for the component's
language_code; image, audio and character values pass through as raw asset keys or character paths. - The returned dictionary is a copy of the live storage, so mutating it never corrupts the variable (or any other variable sharing its storage through setMap aliasing).
- A missing, non-map, wrong-key-type, or wrong-value-type variable emits a
push_warningand returns an emptyDictionary.
# A String-keyed, int-valued map - values arrive as native ints
var inventory: Dictionary = storyflow.get_string_to_int_map("inventory")
for item_name in inventory:
print("%s x%d" % [item_name, inventory[item_name]])
# An int-keyed, String-valued map - keys are native ints
var slot_labels: Dictionary = storyflow.get_int_to_string_map("slotLabels")
print(slot_labels[1])
# Restrict the lookup to a global variable with is_global = true
var flags: Dictionary = storyflow.get_string_to_bool_map("worldFlags", true) Reading Keys in Insertion Order
The returned Dictionary preserves the map's insertion order, which matches the editor's serialized order. To read just the keys in that order, call .keys() on the result - for example storyflow.get_string_to_int_map("inventory").keys(). Iterating the dictionary directly (as in the examples above) visits entries in the same order.
Setting Maps from GDScript
Added in v1.2.0. Each typed getter has a matching setter that replaces the variable's entries from a native Dictionary. Pass keys and values as plain GDScript types - the component wraps each value into a StoryFlowVariant typed as the variable's declared value type.
| Function | Native Key | Native Value |
|---|---|---|
set_string_to_bool_map | String | bool |
set_string_to_int_map | String | int |
set_string_to_float_map | String | float |
set_string_to_string_map | String | String (string, enum, image, audio, character) |
set_int_to_bool_map | int | bool |
set_int_to_int_map | int | int |
set_int_to_float_map | int | float |
set_int_to_string_map | int | String (string, enum, image, audio, character) |
Every typed setter has the signature (variable_name: String, values: Dictionary, is_global: bool = false) and returns void. Writes behave like every other variable change: the variable_changed signal is emitted, and if a dialogue is currently showing, its text live-refreshes. A few rules to keep in mind:
- String values are stored verbatim - no string-table key is created, so they bypass localization and are language-locked to whatever you wrote, matching the array setters.
- Image, audio and character values are asset keys or character paths - the same identifiers the read APIs return - because those value types serialize to a plain
Stringentry. - A missing, non-map, wrong-key-type, or wrong-value-type variable emits a
push_warningand is a no-op.
# Replace a String-keyed, int-valued map (emits variable_changed, live-refreshes dialogue)
storyflow.set_string_to_int_map("inventory", {
"sword": 1,
"potion": 3,
})
# Replace an int-keyed, String-valued map - values stored verbatim
storyflow.set_int_to_string_map("slotLabels", {
1: "Main Hand",
2: "Off Hand",
})
# Target a global map with is_global = true
storyflow.set_string_to_bool_map("worldFlags", { "bridgeRepaired": true }, true) Resetting Variables
The plugin provides functions 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.
| Function | Class | What It Resets |
|---|---|---|
reset_variables() | StoryFlowComponent | Resets local variables on this component to their initial values from the script definition |
reset_global_variables() | StoryFlowManager | Resets global variables to their project defaults |
reset_all_state() | StoryFlowManager | Resets global variables, runtime characters, and once-only option tracking - a full session reset |
# Reset just this component's local variables
storyflow.reset_variables()
# Reset global variables across all components
var manager = get_node("/root/StoryFlowRuntime")
manager.reset_global_variables()
# Full state reset (globals + characters + once-only options)
manager.reset_all_state() Reset Scope
reset_variables() 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 reset_all_state() on the manager and then reset_variables() on each active component.