By the end of this material, students should be able to:
Integrate a voice-enabled AI agent into a Unity game using the ElevenLabs Unity SDK, enabling real-time spoken conversation between players and NPCs.
Specifically, students will:
Traditional NPCs have pre-written dialogue. This SDK allows NPCs to:
| Traditional NPC | ElevenAgents NPC |
|---|---|
| Fixed text responses | Live AI conversation |
| No voice | Real spoken voice |
| No player input | Listens to microphone |
| Same for every player | Adapts dynamically |
Player speaks → Microphone → ElevenLabs Agent → Voice response → AudioSource → Player hears NPC
Three core components work together:
⚠️ Why turn off Authentication for testing? Authentication requires a server to generate signed URLs. Disabling it lets you connect with just the Agent ID during development. Always re-enable before shipping.
Window → Package Manager → + → Install package from git URL
Paste this URL:
https://github.com/elevenlabs/unity.git#v0.1.0
Package Manager → ElevenAgents → Samples → GettingStarted → Import
Paste your Agent ID into the sample → Press Play
Expected result: A cube greets you when you walk toward it (~10 minutes total)
A session opens when the player enters a trigger zone and closes when they leave:
// Conceptual structure
void OnTriggerEnter() {
await conversation.StartSessionAsync(new SessionConfig {
AgentId = "your-agent-id"
});
}
void OnTriggerExit() {
conversation.EndSession();
}
| Method | Behavior |
|---|---|
StartSessionAsync | Waits until connection + audio are ready |
EndSession | Safe to call even if session already ended |
| Transport selection | SDK automatically picks the right method per platform |
💡 Mental Model: Think of
StartSessionAsynclike opening a phone call — it waits until both sides are connected before returning.
Default behavior: Voice plays in 2D (everywhere equally)
With AudioSource: Voice plays FROM the character's position
// Attach your character's AudioSource component
new SessionConfig {
AgentId = "your-agent-id",
OutputAudioSource = characterAudioSource // ← add this
}
// GetOutputVolume() returns 0.0 to 1.0
float volume = conversation.GetOutputVolume();
// Use it to animate a mouth or bob a character
mouthOpenAmount = volume;
characterTransform.localScale = baseScale + (Vector3.up * volume * bobAmount);
💡 Use Case: The sample makes a cube bob up and down while talking. You can use the same value to blend a mouth-open animation shape key.
You have 10 NPCs. Creating 10 separate agents is wasteful.
Define placeholders in your agent's prompt on the dashboard:
You are a guard. Your current mood is {{mood}}. Your name is {{name}}.
Then each character fills in their own values at session start:
new SessionConfig {
AgentId = "shared-agent-id",
DynamicVariables = new Dictionary<string, object> {
{ "mood", "angry" }, // string
{ "name", "Captain Rex" }, // string
{ "alertLevel", 3 }, // number
{ "isOnDuty", true } // boolean
}
}
For bigger changes (different voice, different first message):
new SessionConfig {
AgentId = "shared-agent-id",
Overrides = new AgentOverrides {
FirstMessage = "Halt! Who goes there?",
// Voice override also possible
}
}
⚠️ Each override must be enabled in the agent's Security settings first.
The player picks up a sword during a conversation. The NPC doesn't know.
// Send game state without interrupting the conversation
conversation.SendContextualUpdate("The player just picked up the ancient sword.");
conversation.SendContextualUpdate("A dragon has appeared nearby. Threat level: high.");
| Method | Creates a turn? | Interrupts? | Use for |
|---|---|---|---|
SendContextualUpdate | ❌ No | ❌ No | Background game events |
SendUserMessage | ✅ Yes | Possibly | Text input alternative to voice |
💡 Mental Model:
SendContextualUpdateis like whispering stage directions to an actor mid-scene — the audience (player) doesn't hear it, but the actor (agent) adjusts.
The agent can call functions in your game. This is two-way communication:
Game → Agent: "The player entered the dungeon" (contextual update)
Agent → Game: "OpenDoor(doorId: 'main-gate')" (client tool)
Step A: Define the tool in the ElevenLabs dashboard
Tool name: OpenDoor
Parameters: { "doorId": string }
Step B: Register a handler in Unity under the same name
conversation.RegisterClientTool("OpenDoor", async (parameters) => {
string doorId = parameters["doorId"].ToString();
// Your game logic
bool success = await GameManager.OpenDoor(doorId);
if (!success) {
throw new ClientToolException("Door not found: " + doorId);
}
return "Door opened successfully"; // Agent receives this
});
| Feature | Detail |
|---|---|
| Parameter parsing | SDK uses Newtonsoft Json automatically |
| Async support | Handlers can return Awaitable for slow operations |
| Error reporting | Throw ClientToolException to tell agent what failed |
| Return value | Whatever you return goes back to the agent |
conversation.UserTranscriptReceived += (text) => {
subtitleUI.text = "Player: " + text;
};
conversation.AgentResponded += (text) => {
subtitleUI.text = "NPC: " + text;
};
conversation.ModeChanged += (mode) => {
listeningIndicator.SetActive(mode == ConversationMode.Listening);
};
conversation.Interrupted += () => {
Debug.Log("Player interrupted the NPC");
};
✅ All events fire on Unity's main thread — safe to update UI directly, no
Dispatcherneeded.
conversation.SetMicMuted(true); // Mute player mic
conversation.SetVolume(0.8f); // Adjust agent volume
conversation.SendUserMessage("Hello"); // Text instead of voice
float[] freq = conversation.GetFrequencyData(); // For audio visualizers
| Platform | Status | Special Requirement |
|---|---|---|
| Editor (Desktop) | ✅ Full support | None |
| Desktop Build | ✅ Full support | None |
| WebGL | ✅ Supported | Enable Use WebAssembly.Table in Publishing Settings |
| iOS | 🔶 Beta | Request microphone permission before session |
| Android | 🔶 Beta | Request microphone permission before session |
| XR | 🔶 Beta | — |
❌ Play Mode doesn't work when WebGL is active build target
→ Switch to Desktop target for testing in Editor
❌ AudioSource mixer routing doesn't work
❌ Doppler effect doesn't work
❌ OutputAudioSource ignored in WebRTC sessions
✅ Voice plays through Web Audio API instead
DEVELOPMENT:
Player → Agent ID → ElevenLabs
(Simple, but exposes your agent)
PRODUCTION:
Player → Your Server → Signed URL (short-lived) → ElevenLabs
(Secure, API key never in game build)
// ❌ NEVER in a shipped build
new SessionConfig { AgentId = "your-agent-id" }
// ✅ Correct for production
string signedUrl = await YourServer.GetSignedUrl();
new SessionConfig { SignedUrl = signedUrl }
🔴 Critical Rule: Your ElevenLabs API key must never be inside a game build. Anyone can extract it and use your credits.
┌─────────────────────────────────────────────────────┐
│ YOUR UNITY GAME │
│ │
│ Player walks in → StartSessionAsync() │
│ Player speaks → Microphone captured │
│ Game event occurs → SendContextualUpdate() │
│ Agent speaks → AudioSource (spatial!) │
│ Agent wants action → ClientTool handler fires │
│ Player walks out → EndSession() │
└─────────────────────────────────────────────────────┘
↕ Conversation API
┌─────────────────────────────────────────────────────┐
│ ELEVENLABS DASHBOARD │
│ │
│ Agent Prompt + Voice + Tools + Security Settings │
└─────────────────────────────────────────────────────┘
OutputAudioSource set for spatial voiceGetOutputVolume() driving mouth animationSendContextualUpdate for mid-conversation game events