Assets

Making game assets

Levels, props, characters and textures finished in Blender and exported as glTF.

Levels, terrain, props and characters are finished in Blender and exported as plain glTF (PBR materials, baked images, KHR_texture_transform) with a collider mesh. Anything a surface needs, a blend, a mask or a decal, is baked into the export.

Looking at an asset

  1. Render every prop before it is placed, and every exported level before it enters the repository, in Blender: four turntable angles, from above and at eye level, beside a 1.8 m figure, with its size printed against the size it should be.
  2. Read the images at full size. A Blender render of the exported glb must look like the game.
  3. When a render shows a defect, measure the export before changing anything: a ray cast down the wrong line (which mesh, what height, what normal), the baked image pulled out of the glb, or a top-down orthographic render of the spot.

The work, in order

  1. Textures. Use real PBR texture sets at real scale, from the library's textures/ (the folder PATCHWORK_LIBRARY names). A material is a set and a size in metres per repeat, measured from the thing: a brick course is 0.09 m, a cobble 0.10 m, a plain roof tile 0.28 × 0.09 m. Non-square maps keep their aspect, and every image in the export is at most 1024 px.
  2. Props. Use real models from the library's props/, looking at its _thumbs first, never a cone, sphere or box where a real rock, log, tree or shrub exists. Take the highest level of detail under about 4,000 triangles, apply its unit scale into the mesh before it is copied, and give it a bounding-box collider. A prop's material size is no larger than the prop. The turntable decides what a prop is, not its tag.
  3. Ground. A heightfield with one material per tile, its colour, normal and roughness baked in Blender from a layer blend: a weight mask painted in metres with a feather, baked per 16 m tile at 64 px per metre. Never a material per face, which cannot feather and shows as stairs. Bake only where the player walks; ground beyond it is one plain material.
  4. UVs. Box projection in one world frame per dominant axis. A frame taken from each face's own normal makes a quilt at the grid.
  5. Placement. Every solid registers its footprint in one clearance list, and every placement tests against that list. Each base samples all four footprint corners and stands on the lowest. A spawn, portal, NPC or den is at least 1.5 m from every footprint.
  6. Export. A glb for what is drawn, a {v, i} collider JSON (y up, floors facing +y, no ceiling), and a landmarks JSON (spawns, portals, NPCs, dens, lights, bounds). Build twice and compare the three files with cmp; they must be identical.
  7. Bake. The build script's plain glb is never shipped. The game's games/<game>/assets/<game>.bake.ts, run by yarn bake <game>, runs the script into a scratch file and packs it into the game with gltfpack: meshopt geometry and WebP textures, with node and material names, extras and float positions kept (-cc -kn -km -ke -vpf -tw). It cuts each loose texture to WebP. The raw inputs stay in the library, and the game commits the built files and the scripts. yarn health checks Blender, the library, gltfpack and ImageMagick first.

Checking it

The build script measures the exported mesh, not the values it planned from, and prints one line per check:

LinePasses when
CLEAR n=<solids> overlaps=0No two solids' boxes overlap.
STAND objects=<n> max_gap=<m>No object stands more than 0.01 m off the ground.
COPLANAR faces=<n> pairs=0No two faces in any orientation lie within 0.004 m of each other over more than 0.005 m.
TEX <material> set=<set> size=<m>Each rock, masonry and plank material names its set and size.
BAKE obj=<ground> tiles=<n> px=1024The ground's tiles are baked.
REPRO identical=3Two builds give the same three files.

Then render the exported glb in Eevee at 1280×720 under the level's own lights and a weak fill:

  1. A three-quarter view from above; eye level at every spawn along its facing; every portal from 6 m.
  2. Every ground boundary from 1.5 m, and from 3 m above beside a 1.8 m cube. A step is a fail.
  3. A rock or wall from 1.5 m under raking light: relief visible, no panel seams.
  4. From below the ground for holes and inverted faces, and the edge of the level from inside.
  5. An albedo pass, lamps hidden and a white world, when a seam might be shading. A seam that remains is in the texture or UVs.

Last, check that every landmark is on ground and reachable, then take one screenshot in the game. It must agree with the Blender render; if it does not, the asset is wrong.