Skip to main content

Building Whisker — from idea to itch.io

Ten days · three people · 15 commits · released July 31, 2026

This is the build log for Whisker — the COSMOS final project. The commit history is public. Every date and milestone below is sourced directly from it.

Jump to: The idea · The design document · Team and GitHub workflow · Three branches, three phases · The order delivery problem · What I built for the first time · Release day


The idea​

Week 1 of COSMOS introduced Zubek's design framework: a designer starts from the feeling they want the player to have, then works backward to the mechanics that produce it.

The feeling I wanted: cozy but curious. A game that felt safe — warm kitchen, friendly animal customers, gentle music — but had an undercurrent you kept pulling at. Six weeks later, that was the design brief I brought to the COSMOS final project.

Working backward from the feeling:

  • Cozy meant the kitchen had to be readable at a glance. A 2.5D perspective — 3D space, 2D character art — gave depth without losing the soft visual style. That was the first design decision, before any code.
  • Curious meant the customers couldn't just be obstacles. Each one needed dialogue that hinted at a larger story. The mystery — who is taking the community garden's produce? — was the thread that connected the orders to something that actually mattered.

The baking loop followed: drop ingredients into the dough pot in the middle of the kitchen. Simple enough to pick up immediately. Layered enough that paying attention to which customer wanted what felt like the game was talking to you.


The design document​

Before I wrote a line of code, I wrote the design document.

It's in the repository's first commit, alongside the Unity project files. The design document was the team contract — concept, mechanics, structure, cast — committed before any scene existed. If a teammate asked "what are we building?" the answer was a link, not a conversation.

The document established:

  • A cozy 2.5D baking game with a cat baker and animal customers
  • Core mechanic: ingredient drop into a central dough pot
  • Structure: two levels, a customer quota per level to trigger each story beat, leading to two different endings
  • Cast: four named characters — Willow, Finn, Mia, and Charlie — each with dialogue that moved the mystery forward

One thing the document forced early: if the mystery was going to work, the dialogue had to be real. Customers couldn't say generic things. Four characters meant four distinct voices and four interlocking story threads. That committed us to specific scope — and it's what gave the art team something concrete to design to from day one.


Team and GitHub workflow​

Three people. I owned the repository and managed the integration point.

Division of work:

RoleResponsibility
Yisi (design lead + programmer)Design doc, Unity setup, all gameplay scripts, all scene architecture, GitHub repository
Teammate ACharacter sprites, animal customer art, UI visual design
Teammate BDialogue scripts, character voice, story arc continuity

All 15 commits are from my GitHub account — teammates contributed assets outside of git and handed them off for me to import. That's visible in the repo: there's an Assets/Imported Assets/ folder that was the integration point for team deliveries. When a new character sprite arrived, I imported it, wired it to the right prefab, and committed.

Three things made this work without overwriting each other:

  1. Feature branches for each development phase. Three pull requests — not just commits to main — structured the work into clear phases with defined deliverables.
  2. Teammates never touched .unity scene files. Art came in as imported assets; code stayed with me. Unity's .unity files are hard to merge — keeping scene authorship on one person eliminated that class of problem entirely.
  3. Daily commits with descriptive messages. The commit history is a readable log of what changed and why. When a bug appeared after a merge, I could trace which commit introduced it in minutes.

Three branches, three phases​

The GitHub repository has three feature branches, each merged via pull request. They're the actual structure of how the game was built.

Phase 1 — feature/adding-player-mechanics-and-customers (merged July 22)​

Goal: A complete end-to-end skeleton — player can move, pick up ingredients, the game has a start and a win screen.

What shipped in this branch:

  • Player character with working animations and the core baking mechanic
  • Start screen scene (character select)
  • Level 1 scene reorganized into Scenes/core game/
  • Win screen with a completion sound effect (tadaa-47995.mp3)
  • Dialogue system connected to the level

By the time this PR merged, a player could start the game, move through the kitchen, complete one order, and see an end state. The loop worked. That's the only goal of Phase 1 — a rough but continuous playthrough, so Phase 2 has something to polish.

Phase 2 — feature/add-in-story-and-polish-game (merged July 26)​

Goal: Customer AI is real, Level 1 is playable, structure is in place for the remaining levels.

What shipped in this branch:

The customer AI got its full overhaul in this phase. The commit on July 24 is the largest single day of work: "Polished the customer ai, player controls, added in a tutorial message, polished the kitchen, currently working on interactions with the customer." That day added:

  • Customer prefabs (the animal characters as Unity prefabs with NavMesh agents)
  • Food prefabs (carrot cake, croissant, honey pie — specific named items, not placeholders)
  • Customer AI with three states: walking to a table, waiting for an order, leaving when done or when patience runs out

Customer AI was the system I spent the most time on. Making NPCs that felt like they actually wanted something — walked to a specific spot, displayed their order, reacted when you served them wrong — was what separated a "kitchen demo" from a "baking game."

