
Real‑Time Volumetric Cloud Rendering in Unity Using Raymarching
August 29, 2026
Creating a Dynamic Dialogue Engine with AI‑Driven Responses in Unity
September 1, 2026A Story Graph Editor is a visual tool used to create branching dialogue, quests, narrative logic, and complex story flows.
Games such as Detroit: Become Human, Life is Strange, and Disco Elysium rely heavily on story graphs to structure narrative progression.
In this guide, we will create a custom graph-based narrative editor inside Unity using:
- ScriptableObjects to store conversation/quest data
- A custom editor window for graph visualization
- Nodes and ports (inputs/outputs)
- Connectable edges
- Story evaluation at runtime
1. What is a Story Graph?
A story graph is a node-based system where each node represents a narrative event:
- Dialogue line
- Choice
- Condition check
- Quest update
- Cutscene trigger
Edges between nodes control story progression.
Start → Dialogue → Player Choice → Branch → Merge → End
2. Story Node Data (ScriptableObject)
Each story node needs:
- ID
- Node type (Dialogue, Choice, Event, Condition, etc.)
- Text
- Outputs (links to other nodes)
Here is a minimal ScriptableObject node:
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(menuName="Story/Node")]
public class StoryNode : ScriptableObject
{
public string nodeID;
[TextArea]
public string text;
public Vector2 editorPosition;
public List<StoryOutput> outputs = new List<StoryOutput>();
}
[System.Serializable]
public class StoryOutput
{
public string label;
public StoryNode next;
}
3. Story Graph Container
The entire story is stored inside a single StoryGraph asset:
[CreateAssetMenu(menuName="Story/Graph")]
public class StoryGraph : ScriptableObject
{
public StoryNode startNode;
public List<StoryNode> nodes = new List<StoryNode>();
}
4. Creating an Editor Window
We now create a custom window for editing the graph visually.
using UnityEditor;
using UnityEngine;
public class StoryGraphEditor : EditorWindow
{
StoryGraph graph;
Vector2 drag;
Vector2 offset;
[MenuItem("Tools/Story Graph Editor")]
public static void OpenWindow()
{
StoryGraphEditor window = GetWindow<StoryGraphEditor>();
window.titleContent = new GUIContent("Story Graph");
}
void OnGUI()
{
if (graph == null)
{
GUILayout.Label("No graph selected");
}
else
{
DrawGrid(20, 0.2f, Color.gray);
DrawGrid(100, 0.4f, Color.gray);
DrawNodes();
DrawConnections();
DrawToolbar();
ProcessEvents(Event.current);
}
if (GUI.changed) Repaint();
}
}
5. Drawing a Grid Background
Graph windows typically include a panning grid background.
void DrawGrid(float gridSpacing, float gridOpacity, Color gridColor)
{
int width = Mathf.CeilToInt(position.width / gridSpacing);
int height = Mathf.CeilToInt(position.height / gridSpacing);
Handles.BeginGUI();
Handles.color = new Color(gridColor.r, gridColor.g, gridColor.b, gridOpacity);
offset += drag * 0.5f;
for (int i = 0; i < width; i++)
{
Handles.DrawLine(new Vector3(gridSpacing * i, 0), new Vector3(gridSpacing * i, position.height));
}
for (int j = 0; j < height; j++)
{
Handles.DrawLine(new Vector3(0, gridSpacing * j), new Vector3(position.width, gridSpacing * j));
}
Handles.color = Color.white;
Handles.EndGUI();
}
6. Node Rendering (Draggable Boxes)
Each node is drawn as a draggable box:
void DrawNodes()
{
foreach (var node in graph.nodes)
{
GUI.Box(new Rect(node.editorPosition, new Vector2(200, 80)), node.text);
// Make node draggable
if (Event.current.type == EventType.MouseDrag &&
new Rect(node.editorPosition, new Vector2(200, 80)).Contains(Event.current.mousePosition))
{
node.editorPosition += Event.current.delta;
GUI.changed = true;
}
}
}
7. Drawing Connections Between Nodes
Connections are Bezier curves:
void DrawConnections()
{
foreach (var node in graph.nodes)
{
foreach (var output in node.outputs)
{
if (output.next != null)
{
Vector3 start = node.editorPosition + new Vector2(200, 40);
Vector3 end = output.next.editorPosition + new Vector2(0, 40);
Handles.DrawBezier(
start,
end,
start + Vector3.right * 50,
end + Vector3.left * 50,
Color.white,
null,
3
);
}
}
}
}
8. Creating & Linking Nodes
Add a toolbar to create/remove nodes:
void DrawToolbar()
{
GUILayout.BeginArea(new Rect(10, 10, 200, 200));
if (GUILayout.Button("Add Node"))
{
CreateNode();
}
GUILayout.EndArea();
}
void CreateNode()
{
StoryNode node = ScriptableObject.CreateInstance<StoryNode>();
node.nodeID = System.Guid.NewGuid().ToString();
node.editorPosition = Vector2.zero;
graph.nodes.Add(node);
AssetDatabase.AddObjectToAsset(node, graph);
AssetDatabase.SaveAssets();
}
Linking is done by selecting an output and clicking another node.
Implementation is simple:
StoryNode pendingLink;
void ProcessEvents(Event e)
{
if (e.type == EventType.MouseDown && e.button == 0)
{
foreach (var node in graph.nodes)
{
Rect r = new Rect(node.editorPosition, new Vector2(200, 80));
if (r.Contains(e.mousePosition))
{
if (pendingLink == null)
pendingLink = node;
else
{
pendingLink.outputs.Add(new StoryOutput{ next = node });
pendingLink = null;
}
}
}
}
}
9. Evaluating the Story at Runtime
Runtime evaluation is extremely simple:
public class StoryPlayer
{
StoryNode current;
public StoryPlayer(StoryGraph graph)
{
current = graph.startNode;
}
public StoryNode GetCurrentNode()
{
return current;
}
public void Choose(int index)
{
current = current.outputs[index].next;
}
}
This creates a fully working dialogue/quest branching system.
10. Extensions (AAA Narrative Tools)
- Choice conditions (inventory, stats, flags)
- Dialogue audio previews
- Tags and quest states
- Cutscene triggers
- Localization support
- Play mode simulation inside the editor
- Minimap overview of the graph
- Comment boxes and color coding
- Auto-arrange graph layout
These additions turn the tool into something close to Articy Draft or Ink.
11. Summary
A custom Story Graph Editor is an essential tool for narrative-driven games.
By building it with ScriptableObjects and a custom editor window, you gain full control over:
- Dialogue systems
- Quest trees
- Cinematic triggers
- Branching choices
- Complex narrative states
This system is extendable, designer-friendly, and integrates seamlessly with Unity’s asset workflow.








