Live Sync
Live Sync connects Unreal Engine to the StoryFlow Editor via WebSocket, letting you edit your project and see changes reflected in the engine in real time — no manual re-export and re-import after every change.
Overview
Live Sync establishes a WebSocket connection between the Unreal Editor and the StoryFlow Editor running on the same machine. When you save changes in the StoryFlow Editor, those changes are automatically pushed to Unreal and re-imported, keeping your in-engine project data up to date without any manual steps.
- Real-time synchronization — edit in StoryFlow, see results in Unreal immediately
- WebSocket-based communication using
ws://localhost:9000by default - Automatic re-import of the full project when changes are detected
- Blueprint and C++ accessible through
UStoryFlowEditorSubsystem
Editor-Only Feature
Live Sync is provided by UStoryFlowEditorSubsystem, which lives in the StoryFlowEditor module. It is only available in the Unreal Editor and is not included in packaged builds. This is intentional — Live Sync is a development workflow tool, not a runtime feature.
Architecture
Live Sync is built from three components that work together to manage the connection and synchronization lifecycle:
- UStoryFlowEditorSubsystem (UEditorSubsystem) — The main interface you interact with from Blueprint or C++. It exposes connection management, sync requests, and import functions. As an editor subsystem, it is automatically created and destroyed with the editor.
- FStoryFlowWebSocketClient — Handles the low-level WebSocket connection, including establishing the connection, reconnection logic (up to 5 attempts), the initial handshake with the StoryFlow Editor, and ping/keep-alive messages to maintain the connection.
- FStoryFlowSyncManager — Manages the synchronization workflow. It receives incoming project update messages from the WebSocket client, coordinates the re-import process through
UStoryFlowImporter, and fires completion delegates.
Setup
Getting Live Sync running takes just a few steps. Make sure both the StoryFlow Editor and Unreal Editor are open on the same machine.
Step 1: Open your project in the StoryFlow Editor
The StoryFlow Editor runs a sync server that listens for incoming WebSocket connections. Open the project you want to synchronize — the server starts automatically.
Step 2: Connect from Unreal
Call ConnectToStoryFlow() on the editor subsystem. You can do this from a Blueprint utility widget, an editor toolbar button, or C++ code.
// Connect with default settings (localhost:9000)
UStoryFlowEditorSubsystem* Subsystem =
GEditor->GetEditorSubsystem<UStoryFlowEditorSubsystem>();
if (Subsystem)
{
Subsystem->ConnectToStoryFlow();
} If you need to use a different host or port, pass them as parameters:
// Connect with custom host and port
Subsystem->ConnectToStoryFlow(TEXT("localhost"), 9000); Step 3: Request an initial sync
After connecting, call RequestSync() to pull the full project from the editor. This performs an initial import so your Unreal project matches the current state of the StoryFlow Editor.
// Request a full project sync
Subsystem->RequestSync(); Step 4: Edit and iterate
From this point on, any changes you save in the StoryFlow Editor are automatically pushed to Unreal. You do not need to call RequestSync() again unless you want to force a full refresh.
Blueprint Setup
You can call ConnectToStoryFlow and RequestSync from Blueprint as well. Get the editor subsystem using the Get Editor Subsystem node with UStoryFlowEditorSubsystem as the class, then call the functions directly from your event graph.
Connection Functions
The following functions are available on UStoryFlowEditorSubsystem and are all BlueprintCallable:
-
ConnectToStoryFlow(FString Host = "localhost", int32 Port = 9000)— Establishes a WebSocket connection to the StoryFlow Editor's sync server. Both parameters are optional and default tolocalhostand9000respectively. -
Disconnect()— Closes the active WebSocket connection and stops any reconnection attempts. -
IsConnected() -> bool(BlueprintPure) — Returns whether the WebSocket connection is currently active. -
RequestSync()— Sends a request to the StoryFlow Editor for a full project sync. The editor responds with the complete project data, which triggers a re-import.
Import Functions
The editor subsystem also provides direct import functions that Live Sync uses internally. You can call these yourself for manual imports outside of the sync workflow:
-
ImportProject(FString BuildDirectory, FString ContentPath = "/Game/StoryFlow") -> UStoryFlowProjectAsset*— Imports an entire StoryFlow project from a build directory. Returns the created project asset. -
ImportScript(FString JsonPath, FString ContentPath = "/Game/StoryFlow/Data") -> UStoryFlowScriptAsset*— Imports a single script file from a JSON path. Returns the created script asset. -
SetContentPath(FString Path)— Sets the content directory where imported assets are placed. -
GetContentPath() -> FString— Returns the current content path. -
GetProjectAsset() -> UStoryFlowProjectAsset*— Returns the most recently imported project asset.
Events and Delegates
The editor subsystem exposes three delegates you can bind to for responding to connection and sync events:
-
OnConnected— Fires when the WebSocket connection to the StoryFlow Editor is successfully established. -
OnDisconnected— Fires when the connection is lost, whether due to the editor closing, a network issue, or an explicitDisconnect()call. -
OnSyncComplete(UStoryFlowProjectAsset* Project)— Fires when a full project sync finishes. The parameter is the newly imported project asset, ready to be used by yourStoryFlowComponentinstances.
// Binding to Live Sync events in C++
UStoryFlowEditorSubsystem* Subsystem =
GEditor->GetEditorSubsystem<UStoryFlowEditorSubsystem>();
if (Subsystem)
{
Subsystem->OnConnected.AddDynamic(
this, &AMyEditorActor::HandleConnected);
Subsystem->OnDisconnected.AddDynamic(
this, &AMyEditorActor::HandleDisconnected);
Subsystem->OnSyncComplete.AddDynamic(
this, &AMyEditorActor::HandleSyncComplete);
}
void AMyEditorActor::HandleConnected()
{
UE_LOG(LogTemp, Log, TEXT("StoryFlow Live Sync connected"));
}
void AMyEditorActor::HandleDisconnected()
{
UE_LOG(LogTemp, Warning, TEXT("StoryFlow Live Sync disconnected"));
}
void AMyEditorActor::HandleSyncComplete(UStoryFlowProjectAsset* Project)
{
UE_LOG(LogTemp, Log, TEXT("StoryFlow project synced: %s"),
*Project->GetName());
} How It Works Internally
Understanding the internal flow helps when debugging sync issues or extending the system:
- Connection —
FStoryFlowWebSocketClientestablishes a WebSocket connection to the StoryFlow Editor's sync server at the specified host and port. - Handshake — On connection, the client sends a handshake message to identify itself to the editor. The editor acknowledges and begins monitoring for project changes.
- Change detection — When you save changes in the StoryFlow Editor, it pushes the updated project data over the WebSocket as a JSON message.
- Sync manager —
FStoryFlowSyncManagerreceives the incoming data and callsHandleProjectUpdated(), which writes the data to temporary files and triggers a full project re-import throughUStoryFlowImporter. - Re-import — The importer processes all project files (scripts, characters, assets) and creates or updates the corresponding Unreal assets in the content path.
- Completion — The
OnSyncCompletedelegate fires with the newUStoryFlowProjectAsset*, notifying any listeners that the project has been updated. - Component update — Active
StoryFlowComponentinstances automatically pick up the updated project asset through the editor subsystem, so running dialogues can reflect the latest changes.
Reconnection
If the WebSocket connection drops unexpectedly — for example, if the StoryFlow Editor is restarted or the connection is interrupted — the client automatically attempts to reconnect.
- Up to 5 reconnection attempts are made with increasing delay between attempts
- Connection state changes are broadcast through
FStoryFlowWebSocketClientdelegates UStoryFlowEditorSubsystemtranslates these internal delegates into its ownOnConnectedandOnDisconnecteddelegates for your code to respond to- If all reconnection attempts fail, the
OnDisconnecteddelegate fires and you will need to callConnectToStoryFlow()again manually
Monitoring Connection Status
Use IsConnected() to check the current connection state at any time. You can also bind to OnConnected and OnDisconnected to update a status indicator in your editor UI so you always know whether Live Sync is active.
Default Content Path
All assets imported through Live Sync are placed under a configurable content path. The default is /Game/StoryFlow.
- Scripts are imported to
/Game/StoryFlow/Data/ - Character assets go to
/Game/StoryFlow/Characters/ - The project asset is created at
/Game/StoryFlow/
To change the content path, call SetContentPath() before connecting or requesting a sync:
UStoryFlowEditorSubsystem* Subsystem =
GEditor->GetEditorSubsystem<UStoryFlowEditorSubsystem>();
if (Subsystem)
{
// Change where imported assets are placed
Subsystem->SetContentPath(TEXT("/Game/MyGame/Dialogue"));
Subsystem->ConnectToStoryFlow();
Subsystem->RequestSync();
} Limitations
There are a few limitations to be aware of when using Live Sync:
- Editor-only — Live Sync lives in the
StoryFlowEditormodule and is not available in packaged builds. It is strictly a development workflow tool. - Local only — The default connection targets
localhost, which requires the StoryFlow Editor to be running on the same machine as the Unreal Editor. Remote connections are not officially supported. - Full re-import — Each sync performs a complete re-import of the entire project rather than incremental updates. For very large projects, this may take a moment, but for most projects the import is near-instant.