An NPC conversation in Fate of Eclipse: the parchment dialogue panel asks the player's name on a town street
Featured Project

Dynamic NPC Behaviour System

A modular Unity system, built for the game Fate of Eclipse, that connects AI-generated dialogue with world context, navigation, and animation-driven character behaviour.

Team of 6 Unity / C# Gemini API Diploma Final Year Project

The system in play — conversation, interaction, and inventory.

Fate of Eclipse was our diploma final year project, and we wanted its characters to feel less scripted than fixed dialogue trees. The idea was to let players talk to NPCs in an open-ended way, while keeping those conversations tied to the game's world, time, and physical interactions. I built the gameplay systems that make dialogue something more than text on a screen.

Paradise Island terrain in the Unity Editor, the island-shaped Unity Terrain environment built for Fate of Eclipse
Paradise Island — the Unity Terrain we had built replicating from AoT, shown in the Unity Editor.

Fate of Eclipse is a 3D narrative game built in Unity that explores AI-assisted NPC interaction in an explorable environment. Instead of predefined dialogue options, a Gemini API dialogue pipeline combines in-game context, character-specific information, interaction events, and animation control.

This page covers the NPC and interaction systems I built for it, not the whole game. The design goal was to keep what a character says connected to what happens in the world.

Six system modules

DialogueNPC identity, sessions, conversation history, AI requests, and dialogue UI.
InteractionTarget detection, request routing, and separate talk and pickup paths.
Dynamic NPC behaviourConnects NPC responses to interaction handlers and animation requests.
World awarenessKnown locations, in-game time, and NavMesh navigation.
InventoryItem definitions, ownership, contextual actions, and held items.
Animation coordinationAnimation manager, database, and per-character controllers.

Core loop

Explore → Discover a nearby target → Interact (talk or pick up) → Supply the system with identity, world, and time context → Receive a response and any configured animation → Keep exploring.

Intended audience

Players interested in narrative exploration, interactive characters, and AI-assisted gameplay.

Role: Gameplay Systems Programmer · Interaction Architecture · Dialogue Integration

The project had six team members, and most of the core implementation was done by two of us. My collaborator Rushikesh led storyline, narrative, and the 3D character pipeline. I led the gameplay programming and the architecture connecting the systems.

Characters designed for Fate of Eclipse lined up in T-pose in a town scene, with the player character in front
The NPC cast lined up in the town scene, with the player character in front — built with the team's character pipeline.

My primary responsibilities

  • Interaction architecture: Proximity-based target detection that picks the nearest valid target, with IInteractable and IPickable contracts and in-range and out-of-range handling, coordinated by PlayerInteractionHandler.
  • Dialogue and AI integration: The path from player input to NPC dialogue sessions, with the Gemini API isolated behind a dedicated handler, NPC data assets for character-specific instructions, and environment and time context added to the conversation.
  • Dynamic NPC behaviour: The event pathway from NPC interaction handlers to an animation database and character-specific animation controllers.
  • World awareness and navigation: Connecting dialogue to known locations, the game's TimeManager, and Unity's NavMeshAgent.
  • Inventory: ScriptableObject item definitions, inventory ownership, selection, held-item lifecycle, and event-driven updates for add, remove, throw, and hold.
  • Integration: Event-driven communication across all of the above, and connecting Rushikesh's narrative and character assets to the gameplay systems.
Tech stack: Unity · C# · Gemini API · ScriptableObjects · NavMeshAgent · Animator · Event-driven architecture
Third-party: Unity Starter Assets (third-person controller and input) · Meta AI · Meshy AI · AccuRIG (character pipeline)
Team of 6 · Diploma final year project
01

Architecture overview

I treated the NPC system as cooperating parts, not one NPC controller doing dialogue, input, navigation, animation, and inventory. Three approaches work together:

Component-basedMonoBehaviours each own one focused runtime responsibility.
Data-drivenScriptableObjects hold reusable NPC, item, animation, and event-channel data.
Event-drivenEvent channels carry gameplay events between systems that should not reference each other.

This is why the dialogue system never has to manage the player's inventory or every character's Animator. A GameStateManager also raises a game-state-changed event that the InputManager listens to, so dialogue and inventory screens can take priority over normal gameplay input.

