Experiment - Three.js / Character Design

Bunny Carrot Chase

On building a clay bunny from pure geometry, and what the carrot designs reveal about the game underneath.

2026

When artists model a character, they drag vertices. They push and pull until the shape feels right. The feedback is immediate, physical, intuitive.

When you model in Three.js, no Blender, no .obj files, primitives only none of that is available to you. You look at a reference image. You estimate a ratio. You write a number. You re-render. You look again.

It is a different skill. Not better or worse. Slower in some directions, more precise in others. Every dimension is a decision you have to consciously make and write down.

Modelling from a reference image in code means reasoning about proportions, not dragging vertices.

The bunny, piece by piece.

// SphereGeometry, CylinderGeometry, and a lot of 0.05 increments

The brief was a clay-style render - round white body, stubby ears, two black dot eyes, sitting pose. Simple to look at. Harder to recreate without a sculpting tool.

BodySphereGeometry scaled (1, 0.85, 1.1). Slightly flat on the vertical, slightly deep front-to-back. What stops it reading as a perfect ball - which is what nobody looks like.
HeadSmaller sphere. 0.95 units up from centre, 0.3 units forward. The forward offset is everything - it makes the head sit in front of the body, not on top of it. That single number is the difference between a bunny and a snowman.
EarsTwo CylinderGeometry primitives, each rotated ±0.18 radians outward. Close enough to read as ears. Far enough apart to have personality. Any wider and they look alarmed. Any narrower and they look like a hat.
EyesTwo spheres at 0.055 unit radius. Positioned entirely by feel. Not by formula. The face only clicked when the eyes were in the right place, and you know right when you see it.

Four iterations.

Not twenty. Not two. Four. This is important because most people assume character modelling in code will take forever, and abandon it before discovering the iteration count is actually small - if you reason carefully between each one.

01
A shape. Technically correct. Reads as a white blob with ears attached.
02
An animal. The head offset started working. Something with intention was emerging.
03
Close. The proportions were landing. The face still wasn’t right.
04
The bunny in the reference image. Immediately. Not gradually - the moment the last proportion clicked, the whole thing resolved at once.
The moment the proportions landed, it was immediately a bunny. Not before.

The carrots were the real design work.

// three types, three visual languages, recognisable from any angle at any scale

The bunny needed to be charming. The carrots needed to be instantly legible mid-game, from any angle, at any scale, under movement. That is a harder problem than charm.

Colour alone does not solve it. Colour helps, but if two carrots have the same silhouette, players hesitate. Hesitation in a 30-second game is a design failure. Each carrot needed a distinct shape language, not just a distinct hue.

OrangeCone inverted, three leaf spheres at the top. The most readable silhouette universally understood as carrot. It is the baseline. Familiar on purpose.
GoldRich yellow body with two spinning torus rings, one tilted. Five sparkle dots orbiting. Kinetic. You see it moving before you see its colour. Unmistakably different.
PurpleDeep violet with three ribbed bands, lilac leaves, a five-orb crown rotating at the top. Looks like a magical artefact. It should it only appears when you earn it.

The visual design of each carrot is not decorative. It is functional documentation of the game’s rules. Orange means: always here, always available, standard effort. Gold means: fleeting, urgent, look fast. Purple means: you did something different to summon this.

Players learn this without being told. That is the game design working correctly.

Why no external models.

The constraint was not a limitation it was a choice that held throughout the project. One HTML file. No build tools. No external assets. Open on any phone.

Importing a .obj file breaks that contract. The moment you have an external dependency, you have a loading state, a failure state, a hosting problem. Three primitives and some math have none of those problems.

There is a broader principle here. The projects that ship cleanly are usually the ones where every constraint was chosen deliberately, not inherited accidentally. Constraints you choose become features. Constraints you inherit become bugs.

One .html file. Open on any phone. No install.

That sentence is the entire technical philosophy of the project. Every modelling decision, every geometry choice, every carrot design - it all serves that sentence. The bunny exists inside that sentence. So does the radar chart. So does the behavioural layer.

When you know what you are making, you know what you are not making. That clarity is what makes the thing ship.

Play Bunny Carrot Chase ↗