← Back to projects

// Personal Project · Game · Case Study

Mew Brew title screen with the game's logo and main menu

Mew Brew

Drop, merge and grow the roundest cats. A cozy physics merge puzzle I designed and programmed in Godot, from the merge logic to the level system, and shipped to itch.io as a game you play straight in the browser.

Role
Design & programming
Type
Browser game (also Windows)
Stack
Godot 4.5 · GDScript
Status
Released on itch.io

01. Play it

Drop cats into the mug. When two cats of the same kind touch, they merge into a bigger, rounder cat, all the way up to level 8. If cats stay piled above the rim for too long, it's game over. Move with A / D and drop with Space.

Runs in your browser via itch.io. Nothing downloads until you press Play.

02. How it plays

Beyond the endless mug, the game has a level-select map styled as a recipe book: a tutorial, four goal-based levels and an Endless mode. Each level has its own goal, like reaching a score or creating a certain cat.

The recipe-book level map with the tutorial, four levels and Endless mode
The level map, styled as a recipe book.

When the mug gets crowded there are two power-ups: Delete removes a single cat, and the Rat runs through the mug and shuffles everything around. Progress saves automatically, and there are volume and fullscreen settings.

The Rat power-up running through the mug and knocking the cats around
The Rat power-up shaking up a crowded mug.

03. The tricky part: merging inside a physics engine

Merges are detected on RigidBody2D contacts. That sounds simple, but both cats in a pair get the contact signal, often in the same frame, so without guards the same pair would merge twice and spawn two big cats. Each cat flags itself (and its partner) as merged the moment a merge starts, and freshly spawned cats ignore contacts for a short moment.

The second problem was an error from the physics engine on every merge. The contact callback runs in the middle of the physics step, where new bodies can't be added. Spawning the merged cat with a deferred call moves it to just after the step, and the error went away.

func _on_body_entered(body):
    if has_merged or freeze or not can_merge:
        return
    if body is RigidBody2D and body.has_method("get_cat_level"):
        if body.get_cat_level() == cat_level and not body.has_merged and not body.freeze:
            merge_with(body)

func merge_with(other: RigidBody2D):
    has_merged = true
    other.has_merged = true
    var merge_pos = (global_position + other.global_position) / 2
    game.spawn_merged_cat.call_deferred(cat_level + 1, merge_pos)
    queue_free()
    other.queue_free()

Lightly trimmed from the real cat.gd: game stands in for the game scene node, and the level-cap check and scoring are left out.

04. How it's built

Data-driven levels

A level manager defines each level as data: score goals, cat-level goals, or endless mode. Adding a level means adding an entry, not a scene.

Shaders & particles

Custom shaders for the cat-removal effect and the menu, and heart-shaped particle bursts generated from a parametric heart curve.

Global singletons

Autoloads handle game state and saving, music that carries across scenes, and an options panel available from anywhere.

Web export

Exported to HTML5 / WebAssembly with threads turned off, so it runs on itch.io without SharedArrayBuffer. Also exports to Windows.

05. Getting it ready to ship

Getting from "works on my machine" to a public release was its own chunk of work:

  • Fixed a save/load bug caused by an off-by-one level index.
  • Removed dead code and debug output, deleted unused assets and duplicate scenes, and fixed editor-only errors.
  • Set up a Web export that works on itch.io without SharedArrayBuffer, and hid the Exit button in browser builds.
  • Designed the itch.io page: cover, banner, themed colors and screenshots.
The Level Cleared screen
Level cleared.