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
- Finish Guide 0.
- Read Guide B §B1 (player) and §B2 (managers).
- One new input action:
Interact.
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:
DialoguePanel— anchor to the bottom, stretch horizontallyLineText— enable Wrapping, set Overflow → Overflow, font size ~24ChoiceContainer— add a Vertical Layout Group, set Spacing 8, tick Control Child Size → WidthPortraitImage— set Preserve Aspect ✓
The choice button prefab
- Right-click Canvas → UI → Button - TextMeshPro. Name it
ChoiceButton. - Make it wide and short. Set the text to
Choice text goes here. - Drag it into
Assets/MyGame/Prefabs/. - Delete it from the scene.
The runner
- Empty GameObject
DialogueSystem(anywhere in the scene). - 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 |
DialogueRunnerhides the panel on Awake, so leave it visible in the editor.
Test it now, before writing any dialogue
- Create a throwaway
DialogueData(right-click → Create → GAME1303 → Dialogue) with two lines. - Add a
DialogueTriggerto an empty GameObject with a trigger collider: drag the data and the runner in, tick Trigger On Start. - 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:
- Go To Line −1 means "just continue to the next line."
- Go To Line 5 on lines 3 and 4 means both branches rejoin at line 5.
- Require Flags on a line skips it entirely if the condition fails — the cheapest way to make a character react to earlier events.
- Choices with Go To Line −1 end the conversation.
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
- Empty GameObject
Photograph+ sprite. - Box Collider 2D, Is Trigger ✓.
- 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 |
- On Interact → drag
DialogueSystem→DialogueRunner.StartDialogue→ drag yourHall_FirstMemoryasset 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.
- Empty GameObject
WorldState+ the WorldStateSwapper component. - 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 |
- 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
PuzzleSwitch: sprite + trigger collider + the component. Activation Touch or Stand, One Shot ✓. Assign On/Off sprites.Door: sprite + solid collider + the Door component. Requirement None, Open Mode Slide, Open Offset (0, 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
- Empty GameObject
EndingSelector+ the component. DragDialogueSysteminto Dialogue Runner. - 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 |
- Set Wait For Epilogue ✓ so the closing dialogue plays before the scene changes.
- 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
- The dialogue panel appears and text types out.
- Pressing E mid-typing shows the whole line at once.
- Pressing E again advances.
- A line with choices shows buttons, and clicking one jumps to the right line.
- A flag set by a choice persists — check by adding a line with Require Flags on it.
- The world tint changes when a memory flag is set.
- A flag-gated door opens once you have the flag.
- 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
- Write the game. Your systems are done after section 10 — everything else is
DialogueDataassets and level layout. - Portraits for the speakers in your conversations.
- A type sound — set
Type Sfxon the runner. Subtle, and it does a lot. ParallaxBackgroundfor the mansion depth.- Ambient audio that changes with world state — wire
WorldStateSwapper.OnStateChanged. - 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 |