
Building a Custom Story Graph Editor in Unity (Visual Narrative System)
August 31, 2026
Dynamic Quest Generator Using AI + Rule‑Based Systems (AAA Design + Unity Implementation)
September 1, 2026Modern narrative games increasingly blend authored dialogue with procedural, AI‑generated responses.
This allows for conversations that adapt to player choices, character personality, emotional tone, quest progress, and even long‑term narrative memory.
In this guide, we build a hybrid dialogue system where:
- Writers create core dialogue nodes
- The engine decides when to use AI generation
- AI expands, rewrites, or synthesizes responses dynamically
- Context and character personality are injected into prompts
- The system runs fully inside Unity
1. System Architecture
The dialogue engine contains three layers:
- Authored Layer (ScriptableObjects, story graph)
- Runtime Dialogue Engine (flow controller)
- AI Response Layer (prompt builder + LLM API)
A conversation can switch between fixed and AI‑generated lines depending on the node type.
Player → DialogueNode → (AI Response?) → NextNode
2. Dialogue Node Structure
We extend the StoryNode to support AI generation modes:
public enum DialogueMode
{
Authored, // Use writer‑authored text
AIGenerated, // Fully AI‑generated response
AIVariation, // Rewrite/expand existing text
AIChoice // AI generates multiple choices
}
[System.Serializable]
public class DialogueNodeData
{
public string speaker;
public string text;
public DialogueMode mode;
public string personalityTag; // "angry", "shy", "sarcastic"
public string contextHint; // "player betrayed him", etc.
}
3. AI Context Model
AI needs story context to generate consistent dialogue.
We define a context container:
public class DialogueContext
{
public string playerName;
public Dictionary<string, bool> flags = new Dictionary<string, bool>();
public List<string> conversationHistory = new List<string>();
public string questState;
}
4. Building the AI Prompt
We generate structured prompts at runtime:
public class AIPromptBuilder
{
public static string BuildPrompt(DialogueNodeData node, DialogueContext ctx)
{
return
$@"You are '{node.speaker}' speaking in a video game.
Personality: {node.personalityTag}
Context: {node.contextHint}
QuestState: {ctx.questState}
Conversation so far:
{string.Join("\n", ctx.conversationHistory)}
Task:
Mode = {node.mode}
If Authored: Return the line exactly as-is:
'{node.text}'
If AIVariation: Rewrite this line with more emotion but same meaning:
'{node.text}'
If AIGenerated: Generate a new line that fits the situation.
If AIChoice: Generate 3 different responses, numbered 1‑3.
Keep responses short, conversational and in‑character.";
}
}
5. AI Integration Layer
This layer calls your LLM provider (OpenAI, Gemini, local model, etc.).
Example using UnityWebRequest:
public class AIClient
{
public static async Task<string> QueryAsync(string prompt)
{
var body = new
{
model = "gpt-4o-mini",
messages = new[]{ new { role="user", content=prompt } }
};
var json = JsonUtility.ToJson(body);
using var req = new UnityEngine.Networking.UnityWebRequest("https://api.openai.com/v1/chat/completions", "POST");
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json);
req.uploadHandler = new UnityEngine.Networking.UploadHandlerRaw(bytes);
req.downloadHandler = new UnityEngine.Networking.DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json");
req.SetRequestHeader("Authorization", "Bearer " + APIKeys.OpenAI);
await req.SendWebRequest();
if (req.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
{
return ExtractText(req.downloadHandler.text);
}
return "[AI Error]";
}
static string ExtractText(string json)
{
// Very simplified JSON parser
return "\"choices\":[{\"message\":{\"content\":\""
.GetBetween(json, "content\":\"", "\"");
}
}
6. Dialogue Engine
The DialogueEngine handles:
- Node traversal
- Calling AI if needed
- Updating memory/context
public class DialogueEngine
{
private DialogueNodeData currentNode;
private DialogueContext context;
private StoryGraph graph;
public DialogueEngine(StoryGraph g, DialogueContext ctx)
{
graph = g;
context = ctx;
currentNode = graph.startNode.GetDialogueData();
}
public async Task<string> GetLine()
{
string line = currentNode.text;
// AI modes override authored text
if (currentNode.mode != DialogueMode.Authored)
{
string prompt = AIPromptBuilder.BuildPrompt(currentNode, context);
line = await AIClient.QueryAsync(prompt);
}
// store in history
context.conversationHistory.Add($"{currentNode.speaker}: {line}");
return line;
}
public void Choose(int index)
{
currentNode = currentNode.outputs[index].next.GetDialogueData();
}
}
7. AI Choices (Dynamic Branching)
If a node is set to AIChoice, the AI will return numbered options:
1. "What happened to you?" 2. "Calm down. Let's talk." 3. "You're lying."
We parse them into branching UI:
public List<string> ParseChoices(string aiText)
{
var lines = aiText.Split('\n');
return lines.Where(x => x.StartsWith("1.") || x.StartsWith("2.") || x.StartsWith("3."))
.Select(x => x.Substring(3))
.ToList();
}
8. Maintaining Character Consistency
AI is unpredictable unless guided. Best practices:
- Create a Character Profile ScriptableObject for every NPC
- Include personality, speech patterns, motivations
- Pass it to the AI prompt each time
- Feed conversation history, but capped (last 6‑10 lines)
9. Preventing AI From Breaking Story Logic
The biggest risk: AI says things that break quests.
Solutions:
- Hard rules inside prompt: “Do not reveal future events.”
- Inject flags: “Character does NOT know X.”
- Use AIVariation instead of full generation
- Limit AIChoice to in‑context safe outputs
- Keep critical story points authored
10. Example Help: Personality Templates
sarcastic → always slightly mocking, uses snarky tone noble → formal, heroic, self‑sacrificing tone timid → avoids confrontation, short sentences, soft voice angry → short, explosive lines, confrontational mentor → wise, guiding language, longer sentences
These templates can be referenced automatically by the prompt builder.
11. Advanced Features
- AI summaries to compress memory
- Personality drift over time
- Dynamic emotions (anger/fear/trust)
- Quest‑aware AI logic
- Two‑NPC AI conversations
- Procedural idle chatter systems
12. Summary
By combining authored nodes with AI‑generated responses, you create a dialogue system that is:
- Flexible
- Reactive
- Emotionally expressive
- Narratively robust
- Deeply replayable
This hybrid model preserves the structure of traditional branching narratives while enabling the dynamism of AI‑driven storytelling.









