OUGADEV / Projects / Yotei Quest System
Unreal Engine 5.7 In Dev
← Back to Projects

Game Systems Documentation

Yotei Quest System (UE5)

A dynamic, weighted quest director inspired by Ghost of Tsushima and Ghost of Yotei. Built Blueprint-only in Unreal Engine 5.7 using real production patterns: event dispatchers, data-driven content, and a catch-up mechanic that adjusts pacing based on the player's power vs the world's difficulty.

Unreal Engine 5.7 In Progress

Overview

Unreal Engine 5.7 In Development

A quest system built in Unreal Engine 5.7, Blueprint-only, designed around the same dynamic content-surfacing logic used in Ghost of Tsushima and Ghost of Yotei. The system acts as a weighted deck of cards: the game draws a quest category based on how the player's power compares to the world's difficulty, so pacing always serves the player's progression rather than following a fixed script.

▶ Project devlog - paste YouTube embed ID here

Project devlog, overall system demo running in-engine

What's Done

Quest Director
✓ Done
Weighted Draw
✓ Done
Quest UI / Log
✓ Done
Dialogue System
✓ Done
Two-Shot Camera
✓ Done
Settler Trigger
⟳ In Dev
Camp Trigger
Planned
Bounty Board
Planned
Patrol Trigger
Planned
Ally Recruitment
Planned

In-Engine Screenshots

Quest Log
Quest Log open in Pause Menu
Dialogue system
Auto-advance dialogue with two-shot camera

The Concept

Unreal Engine 5.7

Instead of scripting which quest the player gets next, the system draws from a pool of available quests using category weights that shift based on the player's situation. The director is invisible to the player; from their perspective, the world just keeps offering something interesting at the right time.

The Four Card Types

CategoryPurpose
Main QuestAdvances the primary story arc. High weight when the player is ahead of the curve.
Side QuestUnlocks allies, gear, and relationships. High weight when the player needs to catch up.
ExplorationRegion-specific content, secrets, lore. Drawn when other pools are thin.
CombatBounties, patrol hunts, arena challenges. Drawn to spike tension or reward aggression.

How Weights Shift

Each region has a difficulty level. The player has a power level. The gap between them drives how category weights are calculated:

  • Player power below region difficulty: Side Quest and Exploration weights rise. The game surfaces quests that give allies, gear, and skills to close the gap.
  • Player power above region difficulty: Main Quest weight rises. The game trusts the player is strong enough and pushes the story forward.
  • Player power equal to region difficulty: balanced distribution across all four categories.
Design reference: this is the same philosophy as Left 4 Dead's AI Director, a hidden system that reads the player's state and adjusts the experience accordingly rather than scripting every beat by hand. Ghost of Yotei uses it to surface quests; Left 4 Dead used it to place enemies. The underlying pattern is the same.

How It's Built

  • Every widget subscribes to an Event Dispatcher and only updates when something changes. Nothing reads state in Tick.
  • BP_YoteiGameInstance is the only place runtime quest state lives. No system caches a copy.
  • All quest content lives in a JSON-imported Data Table. Adding a quest means adding a row, not a new Blueprint asset.
  • Every system is built so new features add nodes and fields rather than rewiring what already works.

System Map

Unreal Engine 5.7

How all the pieces connect, from the moment something triggers a draw to the moment the player sees a new quest in their log.

Quest System Pipeline
Triggers
🏕 Camp Visitor
🧑 Settler NPC
📌 Bounty Board
⚔️ Last Enemy
→
Quest Director
1
Compute category weights from PlayerPowerLevel vs RegionDifficulty
2
Weighted random roll across available categories
3
Get available quests for picked category in current region
4
If pool empty, remove category and re-roll. Repeat until drawn or exhausted.
→
Game Instance
ActiveQuests[ ]
CompletedQuests[ ]
TrackedQuest
OnQuestUnlocked
OnQuestCompleted
(dispatchers)
→
Quest UI
WBP_QuestTab
WBP_QuestSection
WBP_QuestEntry
WBP_QuestDetail

How Data Flows

  • Triggers never talk to the UI directly. They call DrawAndUnlockQuest on the Quest Director, and everything downstream reacts to the dispatchers the Game Instance broadcasts.
  • The Quest UI never polls for state. Every widget binds to OnQuestUnlocked and OnQuestCompleted in Event Construct, so updates arrive as events rather than being requested on a timer.
  • The re-roll loop is bounded. If all four categories are exhausted with no valid quests remaining, the function returns None cleanly. The trigger handles this gracefully rather than crashing or looping.
  • The Dialogue System sits beside this pipeline, not inside it. Triggers that involve dialogue (Settler, Camp) call the Dialogue System first; the quest draw fires at the end of the conversation, as the payoff.

