Dekiro Dev Blog

Part 5 of 28

Everything from Code: 177 Monsters, 203 Items and a Town Built in Headless Blender

No hand-modelling: every model in the game comes out of a Python script, and the first batch looked like wooden dolls.

The constraint #

Our developer is an AI working through a terminal. It can't drag vertices around in Blender's viewport, and the human isn't a 3D artist either. For the first few days every monster was a capsule. The design doc planned for free low-poly asset packs later.

Then the question changed: if Claude Code can write a Godot game, can it write a Blender script that builds a wolf? Blender runs headless (blender -b -P script.py) and its Python API can do anything the UI can. The first test was a single Giant Rat. Less than an hour later there was a commit titled "Low-poly models for all 77 monsters: parametric Blender builders (quadruped, humanoid, critter) from one spec table". It changed 165 files.

Specs, builders, a loop #

The pipeline has three layers.

1. A spec table (tools/blender/monster_specs.py) says which body each monster gets and how it looks. Base templates keep the variants short:

WOLF = {"body": "quadruped", "length": 0.92, "leg": 0.46, "girth": 0.155, "depth": 0.165, "head": 0.125, "snout": 0.2, "chest": 1.1}
	"wolf": with_(WOLF, main="#7d7d80", belly="#b4b4b6"),
	"grey_wolf": with_(WOLF, main="#9a9a9e", belly="#d0d0d2", length=0.98),
	"blue_wolf": with_(WOLF, main="#4f6a9a", belly="#9ab0d8", eye="#80d0ff", eye_glow=2.0),
	"alpha_wolf": with_(WOLF, main="#5e5e66", belly="#8e8e96", chest=1.3, extras=["mane"], eye="#ffcc33", eye_glow=2.0, length=1.05),

2. Builders turn a spec into a rigged, animated mesh. There are four: quadruped (dogs, wolves, cats, bears, boars, deer), humanoid (goblins, orcs, cultists, skeletons, demons, shades), critter (rats, spiders and other small things) and stone for the Shadow monoliths. Each takes a vocabulary of options. The humanoid builder alone knows five body frames, 15 outfit pieces, 16 features from tusks to wings, and 14 weapon types.

3. A build loop (build_monsters.py) reads the game's own data/monsters.json for each monster's name and height, runs the right builder, and exports a .glb:

	for mid in ids:
		spec = dict(monster_specs.SPECS[mid])
		builder = BUILDERS[spec.pop("body")]
		height = float(spec.pop("height", known[mid]["height"]))
		spec["name"] = known[mid]["name"].replace(" ", "")
		lp.clear_scene()
		arm = builder(spec, height)
		out = os.path.join(ROOT, "assets", "models", mid + ".glb")
		os.makedirs(os.path.dirname(out), exist_ok=True)
		lp.export_glb(out)

The first 77 built in about ten seconds. With --preview <dir> it also renders one image per monster, and contact_sheet.py puts them all on one sheet. That sheet is how the AI reviews a whole bestiary at a glance: it opens a single PNG and looks for anything off.

Contact sheet of the four-legged monsters (after the organic rebuild)
Wolf, grey wolf, blue wolf and alpha wolf: one spec, different numbers

Conventions instead of fix-ups #

The Godot side, ModelVisual, needs no per-model tweaks because the builders follow fixed conventions: meters, feet at z = 0, facing glTF forward, and four animations named idle, walk, attack and death. Two conventions turned out to matter for feel:

  • Every attack animation lands its blow on frame 4. ModelVisual knows this (STRIKE_TIME := 4.0 / 24.0) and skips most of the wind-up for melee hits, so the blow lands right as the damage number appears. For telegraphed attacks it does the opposite and stretches the wind-up over the warning time.
  • Walk speed travels with the model. Each armature carries the speed its walk cycle covers as a glTF extra (export_extras=True), so feet don't slide whether a rat scurries or a bear lumbers.

Headless machines (dedicated servers, tests) skip the meshes and keep the capsule. That's the "logic separate from visuals" rule from the first day paying off: the server never needs to know a wolf has a tail.

Shadow stones, items and a town #

The same approach spread to everything else that has a shape.

Shadow stones: 11 carved monoliths, each stacked from three chunks on bones plus up to four floating shards, so they can shudder when hit, surge when a wave spawns and crumble on death. The glowing runes use a material named Glow, which the game recolours per Shadow type and pulses.

Items: build_items.py builds a model and an inventory icon for every item (139 then, 203 now). Godot exports the item list first (including generated skillbooks and runes), so Blender and the game always agree on what exists. Weapons get shapes from a spec table (swords became katanas with a curved blade, temper line, tsuba and wrapped grip). The Fullmoon Sword, a nod to a popular Korean MMO, has its own hand-written builder.

Buildings: East Asian low-poly houses (hanok, minka, Chinese gabled), temple halls, pagodas, torii gates, stone lanterns, a roofed well and guardian lions. Each is built at a canonical size and scaled in Godot to the footprint the map asks for, so map data keeps saying "a house, 7 x 6 m, here".

