Characters
Characters bring your dialogue to life. This guide covers how character data flows from the StoryFlow Editor into Unity, how to display character information in your UI, and how to work with per-character variables at runtime.
Character Data
Characters are defined in the StoryFlow Editor and exported as part of the JSON project in a characters.json file. At runtime, the plugin resolves character data into a StoryFlowCharacterData object that contains everything your UI needs:
public class StoryFlowCharacterData
{
// Resolved character name
public string Name;
// Asset key for the portrait image (before resolution)
public string ImageAssetKey;
// Character portrait (can be null if no image assigned)
public Sprite Image;
// Mutable character variables - quick-lookup dictionary
public Dictionary<string, StoryFlowVariant> Variables;
// Deep-copied list of character variables for mutation and save/load
public List<StoryFlowVariable> VariablesList;
}
The Name field contains the resolved display string. The Image field is a loaded Sprite ready to display in a UI Image component. The Variables dictionary holds the current runtime values of all per-character variables as StoryFlowVariant values.
Runtime Data is a Deep Copy
When the StoryFlowManager initializes, it creates deep copies of all character data from the imported StoryFlowCharacterAsset ScriptableObjects into StoryFlowCharacterData objects. These runtime copies are what GetCharacterVariable, SetCharacterVariable, and the dialogue system operate on. The original asset data is never mutated, so re-importing or resetting always returns to clean defaults.
Accessing Character Info
From Dialogue State
When the StoryFlowComponent broadcasts a dialogue update, the StoryFlowDialogueState includes the resolved character data for the current dialogue node. Access it through the Character field:
void OnDialogueUpdated(StoryFlowDialogueState state)
{
bool hasCharacter = state.Character != null
&& !string.IsNullOrEmpty(state.Character.Name);
if (hasCharacter)
{
// Character name
nameText.text = state.Character.Name;
// Character portrait (may be null)
if (state.Character.Image != null)
{
portrait.sprite = state.Character.Image;
portrait.gameObject.SetActive(true);
}
else
{
portrait.gameObject.SetActive(false);
}
}
else
{
// No character assigned to this dialogue node
nameText.text = "";
portrait.gameObject.SetActive(false);
}
}
Subscribe to the OnDialogueUpdated event to receive the state:
void Start()
{
var sf = GetComponent<StoryFlowComponent>();
sf.OnDialogueUpdated += OnDialogueUpdated;
}
void OnDestroy()
{
var sf = GetComponent<StoryFlowComponent>();
if (sf != null)
sf.OnDialogueUpdated -= OnDialogueUpdated;
} Accessing Characters Directly
Sometimes you need character data for a character who is not the current speaker. A party roster, a relationship screen, a character select menu or any UI that reasons about characters outside the dialogue flow falls into this bucket. The component exposes three methods for this case:
GetCharacter(characterPath)returns the liveStoryFlowCharacterDatafor a path, ornullif no character is registered there. Use this when game code needs to read several fields without making oneGetCharacterVariablecall per field.GetCharacterVariables(characterPath)returns the names of the custom variables defined on a character. The built-inNameandImagefields are excluded from this list because they are always reachable throughGetCharacterVariableregardless of whether the character declares them.GetCharacterPortrait(characterPath, assetKey = "")returns aSpritefor the character's portrait. This is the officially blessed way to fetch a portrait for an arbitrary character when you are not the current speaker.
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Read everything about a character in one call
StoryFlowCharacterData elder = sf.GetCharacter("characters/elder");
if (elder != null)
{
Debug.Log($"Elder name: {elder.Name}, image key: {elder.ImageAssetKey}");
}
// Enumerate the custom variables that exist on this character
List<string> customVars = sf.GetCharacterVariables("characters/elder");
foreach (string varName in customVars)
{
StoryFlowVariant value = sf.GetCharacterVariable("characters/elder", varName);
Debug.Log($" {varName} = {value}");
}
// Resolve the current portrait Sprite for a non-speaker character
Sprite portrait = sf.GetCharacterPortrait("characters/elder");
if (portrait != null)
rosterImage.sprite = portrait; Prefer GetCharacterPortrait for non-speakers
For the current speaker, read the portrait from state.Character.Image on the dialogue state. For any other character, GetCharacterPortrait is the right call because it walks the full asset cascade (character resolved assets, then script, then project) using the character's current ImageAssetKey, so it picks up runtime setCharacterVar mutations to the Image field.
Character Portraits
The Character.Image field is a Sprite that can be used directly with Unity UI Image components. Keep in mind:
- The image can be
nullif no portrait was assigned to the character in the StoryFlow Editor. Always check before using it. - Portrait sprites are loaded during character resolution, so they are ready to display immediately when the dialogue state is broadcast.
- If the same character appears across multiple dialogues, the same sprite reference is reused - there is no redundant loading.
For UI that displays a portrait outside the active dialogue, like a party roster, a relationship screen or a character select carousel, use GetCharacterPortrait(characterPath) on the component. The method returns a Sprite directly, not a Texture2D, so it drops into any UGUI Image without conversion. Passing an empty assetKey resolves the character's current portrait, which reflects any runtime setCharacterVar mutations to the Image field. Pass a custom key to swap to an alternate pose stored in an image-typed character variable.
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Current portrait for a roster slot
Sprite current = sf.GetCharacterPortrait("characters/elder");
// Alternate pose stored on an image-typed character variable
Sprite cheering = sf.GetCharacterPortrait("characters/elder", "elder_cheer"); Character Interpolation
StoryFlow supports inserting character data directly into dialogue text using curly brace syntax. This allows you to write dialogue that dynamically includes the speaker's name or custom variable values.
{Character.Name}- Inserts the current character's resolved name.{Character.VarName}- Inserts the value of a character variable (e.g.,{Character.mood}or{Character.title}).
For example, a dialogue node with the text:
My name is {Character.Name} and I'm feeling {Character.mood} today. Would render as something like: "My name is Elena and I'm feeling cheerful today."
Resolution Order Matters
Character interpolation depends on a specific internal resolution order. The plugin processes each dialogue node in this exact sequence:
- 1. Resolve character - Look up the character data, load the name, load the portrait sprite.
- 2. Update CurrentDialogueState.Character - Store the resolved character data on the dialogue state.
- 3. Interpolate text - Process
{Character.Name}and other interpolation tokens, reading from the now-populated character data.
This order is critical. If text interpolation happened before character resolution, {Character.Name} would resolve to an empty string. The plugin handles this order internally, so you do not need to manage it yourself. This note is for understanding why the order matters if you encounter debugging scenarios.
Per-Character Variables
Characters can have custom variables attached to them, defined in the StoryFlow Editor. These are ideal for per-character state like mood, relationship level, outfit, or any other property that should travel with the character rather than live as a global variable.
In the StoryFlow Editor, character variables are defined on the character file and manipulated with getCharacterVar and setCharacterVar nodes. At runtime in Unity, these become entries in the character's Variables dictionary.
Per-character variables cannot be map-typed in this release - the editor disables the option pending the upcoming character redesign, which will bring maps to characters. Array-typed character variables now work correctly from the graph: v1.2.0 fixes setCharacterVar silently dropping writes to array character variables and ignoring wired image, audio and character values.
Reading character variables:
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Read a character variable by character path and variable name
StoryFlowVariant trustValue = sf.GetCharacterVariable(
"characters/elder", // Character path
"trustLevel" // Variable name
);
// Use the value (StoryFlowVariant can hold string, int, float, bool, etc.)
int trust = trustValue?.GetInt() ?? 0;
The built-in Name and Image fields are now symmetric with custom variables when read through GetCharacterVariable. Both keys are case-insensitive and always available, even when a character has no custom variables declared:
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Built-in display name as a StoryFlowVariant
StoryFlowVariant nameVariant = sf.GetCharacterVariable("characters/elder", "Name");
string displayName = nameVariant?.GetString() ?? "";
// Built-in image asset key as a StoryFlowVariant
StoryFlowVariant imageVariant = sf.GetCharacterVariable("characters/elder", "Image");
string currentImageKey = imageVariant?.GetString() ?? ""; Writing character variables:
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// Set a character variable
sf.SetCharacterVariable(
"characters/elder", // Character path
"trustLevel", // Variable name
StoryFlowVariant.Int(5) // New value
);
Changes made with SetCharacterVariable take effect immediately. The next time dialogue text is interpolated or a getCharacterVar node runs, it will see the updated value.
Character Variables vs Global Variables
Use character variables for state that conceptually belongs to a character (mood, relationship, outfit, health). Use global variables for state that belongs to the story or world (quest progress, time of day, world flags). This separation keeps your data model clean and makes it easy to reason about where state lives.
Reacting to Character Mutations
Mutations from setCharacterVar nodes and from SetCharacterVariable calls fire OnCharacterVariableChanged on the component with the payload (characterPath, variableName, value). This fires for built-in Name and Image mutations as well as for custom variables. Prefer it over the generic OnVariableChanged event when you only care about character-scoped reactions, because the generic event does not carry the character path.
Character variable state at runtime is managed by the StoryFlowManager:
- Mutable runtime copies: When the manager initializes, it creates mutable copies of all character variables in
StoryFlowManager.RuntimeCharacters. These copies are whatGetCharacterVariableandSetCharacterVariableoperate on. - Cross-dialogue persistence: Changes to character variables persist across different dialogue sessions and script transitions within the same game session. If the elder's trust is set to 5 in one dialogue, it remains 5 when another dialogue references that character.
- Resetting state: Call
ResetRuntimeCharacters()on the manager to restore all character variables to their original imported values. This is useful when starting a new game or resetting story state. - Save/Load integration: Runtime character variable state is included when using the plugin's Save & Load system. Saved character state is fully restored when loading a save slot.
// Reset all character variables to their imported defaults
StoryFlowManager.Instance.ResetRuntimeCharacters(); Working with Character Arrays
A character array is a script or global variable whose element type is character. The runtime stores each element as the character's normalized path string, which means you can iterate the array from C# and feed each path directly into GetCharacter, GetCharacterVariable or GetCharacterPortrait. This is the canonical pattern for rendering a multi-character cast in your UI, for example a party roster, a relationship screen or an active-companions HUD.
Read a character array with GetCharacterArrayVariable(variableName). It returns a List<string> of character paths in their stored order. Pair it with GetCharacterPortrait to render every member:
StoryFlowComponent sf = GetComponent<StoryFlowComponent>();
// "party" is a script or global variable of type character-array
List<string> party = sf.GetCharacterArrayVariable("party");
foreach (string charPath in party)
{
Sprite portrait = sf.GetCharacterPortrait(charPath);
StoryFlowCharacterData data = sf.GetCharacter(charPath);
var slot = Instantiate(partySlotPrefab, partyRoot);
slot.SetPortrait(portrait);
slot.SetName(data?.Name ?? "");
} Character Array vs Character Variable
GetCharacterArrayVariable reads a script or global variable whose element type is character. GetCharacterVariable reads a variable that lives on a character. They sound similar but they look at different stores, so do not confuse them. The character array call also logs a warning if the variable is missing, is not an array or is not a character array, which makes mismatches easy to spot in the Console.
Character Assets
StoryFlowCharacterAsset is a ScriptableObject that stores the imported character definition. Each character in your project becomes a separate asset in the Unity project.
public class StoryFlowCharacterAsset : ScriptableObject
{
// Display name
public string CharacterName;
// Normalized path used for lookups
public string CharacterPath;
// Default portrait image key (resolved during import)
public string ImageAssetKey;
// Resolved portrait sprite
public Sprite ResolvedImage;
// Per-character variables
public List<StoryFlowVariable> Variables;
}
Characters are imported automatically when you import your StoryFlow project. You do not need to import characters separately. The import process reads characters.json from your exported project and creates or updates a StoryFlowCharacterAsset for each character.
Path Normalization
Character paths are normalized internally for consistent lookups. The normalization rule converts the path to lowercase and replaces backslashes with forward slashes. Both the import process and the runtime lookup apply the same normalization. If you are accessing characters directly by path from C# code, the plugin handles normalization for you in GetCharacterVariable and SetCharacterVariable.
If you bypass the public API and access RuntimeCharacters directly, you must normalize paths yourself to avoid lookup failures that result in empty character data.