Guide 9 — Commission Witch
Sylvia Lee
What you're building
A gothic-Halloween platformer. A witch takes an order, gathers ingredients across the level while avoiding ghosts, brews the potion, and gets a different ending depending on what she collected.
From the whiteboard in class: order received → gather ingredients → mix and recipe check. That's your loop, and this guide builds it in exactly that order.
Yours is the first guide where most of the work is creating assets rather than placing objects. You'll make ItemTypes and Recipes in the Project window, and the game emerges from how they're configured.
Time: about three hours.
1. Before you start
- Finish Guide 0.
- Read Guide B §B1 (player) and §B2 (managers).
- One new input action:
Interact. See section 6.
2. Scripts you'll use
Core — Guide B
PlayerController2D · PlayerAnimatorBridge · Health · DamageOnContact · Checkpoint · RespawnManager · CameraTargetAssigner · SceneLoader · GameManager · AudioManager · HUDController · MovingPlatform · ParallaxBackground
Collectibles
ItemType · Collectible · CollectionTracker
Enemies (Tier 2)
FloatingEnemy · EnemyPatrol
Level & flags (Tier 4)
Door · LevelExit · ChoiceFlagManager
Interaction & endings (Tier 6)
Interactable · InteractionPrompt · EndingSelector · (optionally DialogueData + DialogueRunner)
Your signature mechanic (Tier 8)
Recipe · RecipeChecker
3. Create your ingredients first
Everything else references these, so make them now.
Right-click in Project → Create → GAME1303 → Item Type, once per ingredient. Suggested starting set:
| Asset name | Display Name | Score Value |
|---|---|---|
Nightshade | Nightshade | 0 |
Moonpetal | Moonpetal | 0 |
Graveroot | Graveroot | 0 |
Toadstool | Toadstool | 0 |
RottenToadstool | Rotten Toadstool | 0 |
That last one is your bad ingredient — the answer to the whiteboard question "are there bad ingredients?" is yes, and it looks almost like the good one. That's the point.
⚠️ Don't rename these assets later. The save system matches ItemTypes by file name. Renaming after playtesting silently orphans anything already collected.
Put them in Assets/MyGame/ScriptableObjects/.
4. Create a recipe
Right-click → Create → GAME1303 → Recipe. Name it CalmingDraught.
| Field | Value |
|---|---|
| Potion Name | Calming Draught |
| Customer Request | "Something for the sleepless. Nightshade and moonpetal, and mind you don't bring me anything rotten." |
| Required | (size 2) |
| → Element 0 | Item = Nightshade, Amount 2 |
| → Element 1 | Item = Moonpetal, Amount 1 |
| Forbidden | (size 1) → RottenToadstool |
| Extras Allowed | 1 |
| Payment | 10 |
| Set Flag On Success | draught_delivered |
| Set Flag On Failure | draught_ruined |
How grading works
When the witch brews, RecipeChecker decides between four outcomes:
| Result | When |
|---|---|
| Ruined | any forbidden ingredient is carried |
| Failed | something required is missing |
| Passable | everything required, but more than Extras Allowed spare items |
| Perfect | everything required, nothing forbidden, within the extras allowance |
Extras Allowed 1 is the interesting number. At 0 the player must collect exactly the recipe and nothing more, which makes greed actively punishing. At 3 they can hoover up everything. Start at 1 — enough slack to be forgiving, tight enough that grabbing every mushroom you see is a bad idea.
Make two or three recipes so different playthroughs want different ingredients.
5. Build the level
The witch
Guide B §B1, with Health Max 3 and Invulnerability 1.
The ingredients in the world
- Empty GameObject
NightshadePickup+ Sprite Renderer. - Circle Collider 2D, Is Trigger ✓.
- Collectible:
- Item Type →
Nightshade - Amount 1
- Bob ✓
- Item Type →
- Save as a prefab. Repeat per ingredient.
Make the rotten toadstool look similar to the good one. A slightly different tint is enough. A player who has to look carefully is a player paying attention.
The ghosts
Your pitch: ghosts move around, but don't touch them.
- Empty GameObject
Ghost, Tag →Enemy, sprite. - Rigidbody2D — Gravity Scale 0, Freeze Rotation Z ✓.
- Capsule Collider 2D.
- Health — Max 1, Destroy On Death ✓ (they can't be killed in your game, but it avoids oddities)
- FloatingEnemy:
| Field | Value |
|---|---|
| Path | Waypoints (or Wander) |
| Speed | 1.5 |
| Bob Height | 0.25 |
| Bob Speed | 2 |
| Waypoints | two or three empties, dragged out of the ghost |
| Flip To Face Direction | ✓ |
- DamageOnContact — Target Tag
Player, Damage 1, Knockback Force 6.
Save as a prefab. Ghosts are obstacles with a schedule, not enemies you fight — which suits a gathering game much better than combat would.
Level furniture
- Ground on
Groundwith colliders KillZoneunder everythingCheckpoints- Camera per Guide B §B3
- Optionally a
Doorrequiring an ingredient count, to gate a back area
6. The order board and the cauldron
First: the Interact action
Input actions asset → Player map → + → Interact → Button → bind E and gamepad South. Save Asset.
On the player
Add InteractionPrompt:
| Field | Value |
|---|---|
| Prompt Prefab | see below |
| Screen Space | ✗ |
| Format | [E] {0} |
| Block While Dialogue Open | ✓ |
The prompt prefab: GameObject → UI → Canvas, set Render Mode → World Space, scale it down to about 0.01, add a TMP text child. Drag it into the Project as a prefab, then delete it from the scene. One of these serves your whole game.
The order board
- Empty GameObject
OrderBoard+ a noticeboard sprite. - Box Collider 2D, Is Trigger ✓, a bit bigger than the sprite.
- Interactable:
| Field | Value |
|---|---|
| Prompt Text | Read the order |
| Prompt Offset | (0, 1) |
| One Shot | ✗ |
| Sfx Name | paper |
- On Interact → drag the
Cauldronin →RecipeChecker.SetRecipe, then drag yourCalmingDraughtasset into the parameter slot.
The cauldron
- Empty GameObject
Cauldron+ sprite. - Box Collider 2D, Is Trigger ✓.
- Interactable — Prompt Text
Brew, One Shot ✗. - RecipeChecker:
| Field | Value |
|---|---|
| Current Recipe | (leave empty — the board sets it) |
| Consume On Brew | ✗ while testing, ✓ later |
| Brew Sfx | brew |
| Success Sfx | success |
| Fail Sfx | fail |
| Success Effect | a green particle burst |
| Fail Effect | a puff of black smoke |
- On the cauldron's
Interactable, On Interact → dragCauldron→RecipeChecker.Brew.
Consume On Brew off while testing. With it on, a failed brew eats your ingredients and you have to re-gather to try again. Turn it on once the game works — it makes failure sting, which is the right call for the finished game and the wrong one while you're debugging.
7. The endings
- Empty GameObject
EndingSelector+ the EndingSelector component. - Add three Endings. Order matters — first match wins, so hardest at the top.
Element 0 — the best ending
| Field | Value |
|---|---|
| Ending Id | master |
| Display Name | Master of the Craft |
| Require Flags | draught_delivered, !draught_ruined |
| Counter Name | brews_perfect |
| Min Counter Value | 3 |
| Load Scene | Ending_Master |
Element 1 — the middle ending
| Field | Value |
|---|---|
| Ending Id | competent |
| Display Name | A Living, Anyway |
| Counter Name | brews_perfect |
| Min Counter Value | 1 |
| Load Scene | Ending_Competent |
Element 2 — the fallback
| Field | Value |
|---|---|
| Ending Id | struggling |
| Display Name | Perhaps Another Trade |
| (no conditions at all) | |
| Load Scene | Ending_Struggling |
One ending must have no conditions, or a player who matches nothing gets stuck.
Where the counters come from
RecipeChecker adds to brews_perfect, brews_passable and brews_ruined automatically on every brew. You don't wire that — you just read them here. That's how "different endings based on how many ingredients you collect" resolves without any new code.
- Add a
ChoiceFlagManagerto the scene — the counters live there. - Make three simple ending scenes (a Canvas with some text is plenty) and add them to Build Settings.
8. The HUD
- Canvas → three heart Images (
Heart1–3) top-left. - A TMP text per ingredient you want counted, e.g.
NightshadeCount. - Empty child
HUDwith HUDController:- Heart Icons: all three, plus Heart Full / Heart Empty sprites
- Item Counters: one entry per ingredient — Item Type, its Text, Format
{0}
A small icon next to each count makes it readable at a glance. HUDController fills the icon automatically from the ItemType if you assign one.
9. Wire it up
| On this object | Event | Drag in | Choose |
|---|---|---|---|
Player → Health | On Died | RespawnManager | RespawnManager.OnPlayerDied |
Player → Health | On Health Changed | HUD | HUDController.UpdateHealth (Dynamic) |
CollectionTracker | On Count Changed | HUD | HUDController.UpdateItemCount (Dynamic) |
OrderBoard → Interactable | On Interact | Cauldron | RecipeChecker.SetRecipe → drag the Recipe asset in |
Cauldron → Interactable | On Interact | Cauldron | RecipeChecker.Brew |
Cauldron → RecipeChecker | On Perfect | AudioManager | PlaySFX (string) → success |
Cauldron → RecipeChecker | On Ruined | AudioManager | PlaySFX (string) → fail |
LevelExit → LevelExit | On Completed | EndingSelector | EndingSelector.ChooseEnding |
Row 8 is what triggers the ending. If you'd rather the game end at the cauldron instead of a level exit, wire RecipeChecker.OnBrewResult there instead.
10. First playtest
- Walk, jump, take damage from a ghost.
- Pick up an ingredient — the counter goes up.
- Press E at the order board — the Console should show the recipe being set (add a temporary TMP text wired to
RecipeChecker.OnFeedbackto see messages). - Press E at the cauldron with nothing — you should get "Still missing: …".
- Gather the exact recipe and brew — Perfect.
- Gather the recipe plus three extras and brew — Passable.
- Carry a rotten toadstool and brew — Ruined, regardless of everything else.
- Reach the level exit — an ending scene loads.
Wire OnFeedback to a TMP text early. It gives the player (and you) an actual sentence explaining what went wrong, which turns brewing from guesswork into a puzzle.
11. Then add
- More recipes and more ingredients. Your game's variety lives here, and it's all asset creation — no new scripts.
DialogueRunnerso the order board says the request in the customer's voice rather than showing a bare list. Guide 10 §5 has the panel recipe.- Animation — Guide B §B6.
Doorrequiring an ingredient count, gating a harder area.- Ghost variety — one that wanders, one on a fixed path, one that follows a long slow route past the best ingredients.
ParallaxBackground— a gothic treeline. Very on-theme.
12. Design notes for your game specifically
The rotten ingredient is the best thing in your design. It turns gathering from "grab everything" into "look carefully" — one asset that changes how the player moves through the whole level. Make it visually similar but not identical, and put it somewhere tempting: right next to a good one, or somewhere that looks like a reward for a difficult jump.
Extras Allowed is your greed dial. It's the number that decides whether the game is about collecting or about collecting exactly right. I'd keep it tight. A witch who brings the apothecary a bag of random mushrooms should get a worse potion.
Ghosts as obstacles rather than enemies is the right call. Your witch gathers; she doesn't fight. Ghosts on fixed patrol routes make the level a timing puzzle, and that fits the fiction better than a combat system would. Resist adding an attack.
Your endings are already written by the counters. Because RecipeChecker counts perfect and ruined brews automatically, you can add ending conditions without touching gameplay — a "never ruined a single brew" ending is just one more entry with brews_ruined at 0. Cheap, and it rewards a way of playing rather than a score.
Three brews for the best ending is a good target. It means the player has to succeed repeatedly rather than once, which makes the second and third orders feel like they matter.
13. Common problems
| What you see | What's wrong |
|---|---|
| No prompt appears near the board | InteractionPrompt missing on the player, or Prompt Prefab unassigned, or the object isn't a trigger |
| E does nothing | Interact action missing from the input asset |
| Brewing says "no order to fill" | You didn't read the order board first, or SetRecipe isn't wired |
| Brew always says missing ingredients | Ingredient prefabs point at the wrong ItemType assets |
| Everything grades as Passable | Extras Allowed is 0 and you're carrying spares — check your counts |
| Rotten ingredient doesn't ruin it | It isn't in the recipe's Forbidden list |
| Ingredients disappear after a failed brew | Consume On Brew is ticked — untick while testing |
| Ending never fires | LevelExit.OnCompleted → ChooseEnding not wired |
| "No ending matched" warning | No fallback ending with empty conditions |
| Counters never update | ChoiceFlagManager missing from the scene |
| Ghosts fall out of the sky | Gravity Scale isn't 0 |