Flora: 27 variants of trees, plants and rocks at first (33 now, including snowy pines). Their materials are named by role (Leaf, Bark, Needles, Moss...), and Godot reads those names to decide what sways in the wind, what takes the map's colour and what glows. One mesh can serve a green Vale and a snowy Frostpeak.

Textures follow the same idea. The leaf cards and grass blades are pixel art drawn by tools/flora_textures.py with Pillow, leaf by leaf.

Shadow stones at night, each with runes in its Shadow's colour
Generated weapon icons for three of the four schools
The town: houses, the temple hall and stone lanterns from the building builder

What went wrong #

The roofs you could see through #

After we added a pixel-texture shader to every model,1 See the art post. roofs became transparent from below and from some angles. The cause: Blender's roof material was double-sided, and our material swap only made single-sided shader materials. Thin roofs lost their back faces. The fix kept a two-sided variant of the shader for any material that had culling disabled:

	var two_sided := base.cull_mode == BaseMaterial3D.CULL_DISABLED

A second commit closed the roofs underneath with a wooden soffit, so you never look up into an empty roof even when the camera dips.

The Pinocchio dolls #

The bigger lesson is in the docstring of the helper library that every builder uses:

A creature is a set of simple primitives (spheres, cones, cylinders), each rigidly bound to one bone of an armature (one vertex group per part), joined into a single skinned, flat-shaded mesh.

That approach was fast, robust and easy to parametrize, and it got 77 animated monsters into the game in an evening. But rigid parts on bones move like a jointed wooden doll. Limbs are separate cones and spheres, and there is no flesh that bends. They earned the nickname Pinocchio dolls. Fine as placeholders, not what the game should look like.

The fix was one of the first jobs handed to a parallel subagent:2 More on that setup in the coordinator post. an organic rebuild, with continuous bodies skinned to their bones instead of separate primitives. It landed in two commits. The four-legged animals got "one smooth skinned body per animal, painted 128 px pixel fur, per-family shapes (canine, bear, boar, cat, toad, crocodile)". The critters followed: "segmented, smooth, painted: spiders, scorpions, beetle, bat, snake, eye, wisp". The spec tables, the build loop, the conventions and every test that checks "each monster has a model with its animations" stayed the same. Only the builders changed. The humanoid monsters (goblins, orcs, cultists, demons) still use the primitive builder; rebuilding them is queued, not done.

The same wolf as a Pinocchio doll of primitives (left) and after the organic rebuild (right)

Six days later: the pipeline at scale #

The same approach kept absorbing new content. At the time of writing, Blender scripts produce:

  • 182 monster and Shadow stone models (177 monster types and 11 Shadow types share them). Dungeon monsters mostly reuse existing bodies, scaled, recoloured and given one accessory (a crown, horns, armor plates), which was the human's call when 20 dungeons were planned: "We can also just scale up some of the other models we have and recolor them?" Each dungeon's final boss gets its own silhouette instead: a crone under a huge hat with a cauldron on her back, a sexton whose head is a bronze bell, a king with a furnace in his back.
  • 203 item models and icons, including 41 armor pieces that a balance pass found missing.
  • 97 building models: five house styles plus a tall house, teahouse, shop, storehouse, granary, farmhouse and guard tower, each in regional variants (desert, snow, swamp, ember), after a detail pass that added tiled roofs, timber frames, trade signs and weathering in vertex colours, all on one shared material.
  • 53 clutter models (crates, barrels, carts, market stalls, laundry) and 12 animal models for the living-world pass.
  • 458 dungeon kit pieces in 20 themes, from crypt stone to a hedge maze to a palace of mirrors.

The pipeline also gained a second output. The player characters are still modelled in Blender by a script (characters_v4.py), but they never ship as 3D models. They're rendered into 8-direction pixel sprites, which is its own post.

Why generate instead of buy? #

Asset packs would have been faster for the first few monsters. Generating had other advantages:

  • Consistency: one palette logic, one level of detail, one style across every monster, item and building.
  • Data alignment: heights come from monsters.json, item lists from items.json. When a monster is added to the data, its model is one spec line away, and a test notices if it's missing.
  • Change at scale: "swords are katanas now" touched 181 files in one commit: every sword model and icon, rebuilt.
  • No licence questions: everything is original.

The cost is that quality is bound by what can be described in code. That's exactly what the Pinocchio lesson showed.

Takeaways #

  • Blender is a library. Headless Blender with a Python spec table is a real content pipeline, even for animated characters.
  • Fix conventions, not models. Facing, scale, animation names and the strike frame are agreed once, so the engine side never needs per-model fixes.
  • Review in bulk. A contact sheet of every model is the fastest way for anyone, human or AI, to spot the odd one out.
  • Fast primitives are a placeholder, not a style. Plan the step from rigid parts to skinned bodies.

What's next #

With models in place, the world needed space. Next: why we built a seamless open world, switched it off nine hours later, and built Map 1 from a hand-drawn sketch.