Guide 10 — Anabasis

Andraya Rhoads


What you're building

A 2D narrative game. You play a ghost stuck between life and death who longs to remember who they were. You explore, make decisions, solve puzzles, and overcome your former regrets to break the curse.

Jax's note in class: dialogue in the game contributes to the imagery of the world changing to fit the choices you made. That's built into this guide as WorldStateSwapper — the mansion literally changes as the ghost remembers.

Heads up for MythoClash and Detective Liz: this guide builds the dialogue panel that both of your games need. Section 5 is what your guide means by "build the dialogue UI as in Guide 10."

Time: about three hours, and budget a full session for the dialogue panel alone. It's the biggest UI job in the class.


1. Before you start


2. Scripts you'll use

Core — Guide B
PlayerController2D · PlayerAnimatorBridge · PlayerAnimatorBridge · CameraTargetAssigner · SceneLoader · GameManager · AudioManager · HUDController · ParallaxBackground

Flags (Tier 4)
ChoiceFlagManager · Door · PuzzleSwitch · LevelExit

Narrative (Tier 6)
Interactable · InteractionPrompt · DialogueData · DialogueRunner · DialogueTrigger · EndingSelector · WorldStateSwapper · PushBlock

Optional: Health and RespawnManager — see section 3.


3. One decision first: can the ghost die?

You're already dead. That's not a joke — it's a design question worth settling before you build.

No health (recommended). Remove Health and RespawnManager entirely. Your obstacles become puzzles and locked doors rather than damage. The game is about remembering, not surviving. Cleaner, and it fits the fiction.

With health. Add both, and something in the mansion can hurt you. More conventional, and it gives you a use for FloatingEnemy if you want threatening presences.

This guide assumes no health. If you want it, follow Guide B §B1 exactly and add the two components back.


4. Name your flags before you build anything

This is the single most useful ten minutes you'll spend on your game.

Everything in your build — dialogue, doors, world changes, endings — talks through flags: named facts about the playthrough. Write the list down first.

Starting set:

met_the_ghost_in_the_hall
found_the_photograph
remembered_the_fire
remembered_the_promise
remembered_the_betrayal
opened_the_east_wing
forgave_yourself
refused_to_forgive

Conventions: lowercase, underscores, past-tense facts. remembered_the_fire reads better than memory3 in six weeks' time.

Keep this list somewhere you can see it. Every flag typo is a silent failure — a door that never opens, a line that never appears, with no error message.

Then: empty GameObject ChoiceFlagManager in the scene, with Persist Across Scenes ✓.


5. Build the dialogue panel

Take this slowly. Build the whole thing, then test with one line before writing any real dialogue.

The structure

Create this under a Canvas (GameObject → UI → Canvas):

Canvas
└─ DialoguePanel              (UI → Panel, anchored bottom, ~30% screen height)
   ├─ PortraitImage           (UI → Image, left side, square)
   ├─ SpeakerText             (UI → Text - TextMeshPro, above the box)
   ├─ LineText                (UI → Text - TextMeshPro, fills the box)
   ├─ ContinueIndicator       (UI → Image, small arrow, bottom-right)
   └─ ChoiceContainer         (empty UI object + Vertical Layout Group)

Settings that matter:

The choice button prefab

  1. Right-click Canvas → UI → Button - TextMeshPro. Name it ChoiceButton.
  2. Make it wide and short. Set the text to Choice text goes here.
  3. Drag it into Assets/MyGame/Prefabs/.
  4. Delete it from the scene.

The runner

  1. Empty GameObject DialogueSystem (anywhere in the scene).
  2. Add DialogueRunner:
Field Drag in
Panel DialoguePanel
Line Text LineText
Speaker Text SpeakerText
Portrait Image PortraitImage
Continue Indicator ContinueIndicator
Choice Container ChoiceContainer
Choice Button Prefab your ChoiceButton prefab
Characters Per Second 40
Typewriter ✓
Type Sfx (leave empty for now)
Blip Every N Chars 3
  1. DialogueRunner hides the panel on Awake, so leave it visible in the editor.

Test it now, before writing any dialogue

  1. Create a throwaway DialogueData (right-click → Create → GAME1303 → Dialogue) with two lines.
  2. Add a DialogueTrigger to an empty GameObject with a trigger collider: drag the data and the runner in, tick Trigger On Start.
  3. Press Play.

You should see the panel appear, text type out, and the panel close after the second line. Fix this before going further — everything else in your game depends on it.


6. Write a conversation