Quest Director

Unreal Engine 5.7 Done

QuestManager is a Game Instance Subsystem that owns all runtime quest state and the weighted draw logic. It is the single piece every trigger calls, and the single source every widget reads from.

Quest Data Structure

FieldTypePurpose
TitleTextDisplay name shown in the Quest Log
DescriptionTextJournal entry text shown in the detail panel
CategoryE_QuestCategoryMain / Side / Exploration / Combat. Drives the weighted draw.
RegionE_RegionWhich region the quest belongs to. Filters the draw pool by location.
QuestLineNameGroups quests under a named arc in the log (e.g. "The River Valley Tales")
PrerequisitesArray of NameRow names that must be completed before this quest can be drawn
NextQuestNameAuto-unlocks this row on completion, enabling chain quests

DrawAndUnlockQuest

Input: CurrentRegion (E_Region) Output: DrawnQuest (BP_QuestData ref) or None 1. ComputeCategoryWeights(Region) PlayerDelta = PlayerPowerLevel - RegionDifficultyLevel Negative delta → raise Side + Exploration weights Positive delta → raise Main weight Returns: MainWeight, SideWeight, ExplorationWeight, CombatWeight 2. RemainingCategories = [Main, Side, Exploration, Combat] 3. WHILE RemainingCategories not empty: a. TotalWeight = sum of weights for remaining categories b. RandomRoll = Random(0, TotalWeight) c. Walk categories, accumulate RunningSum First category where RunningSum >= RandomRoll = PickedCategory d. Pool = GetAvailableQuests(PickedCategory, Region) Filters out completed quests + quests with unmet prerequisites e. IF Pool not empty: Draw random quest from Pool Add to GameInstance.ActiveQuests[] Broadcast OnQuestUnlocked RETURN DrawnQuest f. ELSE: Remove PickedCategory from RemainingCategories, continue 4. IF all categories exhausted: RETURN None
Why re-roll instead of returning None on an empty pool? If the player has completed all Side Quests in a region, a Side Quest draw should silently fall through to the next category rather than giving the player nothing. The re-roll loop ensures the game always surfaces something as long as any valid quest exists anywhere in the current region's pool.

What the Subsystem Tracks

VariableTypeDescription
ActiveQuestsArray of BP_QuestDataQuests currently unlocked and visible in the log
CompletedQuestsArray of NameRow names of completed quests. Used to filter draw pools and check prerequisites.
TrackedQuestBP_QuestData refThe quest currently pinned to the HUD tracker
PlayerPowerLevelIntegerUpdated by the progression system. Drives weight computation.

Demo

DrawAndUnlockQuest running across different PlayerPowerLevel values, showing the category shift

Triggers

Unreal Engine 5.7

Four systems call DrawAndUnlockQuest, each in its own flavor. The director doesn't care who called it; the trigger is just the context that makes the draw feel earned rather than arbitrary.

🏕
Camp Visitor
Planned

A random NPC arrives at a campfire drawn from a weighted visitor table. They open a short dialogue exchange that ends with a quest draw. The visitor type (wanderer, merchant, fugitive) flavors which categories they can surface.

🧑
Settler NPC
In Dev

A named NPC in a settlement the player can approach. Interact with them, go through a short dialogue, and a quest is drawn. Closest to done: the Interaction System is already wired, just needs the draw hook.

📌
Bounty Board
Planned

A physical board actor the player interacts with. Draws directly from the Combat category with no dialogue, giving the player an on-demand way to surface combat quests without waiting for a random trigger.

⚔️
Last Enemy / Interrogation
Planned

The last surviving enemy of a patrol squad is kept alive during combat. After a brief interrogation dialogue, a quest is drawn. Requires the enemy squad tracking system to know when exactly one remains.

Dependency note: Camp and Patrol both need systems of their own before they can call the draw. Camp needs the weighted-visitor-table logic. Patrol needs squad and last-enemy detection. Settler and Bounty Board have no new dependencies and will ship first.

Quest UI

Unreal Engine 5.7 Done

The Quest Log lives inside the Pause Menu and uses a three-box layout: questline sections (Box 1), quest entries for the selected section (Box 2), and a detail panel for the selected entry (Box 3).

Widget Hierarchy

WBP_PauseMenu └── Widget Switcher (Tab index 1: Quests) └── WBP_QuestTab ├── Box 1, ScrollBox of WBP_QuestSection (one per questline) │ └── WBP_QuestSection bubbles OnSectionClicked ├── Box 2, ScrollBox of WBP_QuestEntry (one per quest in section) │ └── WBP_QuestEntry bubbles OnEntryClicked └── Box 3, WBP_QuestDetail (persistent, updated in place)

