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


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

  1. Empty GameObject NightshadePickup + Sprite Renderer.
  2. Circle Collider 2D, Is Trigger ✓.
  3. Collectible:
    • Item Type → Nightshade
    • Amount 1
    • Bob ✓
  4. 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.

  1. Empty GameObject Ghost, Tag → Enemy, sprite.
  2. Rigidbody2D — Gravity Scale 0, Freeze Rotation Z ✓.
  3. Capsule Collider 2D.
  4. Health — Max 1, Destroy On Death ✓ (they can't be killed in your game, but it avoids oddities)
  5. 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 ✓
  1. 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


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

  1. Empty GameObject OrderBoard + a noticeboard sprite.
  2. Box Collider 2D, Is Trigger ✓, a bit bigger than the sprite.
  3. Interactable:
Field Value
Prompt Text Read the order
Prompt Offset (0, 1)
One Shot ✗
Sfx Name paper
  1. On Interact → drag the Cauldron in → RecipeChecker.SetRecipe, then drag your CalmingDraught asset into the parameter slot.

The cauldron

  1. Empty GameObject Cauldron + sprite.
  2. Box Collider 2D, Is Trigger ✓.
  3. Interactable — Prompt Text Brew, One Shot ✗.
  4. 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
  1. On the cauldron's Interactable, On Interact → drag Cauldron → 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

  1. Empty GameObject EndingSelector + the EndingSelector component.
  2. 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.

  1. Add a ChoiceFlagManager to the scene — the counters live there.
  2. Make three simple ending scenes (a Canvas with some text is plenty) and add them to Build Settings.

8. The HUD

  1. Canvas → three heart Images (Heart1–3) top-left.
  2. A TMP text per ingredient you want counted, e.g. NightshadeCount.
  3. Empty child HUD with 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

  1. Walk, jump, take damage from a ghost.
  2. Pick up an ingredient — the counter goes up.
  3. Press E at the order board — the Console should show the recipe being set (add a temporary TMP text wired to RecipeChecker.OnFeedback to see messages).
  4. Press E at the cauldron with nothing — you should get "Still missing: …".
  5. Gather the exact recipe and brew — Perfect.
  6. Gather the recipe plus three extras and brew — Passable.
  7. Carry a rotten toadstool and brew — Ruined, regardless of everything else.
  8. 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

  1. More recipes and more ingredients. Your game's variety lives here, and it's all asset creation — no new scripts.
  2. DialogueRunner so the order board says the request in the customer's voice rather than showing a bare list. Guide 10 §5 has the panel recipe.
  3. Animation — Guide B §B6.
  4. Door requiring an ingredient count, gating a harder area.
  5. Ghost variety — one that wanders, one on a fixed path, one that follows a long slow route past the best ingredients.
  6. 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

14. Checklist