Objectives & progression
Intermediate Last updated Jul 9, 2026
On this page
Everything between “accept” and “turn in” is described here: how objectives are credited, how conditions gate content, how effects mutate state, and how quests link into chains.
Objectives
A trackable goal lives inside a step. The step uses a generic id (Goal, Deliver), while
the concrete kind lives in objective.type, so you can change the kind without re-keying anything:
- id: Goal
objective:
type: Kill
target: MyGame.Enemy.Wolf # GameplayTag, MUST be registered
count: 3
location: MyGame.Area.Farm # optional area gate
desc: "Kill the wolves near the farm"
next: Deliver
| Type | Meaning | How it’s credited |
|---|---|---|
Kill | kill N tagged enemies | your death code reports a Kill event |
Collect | gather N tagged items | your pickup code reports a Collect event |
Reach | reach a tagged place | a Reach event, or CompleteObjective from a trigger |
TalkTo | talk to someone | automatic: starting a conversation credits it |
Deliver | report back to the giver | satisfied by the turn-in act itself |
Custom | anything else | your game reports Custom + your own tag |
Two notable conveniences:
TalkTotargets a speaker key, not a tag (target: Mary, the same key used inspeakers:and the Cast tab). Any conversation with that NPC credits the objective for the talking player only, and only while the objective is active, so talking to the NPC before accepting the quest correctly credits nothing.Delivernever blocks the turn-in. It exists so the tracker shows a “report back” line, and it is auto-ticked when the quest is turned in.
Crediting from your game is one call (Blueprint or C++), server-authoritative and idempotent
per EventSourceId:
Comp->ReportObjectiveEvent(Instigator, EQuestwrightObjectiveEventType::Kill,
MatchTags, LocationTags, EventSourceId);
Tag matching is hierarchical: an objective with target: Enemy.Wolf is credited by an
event tagged Enemy.Wolf.Alpha, so there is no need to list every subtype.

GameplayTags: the one critical rule
Every target, location, choice require/grant, condition tag and effect grant_tag
value is a GameplayTag that must already be registered in your project; otherwise it
resolves to an invalid tag and is silently ignored. This is the #1 cause of “my objective
never counts”. Register tags either way:
- Native C++ (preferred for shipped tags):
UE_DEFINE_GAMEPLAY_TAG_COMMENT(...). - Project Settings → Project → GameplayTags (writes
Config/DefaultGameplayTags.ini).
Use a dotted hierarchy (MyGame.Enemy.Wolf, MyGame.Area.Farm) so hierarchical matching works
for free. The only exception: a TalkTo target is a speaker key, not a tag.
Conditions
Conditions gate quest availability (available_when) and step entry (require). Atomic kinds:
| Form | Meaning |
|---|---|
{ quest: <QuestId>, state: <state> } | another quest is in a state (available, active, completed, failed, declined) |
{ objective: <ObjId>, state: <state> } | an objective is active / complete / failed / inactive |
{ flag: <Name>, equals: <Value> } | a per-playthrough quest variable equals a value |
{ tag: <GameplayTag> } | the player owns a gameplay tag |
{ item: <ItemId>, min: <N> } | inventory has ≥ N of an item (via your inventory provider) |
{ attribute: <AttrId>, min: <N> } | a stat is ≥ N (via your stat provider) |
Composites: { all: [...] } (AND), { any: [...] } (OR), { not: <cond> }.
available_when:
all:
- { quest: Region.Intro, state: completed }
- { not: { flag: Betrayed, equals: "true" } }
Effects (on_end:)
Applied when a scene exits:
on_end:
quest: accept # accept | complete | decline | fail | turnin
set: { RewardTier: Negotiated } # write flags, read back by flag conditions
grant_tag: Region.MetBran # grant gameplay tag(s) to the player
grant_quest: Region.FollowUp # auto-accept follow-up quest(s)
A turn-in scene typically ends with on_end: { quest: turnin } plus end: completed.
Branching on player choices
To make the giver react to an earlier choice, set a flag when the choice happens, then author
two turn-in steps: the flag-gated one first, the catch-all last. The giver routing picks
the first turn-in whose require passes:
- id: Haggle
scene: { ... }
on_end: { quest: accept, set: { RewardTier: Negotiated } }
- id: DeliverHaggled # specific branch; keep it before the catch-all
require:
all:
- { objective: Goal, state: complete }
- { flag: RewardTier, equals: Negotiated }
scene: { ... }
on_end: { quest: turnin }
end: completed
- id: Deliver # default branch, carries the tracked objective
require: { objective: Goal, state: complete }
objective: { type: Deliver, count: 1, desc: "Report back" }
scene: { ... }
on_end: { quest: turnin }
end: completed
The shipped demo quest uses exactly this pattern (a haggle leads to a different pay-out scene).
Quest chains
A chain is just data; there is no separate asset:
- Quest B’s
available_when: { quest: A, state: completed }(prerequisite), and/or - Quest A’s
on_end: { grant_quest: B }(auto-accept the follow-up).
priority orders multiple quests offered by one giver (higher first). The Studio’s New
Chain UI writes these values for you by drag & drop; hand-authoring is equivalent. Validation
catches chain cycles and dangling quest/objective references.
Barks
Ambient barks (barks:) play over the giver’s head on a randomized timer; idle barks
(idle_barks:) play on interact when the giver has nothing left to offer. Both go through the
same voice-over and face pipeline in the giver’s voice:
barks:
interval: [8, 16]
lines:
- "Wolves howling again..."
- { text: "After dark, nobody sets foot in the yard.", weight: 2 }
idle_barks:
lines:
- "Rest easy. The farm's safe, thanks to you."
Rewards
rewards:
base:
currency: 20
xp: 50
items:
- { def: SmokedMeat, count: 1 }
currency, xp and items are recorded on turn-in; your game grants the actual goods through
the inventory provider seam (see Runtime integration).