Overview diagram of the Player, NPC, Game Manager, and Dialogue UI groups connected by event channels such as Interaction Key Press, Dialogue Start, and Interaction Animation Triggered
System overview — Player, NPC, Game Manager, and Dialogue UI communicating through event channels.
02

Interaction detection

The first problem was a single, consistent way for the player to find and use things in the world. InputManager publishes interaction and pickup key presses through separate event channels. PlayerInteractionHandler scans nearby colliders, picks the nearest valid target, and routes the request.

Two contracts split the paths: IInteractable for things that respond to an interaction, such as NPCs, and IPickable for items that can be collected. Talking to an NPC and picking up an item both start with a key press, but they are different actions.

Design decision: keep detection, input events, and object-specific behaviour apart, so a new interactable can be added without touching the player interaction controller.

Flow diagram of player interaction: InputManager event channels, PlayerInteractionHandler scanning colliders, IInteractable and IPickable targets, NPCInteractionHandler, and the animation request pipeline
Player interaction flow — from key press to target selection to NPC or item behaviour.
03

AI dialogue as a managed session

I treated a conversation as a session the game manages, not a string returned by an API. That gives the system somewhere to keep history, handle queries, and attach game context.

  1. Start: Interacting with an NPC calls NPCInteractionHandler, which asks DialogueSessionManager to start a session. The session is seeded with the NPC's identity and instructions from its NPCDataSO.
  2. Open: An opening entry is created, a start event with the session ID is raised, and DialogueUIHandler fetches the session and displays it.
  3. Ask: The player submits a question through the dialogue UI, which raises DialogueAskQueryRaisedEventChannelSO. The NPC handler adds the query to the session together with environment and timestamp context.
  4. Request: The handler calls GeminiAPIHandler with the session and a callback. This is the only place that talks to the external service.
  5. Respond: The callback returns the response, which is stored as a new DialogueEntry. The UI reads entries by index and supports Previous and Next.
  6. End: Quitting raises DialogueEndEventChannelSO so dependent systems know the conversation is over.

The player-facing side is a parchment-style dialogue panel: the NPC's name and current response, a text field for the player's question, and Submit, Quit, Previous, Ask, and Next buttons. Ask and Submit raise the query event, Previous and Next step through the session's stored entries, and Quit raises the end event. The same panel appears in the banner at the top of this page.

Parchment-style dialogue panel with an NPC name, response text, a text field reading Write Something, and Submit, Quit, Previous, Ask, and Next buttons
Dialogue UI — the panel driven by DialogueUIHandler.
Dialogue event pipeline diagram showing NPCIdentity, NPCDataSO, DialogueSession, DialogueSessionManager, GeminiAPIHandler, DialogueUIHandler, DialogueEntry, and the dialogue event channels
Dialogue event pipeline — session, API handler, UI, and lifecycle events.
04

From dialogue to action

Without extra work, an NPC can say something while its body does nothing related. I wanted interactions to be able to trigger a physical response, so I separated requesting an animation from playing it.

  1. Request: When an interaction should animate, the NPC handler raises InteractionAnimationTriggeredEventChannelSO.
  2. Resolve: InteractionAnimationsManager receives it and looks up the configuration in InteractionAnimationDatabaseSO.
  3. Play: The matching PlayerAnimationController or NPCAnimationController runs the animation on that character.

Where an interaction is configured for it, such as a gesture or handshake, dialogue can request a matching action while the animation layer stays responsible for executing it. The system does not generate new animations from AI text: it connects AI dialogue to a set of configured animations through events. New animations can be added to the database without changing the dialogue code.

05

World awareness and navigation

A conversation system that knows nothing about the game world cannot answer questions about it, so the dialogue context includes two kinds of world information: where things are and when it is.

Location. NPCs work with known landmarks on Paradise Island. These are fixed destinations, so the NPC never walks to an arbitrary position suggested by generated text. Once a destination is chosen, Unity's NavMeshAgent handles pathfinding and movement, instead of custom pathfinding inside the dialogue code.

Time. The NPC system polls the game's TimeManager for the current in-game time and weekday, and each dialogue entry carries that timestamp. Time is available as context to the dialogue. It does not mean NPCs follow a full daily schedule.

Design decision: the AI side handles conversation, and deterministic systems handle movement and timekeeping.

06

Inventory and the world-item lifecycle

An item exists in three places at once: as a definition, as an object in the world or the player's hand, and as an entry in the inventory. I kept those as separate responsibilities:

ItemData (ScriptableObject)Name, icon, description, tags, world prefab, and behaviour prefab.
WorldItemThe physical item; implements IPickable and publishes the pickup.
GameInventoryManagerOwns the inventory collection and the held-item reference.
Event channelsItem added, removed, thrown, and held-item changed.
InventoryUIHandlerSlots, selected-item details, and contextual actions.
HandHeldItemControllerInstantiates, hides, and throws the in-hand item through InHandItem.

The selected item exposes only the actions it supports: open, use, hold in hand, throw, or put back in the bag. Opening an item can lead to a sub inventory screen, such as a letter. The UI and held-item controller react to inventory events rather than controlling the manager directly.

Design decision: model inventory as an ownership and event system, not a grid of UI slots, so pickup, item actions, and held-item visuals stay in sync.

Inventory architecture diagram: Inventory UI, Item Definition, World Representation, Inventory Ownership with event channels, and Physical Held Item
Inventory architecture — definition, world object, ownership, UI, and held item.
Inventory UI planning: a reference sketch, a wireframe with items in bag, item details and held in hand panels, and a sub inventory screen showing a letter
Where the UI started — reference sketch and wireframes for the item grid, item details, held item, and sub inventory. The final UI is shown in Results.
07

Problems solved

  • Tight coupling: A direct reference approach would make the player controller find targets, trigger dialogue, drive animation, and edit inventory. Focused components, interfaces, and event channels spread those responsibilities out.
  • AI response to physical action: A generated reply does not produce a coherent animation by itself. A separate animation request pathway lets dialogue ask for configured behaviour without owning the animation logic.
  • Conversation context: Independent API calls give disconnected conversations. NPC data assets, sessions, and stored history give each request the context it needs.
  • World-relevant answers: Known locations and the TimeManager feed the dialogue context, so NPCs can respond about where and when.
  • Items in several places: Separate definitions, ownership, events, and held-item lifecycle keep the inventory, the world object, and the hand object consistent.
08

Character pipeline — team contribution

Rushikesh built the characters, and this part was not my work. He used character reference images as visual inspiration, generated a T-pose reference with Meta AI, turned it into a 3D model with Meshy AI, rigged it with AccuRIG, and imported it into Unity.

Those are third-party tools and not custom character-generation technology. My systems handle how the resulting characters interact, converse, and animate at runtime.

The project combined several systems that are often built separately: AI dialogue, player interaction, animation coordination, world context, NavMesh navigation, and inventory. They share one architecture of focused components, reusable data assets, and event channels.

Functional outcomes

  • The interaction layer finds targets and routes requests to NPCs or pickable items.
  • The dialogue layer manages NPC identity, queries, conversation history, and session events, with the Gemini API behind a single handler.
  • The animation layer resolves and runs configured interaction animations on the player and NPCs.
  • NPC dialogue has access to known locations and in-game time and weekday, and NPCs can navigate to landmarks with NavMeshAgent.
  • The inventory layer tracks ownership and held items, and notifies dependent systems of changes.
Final inventory UI in Fate of Eclipse: a grid of items, an item details panel with action buttons, and a held item panel
Final inventory UI — item grid, item details with action buttons, and held item, built from the wireframe in phase 06.

Inside the development environment

Most of the systems above were built and tested in a cluttered scene full of test NPCs, items, and placeholder objects, where interaction ranges, dialogue sessions, and inventory events could be checked quickly.

Development test environment — an early, cluttered scene used to iterate on the systems.

The main lesson was that dynamic NPC behaviour needs more than a generative AI API. It needs character identity, conversation state, world context, interaction pathways, and animation coordination around the API to connect a reply to the game itself.

Splitting responsibilities across components, data assets, interfaces, and event channels made the system easier to extend. It also made integration harder: when systems talk indirectly, event ownership, subscription lifecycles, and debugging matter much more.

With another cycle I would add repeatable integration tests for dialogue sessions, event subscriptions, interaction range changes, and item lifecycle transitions. I would also add a validation layer between AI responses and gameplay actions, so dialogue can never trigger unsupported or invalid behaviour.

The main takeaway: the goal was not to make NPCs speak with AI, but to build the architecture that lets dialogue, character behaviour, world context, and player interaction work as connected systems.

Watch the System in Action Back to all projects