The order delivery system went through multiple iterations during this phase. The full breakdown is documented below.

By the end of Phase 2, Level 1 was complete and playable. Levels 2–5 were the Phase 3 target.

Phase 3 — feature/add-in-final-bits-of-story-for-other-levels-and-sound (merged July 28)​

Goal: All levels built, narrative choices in, audio in.

What shipped in this branch:

On July 27, four new Unity scenes appeared in the repo in a single commit: level2Whisker, level3Whisker, level4Whisker, level5Whisker. Five levels total, each with its own NavMesh bake and customer layout.

The July 28 merge commit added what made the mystery actually work:

  • Choice scenes (choice1.unity through choice5.unity) — interactive narrative branch points where the player's response to a customer shapes the story arc
  • Audio: click.mp3 (button feedback), oven.mp3 (cooking sound), take.mp3 (pickup sound)
  • All five levels linked end-to-end with narrative continuity

The choice scenes are the part most people don't see unless they play through more than once. There are five of them — one per level transition — and they're how the two different endings diverge. The mystery isn't just background flavor. The player's choices during conversations are what determine whether the ending resolves the story or leaves it open.


After the branches: the final two days​

July 30 — credits and a jazz loop​

The day after the showcase, one more commit: credits scene, a final cutscene scene, and a background jazz track (sergequadrado-luxury-jazz-loop-312713.mp3). The music is the piece most players comment on — it's the thing that makes the kitchen feel like a place rather than a level.

This commit also fixed per-character story scenes for Charlie and Finn. The showcase version had all five levels connected; this pass completed the individual character arcs.

August 10 — version 1.0.0​

Ten days after the COSMOS showcase, a clean final commit: "Complete version 1.0.0 of Whisker."

Version 1.0.0 is what's on itch.io. The showcase build was the demo. This was the release.


The order delivery problem​

The mechanic: a customer arrives with an order, the player picks up the right ingredient and delivers it. Simple to describe. The implementation went through four versions before it worked.

The full iteration history is preserved in finishOrder.cs as commented-out code blocks — a record of every approach that didn't work.

Version 1 — FindWithTag:

GameObject heldF = GameObject.FindWithTag(itemTag);
if (heldF == null) { Debug.Log("No food"); }
else { ScoreManager.Instance?.AddPoints(1000); Destroy(heldF); }

Searches the entire scene for any object with the matching tag. Works in a test scene with one food item. Breaks the moment there's bread on the counter across the kitchen — the delivery registers even if the player never touched it.

Version 2 — FindWithTag + CompleteOrder callback:

Added the AI callback so the customer would react. Still using scene-wide search. The underlying model was wrong, I just hadn't noticed because the test kitchen was sparse.

Version 3 — OverlapSphere:

Collider[] nearbyColliders = Physics.OverlapSphere(transform.position, deliveryRadius);
foreach (Collider collider in nearbyColliders) {
if (collider.tag == itemTag) { heldF = collider.gameObject; break; }
}

Proximity check instead of scene-wide. Better — but still asking "is there food near the customer?" A player could throw food across the kitchen and land it adjacent to the customer. That gets credit. It also means a customer who walked past the ingredient pile could accidentally complete their own order.

Version 4 — HeldObject check (what shipped):

PlayerMovementFixed player = FindObjectOfType<PlayerMovementFixed>();
if (player == null || player.HeldObject == null) {
GetComponentInParent<customerAI>()?.TriggerGreeting();
} else if (!player.HeldObject.CompareTag(itemTag)) {
Debug.Log("Bad kitty!");
GetComponentInParent<customerAI>()?.WrongOrder();
} else {
Destroy(player.HeldObject.gameObject);
GetComponentInParent<customerAI>()?.CompleteOrder();
Destroy(gameObject);
}

The question shifted: not "is there food near the customer?" but "is the player carrying the right food?" Once I framed it that way, the implementation was obvious: get the player's HeldObject reference and check its tag.

This also completed the feedback loop. Three states, three customer reactions:

  • Carrying right food → order complete, customer leaves happy
  • Carrying wrong food → WrongOrder() reaction
  • Carrying nothing → TriggerGreeting() (the customer just says hello)

Without that third state, clicking a customer while empty-handed was a silent void. Adding the greeting made the interaction feel intentional from every angle.

The lesson I took: I had been asking the wrong question all three times. Each version fixed the symptom I could observe, not the model underneath it. Writing down "what state does the player need to be in?" before starting version 1 would have gotten to HeldObject immediately.


What I built for the first time​

COSMOS was two weeks. The engines I knew going in were Unity (personal projects), plus a crash course in Bitsy, GameMaker, Ren'Py, and Godot during Week 2. Whisker pushed past all of that.

I knew NavMesh existed in Unity, but had never used NavMeshAgent for multiple NPCs with independent destinations running at the same time. Each Whisker customer navigates to a specific table slot, waits there with a patience timer, and routes to the exit when done or when time runs out — not just "walk toward the player."

