Skip to main content

Characters

Characters bring your dialogue to life. This guide covers how character data flows from the StoryFlow Editor into Unreal Engine, how to display character information in your UI, and how to work with per-character variables at runtime.

Character Data Structure

Characters are defined in the StoryFlow Editor and exported as part of the JSON project in a characters.json file. The plugin represents characters with two complementary types: a data asset for editor-time configuration and a runtime struct for in-game use.

UStoryFlowCharacterAsset

UStoryFlowCharacterAsset is a UDataAsset that stores the imported character definition. Each character in your project becomes a separate asset in the content directory.

C++
UCLASS(BlueprintType)
class UStoryFlowCharacterAsset : public UDataAsset
{
    GENERATED_BODY()

public:
    // Display name (string table key for localization)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    FString Name;

    // Default portrait image (asset key resolved during import)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    FString Image;

    // Per-character variables (e.g., mood, clothing, relationship)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    TMap<FString, FStoryFlowVariable> Variables;

    // Normalized path used for lookups
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    FString CharacterPath;

    // Resolved asset references (character images, etc.)
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    TMap<FString, TSoftObjectPtr<UObject>> ResolvedAssets;
};

Asset vs Runtime Data

The UStoryFlowCharacterAsset holds the raw imported data with string keys and references. At runtime, the plugin resolves these into FStoryFlowCharacterData with actual loaded textures, localized strings, and typed variable values. You typically interact with the resolved runtime data, not the asset directly.

Runtime Character Data

When a dialogue node references a character, the plugin resolves the asset into an FStoryFlowCharacterData struct that contains everything your UI needs:

C++
USTRUCT(BlueprintType)
struct FStoryFlowCharacterData
{
    GENERATED_BODY()

    // Resolved character name (loaded from string table)
    UPROPERTY(BlueprintReadOnly)
    FString Name;

    // Loaded portrait texture (can be null if no image assigned)
    UPROPERTY(BlueprintReadOnly)
    UTexture2D* Image = nullptr;

    // Mutable character variables with typed values
    UPROPERTY(BlueprintReadOnly)
    TMap<FString, FStoryFlowVariant> Variables;
};

The Name field contains the final resolved string from the string table (not a key). The Image field is a loaded UTexture2D* pointer ready to display in a UMG Image widget. The Variables map holds the current runtime values of all per-character variables as FStoryFlowVariant values.

Importing Characters

Import Workflow

Characters are imported automatically when you import your StoryFlow project. You do not need to import characters separately. The import process works as follows:

  1. The importer reads characters.json from your exported project.
  2. Each character entry becomes a separate UStoryFlowCharacterAsset saved in your content directory under the StoryFlow project folder.
  3. Character names are registered in the project's string table for localization support.
  4. Character paths are normalized and stored for consistent runtime lookups.

Asset Resolution

Character images follow a specific resolution pattern that differs from script-level assets:

  • Character images (portraits defined in characters.json) are imported into the project-level ResolvedAssets on the UStoryFlowProjectAsset. These are shared across all scripts.
  • Script-specific assets (images referenced within individual .sfe files) are imported into each UStoryFlowScriptAsset's own ResolvedAssets map.

This separation ensures that character portraits are available globally regardless of which script is currently executing, while script-specific assets remain scoped to their respective scripts.

Re-importing Updates Characters

When you re-import your project, character assets are updated in place. New characters are created, existing characters are updated with any changes, and their resolved assets are refreshed. You do not need to delete old character assets manually.

Character Path Normalization

Critical: Path Normalization

Character paths must be normalized consistently. The plugin uses the following normalization rule:

C++
// Normalization rule: lowercase + forward slashes replaced with backslashes
FString NormalizedPath = CharacterPath.ToLower().Replace(TEXT("/"), TEXT("\\"));

This normalization is applied in two places:

  • During import (ParseCharacters): The character path is normalized before being stored in the asset map.
  • During runtime lookup (FindCharacter): The lookup path is normalized before searching the map.

