Writing quests

Beginner Last updated Jul 9, 2026

On this page

A quest is one .quest.yaml file. The file is the source of truth: the Studio compiles it into assets, and the Quest tab is a view of it, so hand-editing and constructor-editing are always equivalent. This page walks through the format; the complete field-by-field reference (also written to double as an AI prompt) is available as a download.

Where files live

Put quests in <Project>/Content/Quests/; they appear in the Studio library automatically. Name them <Name>.quest.yaml, one quest per file.

Minimal skeleton

quest: Region.MyQuest          # unique id/slug (required)
name: "My Quest"
giver: Bran                    # speaker key of the NPC who gives it
source_culture: en
steps:                         # required, at least 1
  - id: MyQuest_Offer
    scene:
      speakers: [Bran, Player]
      lines:
        - { by: Bran,   id: greet, text: "Hello.", emotion: neutral }
        - { by: Player, id: hi,    text: "Hi." }
      choices:
        - { id: accept, text: "I'll help.", goto: MyQuest_Do }
      on_end: { quest: accept }
  - id: MyQuest_Do
    end: completed

The golden rules

  1. Stable lowercase ids on every line, choice and objective. Ids key localization and the generated voice/face assets; inserting content must never renumber existing ids.
  2. Only use documented fields, emotions, shots, condition kinds and effect verbs. The validator catches anything out of contract, with jump-to-source on every finding.
  3. Every GameplayTag value must be registered in your project (native C++ or DefaultGameplayTags.ini). An unregistered tag fails silently, which is the #1 cause of “my objective never counts”. See Objectives & progression.

Steps

A step is one node of the quest: a dialogue scene, a trackable objective, or a terminal marker.

FieldPurpose
idRequired, stable. Auto-namespaced to <ShortQuest>_<id>.
sceneA dialogue scene (lines + choices)
objectiveA trackable goal: kill / collect / reach / talk / deliver
nextDefault transition to the next step (when the scene has no choices)
endTerminal marker: completed, declined or failed
requireEntry gate, e.g. gate the turn-in step on the objective being complete

Scenes: lines and choices

scene:
  speakers: [Bran, Mira, Player]   # every speaker key present in this scene
  lines:
    - { by: Bran,   id: greet, text: "Come here.", emotion: worried, shot: cu }
    - { by: Mira,   id: warn,  text: "Don't trust him.", to: Player }
    - { by: Player, id: what,  text: "What's wrong?" }
  choices:
    - { id: help,   text: "I'll help.",      goto: MyQuest_Accept }
    - { id: refuse, text: "Not my problem.", goto: MyQuest_Refuse }
  on_end: { quest: accept }

A scene supports any number of speakers: list every key in speakers and set each line’s by. At runtime each key binds to an actor already placed in the level; if none is present, one is spawned from the speaker map written by the Cast stage and staged beside the giver. Spawned characters are removed automatically when the dialogue ends.

Useful line fields:

  • to: names who the line is addressed to; drives over-the-shoulder framing and head-turn in scenes with 3+ speakers.
  • emotion: is one of neutral, happy, sad, angry, afraid, surprised (plus aliases like worriedafraid). Drives the additive facial mood and the VO tone.
  • shot: takes auto (default; the compiler frames the scene), wide, ots, cu, reaction, or the id of a custom camera mark (see Staging & cameras).
  • echo_of: marks a line that repeats a choice’s text; give the choice’s id so they share one localization key.

Choices can gate and grant GameplayTags: require: makes a choice available only if the player owns the tag(s), grant: awards tags when picked.

Dynamic text

Text supports {PlayerName} and custom {Token} placeholders, substituted at display time in a localization-safe way. See Runtime integration for setting token values from your game.

Authoring with an AI assistant

The fastest way is the ready-made Questwright Quest Builder GPT: it already knows the format, so just describe your premise (“a lighthouse keeper asks the player to find his missing brother”) and it answers with a complete .quest.yaml.

Prefer another assistant? The full format reference is deliberately self-contained: paste the reference file into ChatGPT or Claude together with your premise and ask for a .quest.yaml.

Either way, drop the result into Content/Quests/; the validator catches anything out of contract before it ever reaches a build.