Unity Integration Guide
Import AI-generated dialogue trees into your Unity project and wire them up to your game's conversation system.
Overview
The AI Story Engine exports dialogue as a DialogueTree JSON structure
designed to map directly to Unity's data model. Each tree contains nodes with
speaker text, player choices, conditions, and branching logic.
You have two integration options:
- Static export — Export dialogue JSON at build time and bundle it as a TextAsset
- Runtime generation — Call the API at runtime for dynamic, session-aware dialogue (Pro plan)
DialogueTree JSON Schema
The export endpoint (GET /v1/projects/{project_id}/export/unity) returns
a JSON object conforming to the
unity_dialogue_export.json
schema. Here is an example:
{
"version": "1.0",
"npc_id": "npc_x7y8z9",
"npc_name": "Greta Ironforge",
"session_id": "session_001",
"root_node": "node_001",
"nodes": {
"node_001": {
"type": "npc_line",
"speaker": "Greta Ironforge",
"text": "Well, well. Another adventurer looking for trouble. What'll it be?",
"emotion": "amused_skepticism",
"audio_hint": "gruff_female_alto",
"next": "node_002"
},
"node_002": {
"type": "player_choice",
"choices": [
{
"text": "I heard there's something hidden beneath this tavern.",
"next": "node_003",
"condition": null
},
{
"text": "Just an ale, thanks.",
"next": "node_004",
"condition": null
},
{
"text": "[Persuasion] You look like someone with stories. Share one?",
"next": "node_005",
"condition": {
"stat": "charisma",
"min_value": 14
}
}
]
},
"node_003": {
"type": "npc_line",
"speaker": "Greta Ironforge",
"text": "Keep your voice down. The walls have ears in this town. Come back after closing — alone.",
"emotion": "guarded_concern",
"audio_hint": "whispered_urgent",
"event": "quest_hook_hidden_passage",
"next": null
},
"node_004": {
"type": "npc_line",
"speaker": "Greta Ironforge",
"text": "Smart choice. Two coppers.",
"emotion": "neutral_approval",
"audio_hint": "gruff_female_alto",
"next": null
},
"node_005": {
"type": "npc_line",
"speaker": "Greta Ironforge",
"text": "Ha! Flattery won't get you far here. But since you asked nicely... sit down. I'll tell you about the Siege of Ashenmoor.",
"emotion": "warming_nostalgic",
"audio_hint": "softened_reminiscent",
"event": "lore_unlock_ashenmoor",
"next": null
}
},
"metadata": {
"generated_at": "2026-03-13T10:05:00Z",
"kg_seq": 42,
"node_count": 5,
"max_depth": 2
}
}
Node Types
| Type | Description | Key Fields |
|---|---|---|
npc_line |
NPC speaks a line of dialogue | speaker, text, emotion, audio_hint, next |
player_choice |
Player selects from options | choices[] (each with text, next, condition) |
event |
Trigger a game event (no visible dialogue) | event, next |
condition |
Branch based on game state | condition, if_true, if_false |
C# Loader
The following C# classes load the DialogueTree JSON into Unity. Drop these into
your project's Scripts/Dialogue/ folder.
Data Classes
using System;
using System.Collections.Generic;
using UnityEngine;
namespace AIStoryEngine
{
[Serializable]
public class DialogueTree
{
public string version;
public string npc_id;
public string npc_name;
public string session_id;
public string root_node;
public Dictionary<string, DialogueNode> nodes;
public DialogueMetadata metadata;
}
[Serializable]
public class DialogueNode
{
public string type; // "npc_line", "player_choice", "event", "condition"
public string speaker;
public string text;
public string emotion;
public string audio_hint;
public string next; // next node ID, null if terminal
public string @event; // game event to trigger
public List<DialogueChoice> choices;
}
[Serializable]
public class DialogueChoice
{
public string text;
public string next;
public DialogueCondition condition;
}
[Serializable]
public class DialogueCondition
{
public string stat;
public int min_value;
}
[Serializable]
public class DialogueMetadata
{
public string generated_at;
public int kg_seq;
public int node_count;
public int max_depth;
}
}
Loader Component
using UnityEngine;
using Newtonsoft.Json;
namespace AIStoryEngine
{
public class DialogueLoader : MonoBehaviour
{
[SerializeField]
private TextAsset dialogueJson;
private DialogueTree _tree;
private DialogueNode _currentNode;
void Start()
{
if (dialogueJson != null)
{
LoadDialogue(dialogueJson.text);
}
}
/// <summary>
/// Load a DialogueTree from JSON string.
/// Works with both TextAsset content and API response bodies.
/// </summary>
public void LoadDialogue(string json)
{
_tree = JsonConvert.DeserializeObject<DialogueTree>(json);
if (_tree == null || _tree.nodes == null)
{
Debug.LogError("[AIStoryEngine] Failed to parse dialogue JSON");
return;
}
Debug.Log($"[AIStoryEngine] Loaded dialogue for {_tree.npc_name} ({_tree.metadata.node_count} nodes)");
GoToNode(_tree.root_node);
}
/// <summary>
/// Navigate to a specific node by ID.
/// </summary>
public void GoToNode(string nodeId)
{
if (nodeId == null || !_tree.nodes.ContainsKey(nodeId))
{
Debug.Log("[AIStoryEngine] Dialogue ended");
_currentNode = null;
return;
}
_currentNode = _tree.nodes[nodeId];
switch (_currentNode.type)
{
case "npc_line":
HandleNpcLine(_currentNode);
break;
case "player_choice":
HandlePlayerChoice(_currentNode);
break;
case "event":
HandleEvent(_currentNode);
break;
}
}
private void HandleNpcLine(DialogueNode node)
{
Debug.Log($"[{node.speaker}] ({node.emotion}): {node.text}");
// TODO: Display in your UI system
// TODO: Play audio using node.audio_hint
// TODO: Trigger node.@event if present
// Auto-advance to next node (hook up to "Continue" button in production)
// GoToNode(node.next);
}
private void HandlePlayerChoice(DialogueNode node)
{
for (int i = 0; i < node.choices.Count; i++)
{
var choice = node.choices[i];
Debug.Log($" [{i + 1}] {choice.text}");
// TODO: Check choice.condition against player stats
// TODO: Render as UI buttons
}
}
private void HandleEvent(DialogueNode node)
{
Debug.Log($"[AIStoryEngine] Event triggered: {node.@event}");
// TODO: Dispatch to your event system
GoToNode(node.next);
}
/// <summary>
/// Call this when the player selects a choice (0-indexed).
/// </summary>
public void SelectChoice(int choiceIndex)
{
if (_currentNode?.type != "player_choice") return;
if (choiceIndex < 0 || choiceIndex >= _currentNode.choices.Count) return;
var chosen = _currentNode.choices[choiceIndex];
GoToNode(chosen.next);
}
/// <summary>
/// Advance past an NPC line to the next node.
/// </summary>
public void Continue()
{
if (_currentNode?.type == "npc_line")
{
GoToNode(_currentNode.next);
}
}
}
}
Static Import (Build Time)
For static dialogue, export the JSON once and include it as a Unity TextAsset:
Export from the API
curl -o Assets/Dialogue/greta_session001.json \
https://api.aistoryengine.dev/v1/projects/proj_a1b2c3d4/export/unity \
-H "Authorization: Bearer $ASE_API_KEY" \
-G -d "npc_id=npc_x7y8z9" \
-d "session_id=session_001" \
-d "format=dialogue_tree"
Assign in Unity Editor
Drag the .json file onto the DialogueLoader component's
Dialogue Json field in the Inspector. Unity imports .json
files as TextAsset automatically.
Wire up your UI
In the HandleNpcLine and HandlePlayerChoice methods,
replace the Debug.Log calls with your dialogue UI system. Call
Continue() when the player clicks through NPC lines and
SelectChoice(index) when they pick a dialogue option.
Runtime Generation (Pro Plan)
For dynamic dialogue that adapts to player behavior in real time, call the
generate endpoint from Unity using UnityWebRequest:
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
using Newtonsoft.Json;
namespace AIStoryEngine
{
public class RuntimeDialogueClient : MonoBehaviour
{
[SerializeField] private string apiKey; // Use pk_ key for client-side
[SerializeField] private string projectId;
[SerializeField] private string baseUrl = "https://api.aistoryengine.dev";
public IEnumerator GenerateDialogue(
string npcId,
string sessionId,
string playerAction,
System.Action<DialogueTree> onSuccess,
System.Action<string> onError)
{
var requestBody = JsonConvert.SerializeObject(new
{
npc_id = npcId,
session_id = sessionId,
player_action = playerAction,
output_format = "dialogue_tree"
});
var url = $"{baseUrl}/v1/projects/{projectId}/generate";
var request = new UnityWebRequest(url, "POST");
request.uploadHandler = new UploadHandlerRaw(
Encoding.UTF8.GetBytes(requestBody));
request.downloadHandler = new DownloadHandlerBuffer();
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Authorization", $"Bearer {apiKey}");
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
var tree = JsonConvert.DeserializeObject<DialogueTree>(
request.downloadHandler.text);
onSuccess?.Invoke(tree);
}
else
{
onError?.Invoke(request.error);
}
}
}
}
Tips for Unity Editor Integration
Preview in Editor
Create a custom Editor window that loads DialogueTree JSON and lets you click
through the conversation without entering Play Mode. Use
EditorGUILayout.TextArea to display NPC lines and buttons for choices.
Addressables
For large projects, store dialogue JSON as Addressable assets instead of bundling them in Resources. This lets you update dialogue without rebuilding the entire game.
Localization
Export dialogue as CSV (/export/csv) for integration with
Unity Localization Tables. The CSV includes string IDs that map to
DialogueTree node IDs.
Audio Integration
The audio_hint field on NPC lines maps to voice profiles.
Use it to select the right voice clip or to pass to a text-to-speech system
like ElevenLabs for runtime audio generation.
Need help? Join the #unity-integration channel on Discord for community support, or reach out to support@aistoryengine.dev for Pro/Enterprise customers.