If these two normalizations do not match, the character will not be found at runtime, resulting in empty name and image values in your dialogue. The plugin handles this automatically for all built-in operations, but if you are accessing characters directly from C++ code, you must normalize paths yourself before performing lookups.

Displaying Characters

Accessing Character Data

When the UStoryFlowComponent broadcasts a dialogue update, the FStoryFlowDialogueState struct includes the resolved character data for the current dialogue node. Access it through the Character field:

C++
// In your dialogue widget or handler
void UMyDialogueWidget::OnDialogueUpdated(const FStoryFlowDialogueState& State)
{
    // Character name (resolved from string table)
    FString SpeakerName = State.Character.Name;

    // Character portrait texture (may be null)
    UTexture2D* Portrait = State.Character.Image;

    // Character-specific variables
    TMap<FString, FStoryFlowVariant> CharVars = State.Character.Variables;

    // Update your UI
    SpeakerNameText->SetText(FText::FromString(SpeakerName));

    if (Portrait)
    {
        PortraitImage->SetBrushFromTexture(Portrait);
        PortraitImage->SetVisibility(ESlateVisibility::Visible);
    }
    else
    {
        PortraitImage->SetVisibility(ESlateVisibility::Collapsed);
    }
}

In Blueprints, break the DialogueState struct to access the Character field, then break that to reach Name, Image, and Variables.

Character Portraits

The Character.Image field is a UTexture2D* that can be used directly with UMG Image widgets. Keep in mind:

  • The image can be null if no portrait was assigned to the character in the StoryFlow Editor. Always check before using it.
  • Portrait textures 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 texture reference is reused — there is no redundant loading.

Character Name 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:

Code
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. 1. Resolve character — Look up the character asset, load the name from the string table, load the portrait texture.
  2. 2. Update CurrentDialogueState.Character — Store the resolved character data on the dialogue state.
  3. 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.

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 Unreal, these become entries in the character's Variables map.

Reading Character Variables

Use GetCharacterVariable on the UStoryFlowComponent to read a character variable by path and name:

C++
// Read a character variable from the StoryFlow component
UStoryFlowComponent* StoryFlow = FindComponentByClass<UStoryFlowComponent>();

FStoryFlowVariant MoodValue = StoryFlow->GetCharacterVariable(
    TEXT("characters/elena"),  // Character path
    TEXT("mood")               // Variable name
);

// Use the value (FStoryFlowVariant can hold string, int, float, bool, etc.)
FString Mood = MoodValue.GetString();

In Blueprints, search for Get Character Variable on the StoryFlow Component node. It returns an FStoryFlowVariant that you can convert to the expected type.

Writing Character Variables

Use SetCharacterVariable to update a character variable from your game code:

C++
UStoryFlowComponent* StoryFlow = FindComponentByClass<UStoryFlowComponent>();

// Set a character variable
FStoryFlowVariant NewMood;
NewMood.SetString(TEXT("angry"));

StoryFlow->SetCharacterVariable(
    TEXT("characters/elena"),  // Character path
    TEXT("mood"),              // Variable name
    NewMood                    // 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.

Runtime Persistence

Character variable state at runtime is managed by the UStoryFlowSubsystem:

  • Mutable runtime copies: When the subsystem initializes, it creates mutable copies of all character variables in UStoryFlowSubsystem::RuntimeCharacters. These copies are what GetCharacterVariable and SetCharacterVariable operate on.
  • Cross-dialogue persistence: Changes to character variables persist across different dialogue sessions and script transitions within the same game session. If Elena's mood is set to "angry" in one dialogue, it remains "angry" when another dialogue references her.
  • Resetting state: Call ResetRuntimeCharacters() on the subsystem 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.
C++
// Reset all character variables to their imported defaults
UStoryFlowSubsystem* Subsystem = GetGameInstance()->GetSubsystem<UStoryFlowSubsystem>();
Subsystem->ResetRuntimeCharacters();

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.

Need Help?

Join our Discord community to ask questions, share your projects, report bugs, and get support from the team and other users.

Join Discord