Skip to content
nkh.do
Go back

Boom clone devlogs #4: Ride the Pig

You can ride a pig now.

If you’ve been following along — #1 was rectangles and bots, #2 was sprites and a map editor, #3 was multiplayer going live — then you know we went from “colored squares on a grid” to “actual humans bombing each other over the internet” in about a week. The game works. You can invite friends. You can make enemies. You can join a room and have a perfectly normal competitive experience.

But something was missing. Something fundamental. Something that separates a game from a game.

A pig. You can ride a pig. And a turtle. And a spider that phases through walls. I have delivered.

A mounted character charging through the map

The mounts

Three mount types, each a different relationship with physics:

The Pig. Speed 5. You hop on, you go. The pig has no special abilities and no opinions. It is a pig. It moves. It loves you unconditionally. We should all be more like the pig.

The Turtle. Speed 3. Slower than walking. Slower than thinking about walking. You pick up a turtle mount and your first reaction is “wait, am I being punished?” Here’s the thing: there’s a 20% chance the turtle powerup spawns as the rage turtle instead. Speed 10. Anger incarnate. A slow, dignified creature pushed to its absolute limit by the indignity of being picked up. And if you get the regular turtle — the slow one, the one that makes you question every decision that led you here — you can feed it spicy kelp (more on that later) and transform it into the rage turtle mid-match. The turtle is a gamble. The turtle is an investment. The turtle is a redemption arc with a shell.

The Spider. Speed 10, and here’s where it gets weird: it phases through breakable blocks, bombs, and powerups. Crates? Walks through them. A wall of bombs someone just placed? Strolls past. Powerups on the ground? Ignores them like they’re email from HR. Not indestructible walls — the spider respects structural integrity — but everything else might as well not exist. It is simultaneously the best mount and the most terrifying one, because if you see a spider rider heading toward you through a wall of crates, you have approximately one second to make peace with your god.

When your mount gets hit by an explosion, you don’t die. The mount takes the hit. You’re thrown off and frozen for 1 seconds with explosion immunity — the game’s way of saying “that was close, here’s a moment to reconsider your life choices.” Unless you have a shield active, in which case the shield absorbs the entire explosion and you keep riding. The mount doesn’t even flinch. The shield is the “no” card in a game that’s otherwise all “yes.”

And here’s the part I didn’t plan: the dismount freeze reuses the exact same mechanism as one of the consumables. But more on that in a bit. I built one system and used it twice. Whether that’s efficient or lazy depends on whether you’re reading this or writing it.

The effects bitmap (or: how I learned to stop worrying and love bitwise ops)

The spider mount needs to phase through stuff. Future mounts and future potions will need similar effects. I needed a system where multiple effects can stack, multiple sources can grant the same effect, and removing one source doesn’t accidentally clear another. This is the kind of problem that sounds simple until you’re three features deep and everything is on fire.

Enter the effects bitmap: a single number on each entity where each bit is an active modifier.

const EFFECT_BLOCK_PHASE   = 1 << 0  // pass through breakable tiles
const EFFECT_BOMB_PHASE    = 1 << 1  // pass through bombs
const EFFECT_POWERUP_PHASE = 1 << 2  // walk over powerups without collecting

// Hot path: O(1) bitwise AND
if (entity.effects & EFFECT_BLOCK_PHASE) {
  // this entity phases through crates
}

Spider mount sets all three bits. Pig sets none. A future potion that lets you phase through blocks? Set EFFECT_BLOCK_PHASE. Done. Adding a new effect to the game means: define one constant, update the codepaths that check it. No schema changes, no new fields, no migration.

The part that makes this actually hold up at scale: an effect source list. Multiple sources can grant the same bit, and removing one doesn’t clear the other:

// Spider mount grants block + bomb + powerup phase
sourceList.set('mount', EFFECT_BLOCK_PHASE | EFFECT_BOMB_PHASE | EFFECT_POWERUP_PHASE)

// A potion also grants block phase
sourceList.set('potion', EFFECT_BLOCK_PHASE)

// Entity effects = OR of all sources
// Remove the potion? Block phase stays, because the mount still grants it

One number, O(1) checks on the hot path, composable sources that don’t fight each other. This is the kind of system that makes future features cheap and past decisions look smarter than they were. I genuinely love this bitmap. I will bring it up at parties. I will not be invited back.

Consumables, or: five ways to cheat death

Five consumables. Each one a different relationship with mortality:

Holy Water — pop your own bubble. Trapped → alive, 0.5s explosion immunity. One charge. Use it when the timer’s ticking and nobody’s coming. Nobody’s ever coming. We established this in devlog #1 and the situation has not improved.

Shield — 2 seconds of explosion immunity. Walk through fire. Stand on a bomb like it’s a lawn chair. Keep your mount alive through an explosion because you refuse to let the pig die. The pig trusts you.

Arrow — a projectile in your facing direction at speed 15. Detonates bombs on contact. You can trigger chain reactions from across the map without ever being in danger. This is the medieval archery experience: standing very far away and making things significantly worse for everyone else.

Winged Greaves — jump over obstacles in your facing direction. Frozen at the origin for 0.5s with immunity, then teleport to the first empty tile on the other side. Charges consumed = blocked tiles crossed. And here’s the punchline: this reuses the exact same jumpTimer system as the mount dismount stun. The dismount freezes you in place (origin = destination). The greaves freeze you and relocate. Same codepath, different parameters. Two features, one mechanism. I will accept your applause or your silence, both are appropriate.

Spicy Kelp — only works on the turtle mount. Feed it to your turtle and it transforms into the rage turtle: speed 10, no longer a liability, suddenly the fastest thing on the map. This is the turtle’s redemption arc. The payoff for picking the worst mount. The turtle believed in itself when nobody else did and now it’s going to kill everyone.

Ctrl does the right thing

One key: Ctrl. It does the right thing. Trapped? It pops holy water. Riding a turtle? It feeds it spicy kelp. Got a picked-up consumable sitting in your buffer? It uses that first. The server resolves all of it — the client just sends “use quick item” and the server decides what that means, because we do not trust players to find the correct number key while actively dying. Correctly, as it turns out.

Dying is generous

When you die — enemy contact, bubble timeout, disconnect, doesn’t matter — half your collected powerups fly out of your corpse and scatter across the map. The items literally launch from your body to random empty tiles with a little tween animation. 400 milliseconds of graceful arc per item, 60ms stagger between them, like a very small fireworks display celebrating your failure. Your death is someone else’s shopping spree. Dying is generous.

What’s next

Next time: we give people names. And bank accounts. And a place to spend their money. This is still a game about explosions. I promise.

Until then, the pig is available, the spider phases through your defenses, and somewhere a rage turtle is closing the distance at speed 10. See you in #5.


Share this post on:

Previous Post
Boom clone devlogs #5: The Grind
Next Post
Boom clone devlogs #3: Going Online