Discovered Quests Only

Only discovered questlines appear in the log. Showing "The Kitsune's Trial" before the player has encountered it would spoil that the storyline exists at all. The log builds from GameInstance.ActiveQuests[] and CompletedQuests[], grouped by the QuestLine field. If you have no quests from a questline, that questline doesn't exist in your log yet.

How Updates Work

  • Box 3 holds one persistent WBP_QuestDetail instance, created once in Event Construct and updated in place. It never gets rebuilt.
  • Box 1 and Box 2 do get rebuilt on each section/entry click via RefreshQuestList and RefreshEntryList.
  • Clicking a section only rebuilds Box 2. The detail panel only updates when a specific quest entry is clicked, so switching sections leaves whatever is showing in Box 3 alone until the player picks a quest.

Tab Key Fix

The pause menu toggle lives in BP_PlayerController, not BP_PlayerCharacter. Character input stops processing once Set Game Paused is active; a PlayerController is not affected by pause state.

Closing is handled by WBP_PauseMenu's own On Preview Key Down override, not through a Controller bool. This sidesteps the state-desync bug where Tab would get stuck only ever opening the menu.

Quest Log
WBP_QuestTab, three-box layout with section, entries, and detail panel
Quest Detail
WBP_QuestDetail, stage text and track button

Dialogue System

Unreal Engine 5.7 Done

A Yotei-style auto-advance system. Lines play through on a timer derived from the voice clip duration, no Continue button. The only player input available is hold-to-skip the entire conversation.

Design reference: Ghost of Yotei's dialogue is fully automated. Lines play on their own, you cannot advance line by line, and the only available input is hold-to-skip the whole conversation. This system matches that exactly.

Line Timing

  • Line duration is MAX(GetSoundDuration(VoiceClip), MinLineDuration). The minimum guarantees that silent test rows hold long enough to read.
  • MinLineDuration defaults to 2.5 seconds.
  • A timer fires AdvanceDialogue, which reads NextLineID. If it's an empty Name, the conversation ends. If it has a value, the next line loads and the timer resets.

Auto-advance dialogue running with two mannequins and the two-shot camera

Creation order gotcha: WBP_DialogueBox must be created at level start in BeginPlay, not on first Interact. If it is created on Interact, its Bind Event won't be listening in time for that first call. The first line fires, nothing shows, and it is difficult to trace because the widget exists by the time you check.

Two-Shot Camera

Unreal Engine 5.7 Done

BP_DialogueCamera is a CineCameraActor subclass repositioned by ActivateDialogueCamera at the start of every conversation. It places itself at the midpoint between the speaker and the player, offset to the side and slightly up, then looks at the midpoint to frame both characters.

Camera Positioning

Midpoint = (SpeakerLocation + PlayerLocation) / 2 Offset = Midpoint + (RightVector * SideDistance) + (UpVector * HeightOffset) Camera → SetActorLocation(Offset) Camera → LookAt(Midpoint)
  • SideDistance and HeightOffset are exposed variables, tunable per conversation or NPC type without opening the Blueprint.
  • The camera restores the player's original view on EndDialogue by caching the active camera at the start of the conversation and blending back to it.
Dialogue camera
Two-shot framing with BP_DialogueCamera positioned between two mannequins

Roadmap

Unreal Engine 5.7

What's Next

  • Settler trigger: closest to done. Interaction is wired, just needs the draw hook and dialogue.
  • Camp trigger: weighted visitor table, then visitor dialogue, then draw.
  • Bounty Board: board actor, interact, draw directly from Combat pool. No dialogue needed.
  • Combat system: melee core, parry, dodge.
  • Enemy NPC: patrol AI, squad tracking, interrogation trigger (last enemy calls draw).
  • Ally recruitment: the payoff the Quest Director was built to deliver. A Side Quest completion hands over an ally, completing the catch-up loop the weighted system promises.

Dependency Order

Settler Trigger (near done, no new dependencies) ↓ Camp Trigger (needs visitor table logic) Bounty Board (no new dependencies) ↓ Combat System ↓ Enemy NPC (needs Combat) Patrol Trigger (needs Enemy NPC squad tracking) ↓ Ally Recruitment (needs Quest completion hook + NPC result)

Other Systems Needed

  • Inventory system, prerequisite for different weapons and equipment rewards
  • Map system, prerequisite for the Follow Quest tracker marker
  • Enemy types and camps, for the liberating-nation mechanic
  • Horse riding
  • Boss encounters
  • Multiple weapon types