Quest System
A node-graph scripting language for narrative flow, built on top of Unreal's Flow Graph. Designers author quests without touching code — and can launch the game at any beat, with everything that came before replayed as world state rather than played out.
The problem
Quest logic in an open-world game is long, stateful, and touches everything: streaming, spawning, cinematics, the player's position, UI. The usual result is that it ends up half in Level Blueprints and half in an engineer's head, and only that engineer can change it safely.
The harder problem is iteration cost. A beat twenty minutes into a questline can only be reached by playing the twenty minutes before it. When testing a change is that expensive, designers stop testing — they make the change, eyeball the graph, and move on. Bugs get found weeks later by someone else.
So the system had two jobs: make quests authorable by designers, and make any point in the game reachable in seconds.
<video autoplay loop muted playsinline>
What I built
Three nested authoring layers, all of them graph assets a designer opens and edits directly:
- Questline — the sequence and branching of quests across the game.
- Quest — one quest's logic. Owns the data layer it needs loaded and where the player starts.
- Quest Beat — a chunk of a quest, authored as its own sub-graph so beats stay readable and independently launchable.
Inside a graph, designers work with a small deliberate vocabulary of node types — Action (does something to the world), Condition, Trigger, Route, and Prepared — rather than a flat pile of generic nodes. The constraint is the point: a node's category tells you what it is allowed to do, which is what makes the debug launch below possible at all.
The designer workflow
- Create a Quest asset from the content browser and author its graph.
- Point the level's World Settings at the questline; drop a Start Quest node in it.
- Right-click any Start Quest node or Quest Beat and choose Play From Here.
- Iterate. Use
Quest.Debug 1to watch questline progress and live node state on screen.
How it works
Built on Flow Graph, not beside it
Quests and questlines are subclasses of Flow's own UFlowAsset, and the custom
nodes subclass Flow's nodes. That decision bought the whole graph editor, node validation,
live debugging and asset tooling for free, and it means designers learn one graph
paradigm rather than a bespoke one. Where the engine's behaviour was wrong for quests I
overrode it rather than forking: a Start Quest node looks and previews like a Sub Graph
node, but executes its quest as a root flow instance, which is what makes quest
lifetime, console commands and streaming waits behave sensibly.
Prepare and Start: transitions without a hitch
The Start Quest node has two input pins. Prepare loads everything the next quest needs — data layers, assets — without running it, so the previous quest decides when loading begins and can cover it with whatever it is already doing. Once loading finishes, a Prepared node fires inside the incoming quest's own graph, which is where per-quest setup belongs. Start then runs it; if Start arrives while loading is still in flight it waits, so a quest can never begin before its world exists.
The prepare state is persisted, so a save taken mid-transition restores into the same state instead of silently losing an in-flight preparation.
The debug launch, and replaying what was skipped
This is the part that does the real work. Launching at a beat means every action before it never ran — so the NPC that should be waiting was never spawned, the door that should be open was never opened, the flag that gates the next branch was never set. A launch into that world is worthless.
Every Action node therefore answers one question: On Debug Skipped — put the world into the state you would have left behind, and do nothing else. No cinematics, no waiting, no gameplay effects. Implementing it is optional per node and added as the need arises, so the cost is incremental rather than a wall of work up front.
Three rules make the replay trustworthy:
- Only the path actually taken is replayed. Each skipped node is handed the output pin it fired on the way to the launch point, so a Spawn node reached through its Despawned output leaves the actor gone rather than spawning it.
- Last write wins. Nodes declare what they act on, and two skipped nodes acting on the same target collapse to the most recent one — spawn an actor early and despawn it later, and the launch leaves it gone.
- A replayed node cannot fire its outputs. See below.
Streaming discipline
Each quest owns the data layer that holds its spawners and triggers. A quest does not start when its layers report activated — it starts when streaming reports genuinely complete, because activation and "the actors exist" are not the same moment, and the gap is where players fall through unloaded geometry. The player teleport therefore also lives inside the quest and runs after that wait, not in world settings before it.
Debug surfaces
Quest.Debug 1 puts an overlay on screen: questline progress, which quests are
running, and per-node state inside them. Alongside it, Quest.Start,
Quest.Finish and Quest.Teleport cover the cases where a designer
wants to jump without leaving the running session. All of it is stripped from shipping.
Quest.Debug overlay: questline position, running quests, and live node state.What it changed
- Testing a late-game beat went from minutes of replay to seconds. Fill in the real before/after number.
- Quest content stopped needing an engineer. New quests are graph assets; new node types are the only thing that reaches C++.
- Quest-to-quest transitions became testable in isolation — a debug launch can enter the real questline part-way rather than running a quest standalone, so the seams get tested, not just the middles.
What I would do differently
Honest limitation or two — the thing a lead will ask about anyway, answered before they ask.