Getting this right required three things I hadn't dealt with before:

  • Baking the NavMesh correctly for irregular kitchen geometry so customers didn't cut through counters
  • Getting agents to stop precisely at a destination point (the default stopping distance needed tuning per prefab)
  • Wiring the three-state FSM — Walking → Waiting → Leaving — so agents handed control off cleanly between states without fighting each other

The customer AI is the heaviest script in the project. It's also the one I'm most satisfied with.

Multi-scene architecture with SceneManager​

The COSMOS bootcamp ran everything in a single scene. Whisker has fifteen connected scenes: start screen, five levels, five choice scenes, credits, final cutscene, two endings. Wiring SceneManager.LoadScene() to game events and UI, and making sure each scene loaded into the correct state regardless of which path got there, was new ground.

The mistake I made early: I kept game-state information in MonoBehaviours on scene objects. When a scene unloaded, the information vanished. The fix was moving persistent state to DontDestroyOnLoad objects — singletons that survived scene transitions and carried context forward.

Branching narrative through scene routing​

The five choice scenes (choice1.unity–choice5.unity) aren't dialogue variations on a single level — they're separate Unity scenes the player routes through based on decisions made during customer conversations. Which ending loads depends entirely on the path through those scenes.

This required thinking about something I hadn't designed before: narrative state. What does the player know at this point? What choices have they already made? How does this choice scene know where to send them next? Working that out for five branch points, two endings, and four character arcs taught me that branching narrative is a data structure problem as much as it is a writing problem.

Singleton as deliberate architecture​

customerDialogue.Instance and ScoreManager.Instance use the singleton pattern to give any script in the game a single shared reference to these systems. I had seen singletons in tutorials but had never consciously chosen them as the right tool for a real constraint.

The constraint here: the dialogue box is one object, but fifteen scenes need to reach it — wiring a per-scene reference would mean setting it up in every scene. The singleton solved that. Choosing the pattern because it fit the problem, rather than copying it from an example, was a different kind of understanding.

Camera-relative movement (the PlayerMovementFixed story)​

The file is named PlayerMovementFixed.cs. The "Fixed" in the name is honest. Three iterations are preserved in the file as commented-out blocks — the full record of a control scheme that needed two rewrites before it felt right.

Version 1 was a standard 3D controller: mouse look for camera pitch and yaw, arrow key rotation, CharacterController with an Animator blend tree using horizontal, vertical, and run-state parameters. It worked in solo testing because I knew which way the character was facing at all times.

The first external playtest revealed the problem. Whisker uses a fixed overhead camera. Players who moved the character and then pressed W expected to move "up the screen" — away from the camera. The Version 1 controller moved the character in the direction it was facing, which after any rotation was sideways or backward relative to the screen. No one could figure out the controls.

Version 2 stripped out mouse look and arrow-key rotation. Still broken — the movement was world-relative, not camera-relative.

Version 3 (final) — the fix was two lines:

Vector3 camForward = Vector3.ProjectOnPlane(Camera.main.transform.forward, Vector3.up).normalized;
Vector3 camRight = Vector3.ProjectOnPlane(Camera.main.transform.right, Vector3.up).normalized;
Vector3 move = camRight * inputX + camForward * inputZ;

Vector3.ProjectOnPlane flattens the camera's forward vector onto the horizontal plane, so "press W" always means "move toward the back of the screen" regardless of where the character is facing. The character still rotates to face the movement direction — but the movement itself is driven by the camera, not the character's body.

Version 3 also dropped the Animator blend tree for a simpler sprite-based animator, added a ThrowMoveLock timer (a brief movement pause after throwing so a misclick doesn't send the player running), and blocked jumping while holding an item.

The lesson from three versions: a pattern correct for one context — a 3D game with a free camera — can be actively wrong for another. The instinct to grab a working tutorial controller and build on it is reasonable. The discipline to throw it out and rewrite when the context changed was what actually shipped.


Release day​

▶ Play Whisker on itch.io — released July 31, 2026.

The last day of COSMOS was a campus showcase — laptops out on the lawn, everyone playing everyone else's games. We uploaded to itch.io the morning of the showcase. The link was in the README, the README was on the screen.

A player who wanted to keep playing after the showcase had a URL before they left the room.

That's the line between a prototype and a shipped game. It was deliberate.


What I'd do differently​

The three-branch structure worked, but Phase 3 was the most compressed. Four levels in one commit on July 27 meant the individual level layouts weren't as distinct as they could have been. Levels 2–5 share a lot of structural decisions that were rushed rather than considered. Starting level architecture earlier — even with placeholder art — would have made the final week less about building and more about finishing.

The order delivery problem took three wrong versions. Each one fixed the visible symptom. Writing down the model question first ("what state does the player need to be in?") is now how I start any interaction system.

Daily commits paid off. When something broke during the Phase 2 polish pass, the commit history made it traceable. This wasn't good luck — it was the habit from Week 2 carrying into the final project, exactly as intended.