Right-click → Create → GAME1303 → Dialogue. Name it Hall_FirstMemory.

Write it on paper first

Seriously. Number your lines 0, 1, 2… and draw arrows for the branches. Branching dialogue is far easier to type in than to invent in the Inspector.

A worked example

# Speaker Text Set Flag Go To Choices
0 A photograph, face-down on the floor. −1
1 You know this frame. You don't know why. found_the_photograph −1
2 Turn it over? −1 2 choices ↓
3 A woman. Laughing. The fire hadn't happened yet. remembered_the_fire 5
4 You leave it where it lies. Some things stay buried. 5
5 The hall feels colder than it did. −1

Line 2's choices:

Choice text Go To Line Set Flag
Turn it over. 3 remembered_the_fire
Leave it. 4 (none)

How the fields work:

Reacting to the past

Add this as line 6 with Require Flags = remembered_the_fire:

You've seen her face now. The hall knows it too.

A returning player who turned the photograph gets that line; one who didn't never sees it and never knows it existed. That's most of what a narrative system does, and it costs one field.

Compound conditions

The Require Flags field takes a comma-separated list, and ! means "must NOT be set":

remembered_the_fire, !refused_to_forgive

Same syntax everywhere in the kit.


7. Put interactables in the world

On the player

Add InteractionPrompt (Guide 9 §6 has the prompt prefab recipe — a small world-space Canvas with a TMP text).

An examinable object

  1. Empty GameObject Photograph + sprite.
  2. Box Collider 2D, Is Trigger ✓.
  3. Interactable:
Field Value
Prompt Text Examine
One Shot ✓ (a memory recovered once)
Require Flags (optional gating)
Set Flag On Interact (optional)
Used Sprite a "disturbed" version, if you have one
  1. On Interact → drag DialogueSystem → DialogueRunner.StartDialogue → drag your Hall_FirstMemory asset into the parameter slot.

That's the whole pattern. Every memory in your game is one of these.

A conversation the game imposes

For opening narration or a ghost that appears when you enter a room, use DialogueTrigger instead: trigger collider, drag in the data and the runner, One Shot ✓.

Interactable = the player chooses. DialogueTrigger = the game decides.


8. Make the world change

This is Jax's idea, and it's the thing that will make your game feel different from a visual novel.

  1. Empty GameObject WorldState + the WorldStateSwapper component.
  2. Add States. Last matching state wins, so go from base to most-transformed.

Element 0 — the base

Field Value
Name Faded
Require Flags (empty)
Colour Tints your wall/floor SpriteRenderers → a washed grey-blue

Element 1

Field Value
Name Remembering
Require Flags remembered_the_fire
Colour Tints the same renderers → warmer, slightly amber
Show Objects a lit fireplace object
Sprite Swaps a cracked portrait → an intact one

Element 2

Field Value
Name Sharp
Require Flags remembered_the_fire, remembered_the_promise
Colour Tints full colour
Show Objects furniture, curtains
Hide Objects the dust sheets
  1. Set Transition Time to 1.5 so colours fade rather than snap.

It re-checks automatically whenever a flag changes — no wiring needed.

Start desaturated and add colour as memories return. It's the most legible version of "the world changes with your choices," and it costs you nothing but tint values.


9. Puzzles

A switch and a door

  1. PuzzleSwitch: sprite + trigger collider + the component. Activation Touch or Stand, One Shot ✓. Assign On/Off sprites.
  2. Door: sprite + solid collider + the Door component. Requirement None, Open Mode Slide, Open Offset (0, 3).
  3. Wire PuzzleSwitch.OnActivated → Door.Open.

A door gated on a memory

On the Door: Requirement Flag, Required Flag remembered_the_promise. Add a proximity trigger child and drag it into Proximity Zone so it checks when you walk up.

Wire Door.OnOpenFailed to a DialogueTrigger or a text hint — a locked door that says "You don't remember what's behind this yet" is a signpost; a silent one is a bug.

A push block

PushBlock: sprite + Rigidbody2D + solid collider + the component, Mode Physics.

Wire RespawnManager.OnRespawned → PushBlock.ResetPosition if you have a respawn manager. Without health you don't, so instead consider placing blocks where they can't be pushed somewhere unrecoverable — or add a reset button to your pause menu.


10. The endings

  1. Empty GameObject EndingSelector + the component. Drag DialogueSystem into Dialogue Runner.
  2. Add endings, hardest first:
# Id Require Flags Epilogue Load Scene
0 truth remembered_the_fire, remembered_the_promise, remembered_the_betrayal, forgave_yourself Epilogue_Truth Ending_Truth
1 partial remembered_the_fire, remembered_the_promise Epilogue_Partial Ending_Partial
2 lost (none) Epilogue_Lost Ending_Lost
  1. Set Wait For Epilogue ✓ so the closing dialogue plays before the scene changes.
  2. Wire LevelExit.OnCompleted → EndingSelector.ChooseEnding.

The ending list

Every ending seen sets the flag ending_seen_<id>. Your mood board has an ENDING LIST in the menu — that's just a set of flag checks. A Canvas with three text entries, each shown or hidden based on ending_seen_truth and friends. Ask your instructor for the four-line script, or use a WorldStateSwapper on the menu scene with one state per ending flag.


11. Wire it up

On this object Event Drag in Choose
Each Interactable On Interact DialogueSystem DialogueRunner.StartDialogue → drag the DialogueData in
PuzzleSwitch On Activated Door Door.Open
Door On Open Failed AudioManager PlaySFX (string) → door_locked
LevelExit On Completed EndingSelector EndingSelector.ChooseEnding
DialogueRunner On Dialogue Started (optional) pause ambient audio, dim lights

WorldStateSwapper needs no wiring — it watches the flag manager itself.


12. First playtest

  1. The dialogue panel appears and text types out.
  2. Pressing E mid-typing shows the whole line at once.
  3. Pressing E again advances.
  4. A line with choices shows buttons, and clicking one jumps to the right line.
  5. A flag set by a choice persists — check by adding a line with Require Flags on it.
  6. The world tint changes when a memory flag is set.
  7. A flag-gated door opens once you have the flag.
  8. The ending selector picks the right ending — test by setting flags manually in ChoiceFlagManager's Starting Flags list.

Step 8 is worth doing deliberately: put remembered_the_fire and remembered_the_promise in Starting Flags, play, reach the exit, and confirm you get the partial ending rather than truth or lost.


13. Then add

  1. Write the game. Your systems are done after section 10 — everything else is DialogueData assets and level layout.
  2. Portraits for the speakers in your conversations.
  3. A type sound — set Type Sfx on the runner. Subtle, and it does a lot.
  4. ParallaxBackground for the mansion depth.
  5. Ambient audio that changes with world state — wire WorldStateSwapper.OnStateChanged.
  6. More world states — one per memory rather than three broad ones.

14. Design notes for your game specifically

Your mood board is monochrome with bursts of red and gold. WorldStateSwapper's colour tints are exactly the tool for that — start almost fully desaturated and let each recovered memory bring back a colour. The player will feel the change before they consciously notice it.

One-shot interactables suit a memory game. A memory recovered stays recovered. Tick One Shot on every examinable and assign a Used Sprite so the world visibly accumulates evidence of where you've been. A room you've fully explored should look different from one you haven't.

Require Flags is your best writing tool, not just a mechanic. A line that only appears if the player already knows something lets you write a character who remembers them. Use it for small touches — a ghost who greets you differently the second time — rather than only for big branches. The small ones are what make it feel alive.

Decide what "breaking the curse" means before you write the endings. Your pitch says overcoming regrets. The flag list in section 4 assumes forgiveness is a choice the player makes explicitly (forgave_yourself / refused_to_forgive). That's a strong structure: the best ending needs both knowing the truth and accepting it, which are different things and can be separately earned.

Budget more time than you think for writing. The systems here are the easy part. A branching conversation that reads well is slow work, and it's the actual content of your game. Build one room properly and completely before you build five rooms roughly.


15. Common problems

What you see What's wrong
Panel never appears Panel not assigned on DialogueRunner, or the DialogueData has no lines
Text appears instantly Typewriter unticked, or Characters Per Second is 0
Can't advance dialogue InteractionPrompt missing on the player — it forwards the key to the runner
Choices don't appear Choice Container or Choice Button Prefab unassigned
Choice buttons stack on top of each other No Vertical Layout Group on the container
Choice jumps to the wrong line Go To Line is an index — line 0 is the first. Count carefully
Conversation ends early A line's Require Flags fails and every line after it also fails
A conversation only plays once One Shot on the DialogueData — untick it, or that's intended
Flags don't persist No ChoiceFlagManager in the scene, or Persist Across Scenes unticked
World never changes Flag name typo — check spelling against your list
Tint snaps instead of fading Transition Time is 0
"No ending matched" No fallback ending with empty conditions
Ending fires immediately An early ending has no conditions — move the fallback to last

16. Checklist