Dialogue Agents
AgentChatService is the component that makes a character a dialogue agent. Everything the conversation runtime needs to know about a character -- which trees it can play, its portrait, its moods, its blackboard -- hangs off this one MonoBehaviour.
Add it to the root GameObject of the character. That placement matters: the AudioSource used for voice lines is created there, and lip sync providers read audio through OnAudioFilterRead on the same object.
What it sets up for you
The minimum viable NPC is a GameObject with AgentChatService and a chat tree assigned. Everything else has a default, and the component provides it when the agent starts:
- a presentation service, if the GameObject does not already have one
- the agent's mood and non-verbal eventing
- the agent's blackboard, seeded from its templates and values
- a link to any shared agent on the same GameObject, such as a CivilAI agent
You do not need to add or wire any of these yourself.
Standalone agents and shared agents
AgentChatService implements IAgentService itself, but it prefers to delegate. If another agent component is present on the same GameObject, that component becomes the source of truth for identity, blackboard, and pause state.
| Behaviour | Standalone | With a shared agent (e.g. CivilAI) |
|---|---|---|
GetID() | A GUID generated on first use | The shared agent's ID |
GetDisplayName() | null | The shared agent's display name |
GetBlackboardService() | Its own BlackboardService | The shared agent's blackboard |
GetPauseService() | Its own PauseService | The shared agent's pause service |
Two consequences worth knowing up front. A standalone agent's ID is not stable across sessions, so do not persist anything keyed on it. And a standalone agent has no display name -- which is fine, because {speaker.name} reads the GameObject name rather than the display name anyway. See Token System.
Assigning trees
Two tree fields sit directly on the component:
- Chat Tree -- the tree used when the player talks to this agent
- Headless Chat Tree -- the tree used when this agent talks to another agent
They are separate because the two situations want different content. A merchant's player tree is a shop; their headless tree is small talk. See Chat Trees and Agent-to-Agent Conversations.
If no headless tree is assigned, the orchestrator falls back to the module-wide default headless tree.
Conditional tree selection
A single chat tree per NPC stops being enough quickly -- a guard who has arrested you should not open with the same greeting. Two fields handle this, and they are checked in order:
- Local Tree Selection Overrides -- an array of rules on this agent
- Shared Tree Selection -- a
DialogueTreeSelectionRepositoryasset, shared by many agents - the repository's Fallback Tree
- the plain Chat Tree field
The first step that produces a tree wins. So a local override beats the shared repository, and the shared repository beats the plain assignment.
A rule is a requirement collection plus a tree:
| Field | Meaning |
|---|---|
| Requirement Collection | Conditions that must be met for this rule to apply. |
| Tree | The tree to play when they are. |
Rules are evaluated top to bottom and the first match wins, so order them most specific first. A rule with no requirement collection always matches, which makes it a useful catch-all at the bottom of a list. A rule with no tree assigned is skipped rather than treated as a match.
RequirementCollection is a shared BardTreeLtd type, not a dialogue-specific one. See the Requirement System guide in the CIVIL-AI docs for how to author one.
Requirements are evaluated against the DialogueParticipants for the conversation, so a rule can test the listener as well as the agent -- "greet the player differently if the player is carrying the stolen ring".
Rule-based selection lives in FetchChatTree(participants). A headless conversation asks for the headless tree first, then the module-wide default headless tree, and only reaches rule selection if both are empty:
FetchHeadlessChatTree()- the orchestrator's fallback tree (Default Headless Tree in module settings)
FetchChatTree(participants)-- rule selectionFetchChatTree()
So assigning a headless tree, or a module default, takes your rules out of the picture for agent-to-agent conversations.
Replacing the selector
Selection is delegated to IDialogueTreeSelector. If nothing is registered, RequirementGatedDialogueTreeSelector is used, which implements the first-match-wins behaviour above. Register your own implementation against IDialogueTreeSelector to change how a tree is chosen -- weighted random, priority-scored, whatever the project needs -- without touching any agent.
Blackboard values
Two arrays seed the agent's blackboard, and both are applied when the agent initializes:
- Blackboard Value Templates --
BlackboardValueTemplateassets, shared across many agents - Blackboard Values -- values specific to this agent
Templates are applied first and the agent's own values second, so a local value overrides the template's value for the same key. Use templates for the shape ("every villager has trust, has_met_player") and local values for the exceptions.
If the agent delegates to a shared agent, these values are written into that agent's blackboard, not a separate one. See Blackboard.
Portrait and mood
- Portrait -- a
Spriteshown in the dialogue panel while this agent is speaking, if portraits are enabled in module settings - Mood Repository -- the set of moods this agent can be in
The mood repository is per agent, so a stoic character and an expressive one can carry different mood sets and different idle animations. See Mood.
Presentation
AgentChatService does not animate anything itself. It resolves an IAgentPresentationService on the GameObject and hands presentation to it -- looking at the listener, playing speech and mood animations, disabling cloth, driving lip sync.
If you add nothing, DefaultDialoguePresentationService is added automatically. It finds an Animator on the object or its children, plays clips through a PlayableGraph on unscaled time so animation keeps running while the world is paused, and forwards lip sync to whatever ILipSyncProvider it finds. See Agent Presentation Service and Lip Sync.
To take over presentation entirely, put your own IAgentPresentationService component on the agent before it initializes.
Practical advice
Start with one tree in the Chat Tree field and nothing else. Get a conversation running, then add the pieces that turn it into a character: a portrait, a mood repository, a headless tree, and only then conditional selection.
When you do reach for tree selection, prefer a shared DialogueTreeSelectionRepository over per-agent overrides. Villagers who share a rule set are much easier to retune later than fifty agents each carrying their own copy of the same three rules.
If an agent is meant to be a full CivilAI citizen, add the CivilAI agent component first and AgentChatService second. It will find the shared agent on initialize and delegate to it, which is what keeps one identity and one blackboard across both modules.
Reference
Next step
Continue to Player Conversations, or read Agent Chat Service for the full API.