Modular 3D Assets
For efficiency, we rely on reusable, modular assets: pieces built to a shared grid, with
deliberate pivots, that snap together in any combination without anyone nudging anything into place.
The payoff is that a room can be assembled in minutes from parts that already exist. The price is that the constraints on this page are not stylistic preferences. A piece that misses the grid by 2 cm, or pivots 10 cm off, does not produce a slightly worse level. It produces a gap that a level designer works around forever.
Make a Kit Piece walks the whole thing in order. This page is the reference behind it.
The numbers everything is built on
Scale
Metric. 1 Blender unit = 1 metre in Unity.
Two things follow, and both are checked:
- Apply your scale before exporting. A mesh scaled in object mode carries that factor into the
.fbxand everything downstream inherits it.MSH002is an error and blocks the export;
the tolerance is0.0001. - Unity import scale is
1.MSH014checks it, but it is enforced by Unity, not Blender,
because it is a property of the importer that Blender cannot see. The postprocessor sets it on
import for any model whose name carries a known type prefix.
Orientation
-Y forward in Blender, +Z forward in Unity. Z is up in Blender. The add-on has guides for
it, and these are the Blender values because Blender is where the check runs.
This also settles a question that comes up on every kit: which corner is "left" is defined by
the forward axis, not by where your viewport happens to be pointing.
Grids & Increments
For easy mix-and-match, everything lands on the grid wherever possible:
Reading that table, the numbers split into three groups:
The grid itself: primary, sub, increments |
A 1 m primary grid with a 0.25 m sub-grid. Dimensions land on 0.1, 0.5, 1, 2, 5, 10 m. |
What a kit is made of: wallSegment, wallThickness, doorOpening, windowOpenings, stairRiser, stairGoing |
Our default wall is 4 m wide, 3 m high, 0.2 m thick. When every wall in a kit shares that, pieces combine, connect and rotate without gaps. |
Who walks through it: characterHeight, eyeHeight, stepHeight, capsuleRadius |
The reason the other numbers are what they are. |
That third group is the one people skip, and it is what makes a space feel right rather than merely
fit together. A 1 × 2.25 m door opening is not arbitrary. It is sized for a 1.8 m character with 1.65 m
eye height walking through it, and every window head lines up with it. A stair with a 0.1875 m riser
and a 0.25 m going is easy to climb because it sits well under the 0.3 m stepHeight, and sixteen
risers make exactly one 3 m storey. Design a kit against a character who is not ours and it will look correct in Blender and
feel wrong the moment someone walks around in it.
Several of these are ranges in practice (wall segments get smaller for detailed rooms, character
height changes with the player-character), so a game can override them in its own
projects/<game>.json. The table above is the base. → Asset Rules (Generated) > Project overrides
Pivots
Where the pivot sits decides how much work every level designer does with your asset for the rest of
its life. Getting it wrong breaks nothing and costs everyone.
MSH009 checks it, within a tolerance of 0.001.
| Class | Pivot | Why |
|---|---|---|
| Prop (chairs, barrels, pillars, anything freestanding) | Bottom-centre, resting exactly on the ground plane at Z=0 in Blender / Y=0 in Unity |
It sits flush on the floor the moment it is dropped in, and a centred pivot rotates it in place without it appearing to move. |
| Wall, fence | Bottom-left corner | A 4 m wall pivoted at (0,0,0) means the next one snaps at (4,0,0). Rotation makes corners trivial. |
| Floor | Top-left corner of the top face | The walking surface has to sit at exactly Y=0, so a bottom-centre prop dropped at Y=0 rests on it perfectly. Corner pivots snap huge grids of floors together by matching corners. |
| Ceiling | Bottom-left corner of the bottom face | Mirrors the floor. The visible underside meets the top of the wall segment at Y = wall height, with no manual offset. A ceiling pivoted like a floor sits one thickness too high. |
| Door, hinged objects | On the bottom, directly below the hinge | Unity rotates around the pivot. A centred pivot makes the door spin like a revolving door. |
| Insert (a window or panel that fills an opening) | Bottom-left of the opening, not of its own bounding box | Its surround overlaps the hole, so its box is bigger than the hole. Pivot on the opening and it drops straight in. |
| Trim (cornice, string course, sill, plinth) | Bottom-left at the wall plane, not at the projection | Bottom-left of its bounding box is a point floating in the air in front of the building. |
| Corner (pier, quoin, pilaster) | Bottom-left, exactly like a wall | A corner continues a run, so it snaps from where the run’s next wall would have started, not from the concave vertex inside it. |
| Hatch (a trapdoor, a floor hatch) | The hinge edge, on the top face | A door in a floor. Its lid finishes flush with the walking surface rather than standing on it, and it turns about a horizontal edge. → Openings |
| Roof (a slope panel) | Bottom-left of the eave edge | A separate class for the pitch check, the tiling axis and the MSH015 exemption. A sloped piece has no axis-aligned cell. → Roofs |
| Gable (the wall that closes a pitched roof) | Bottom-left, like the wall it is | Its top edge has to be the roof’s own pitch, and MSH017 is built so that it cannot check it. → Roofs > The gable is the piece that will bite you |
| Stair (one step of a flight) | Bottom-left | Tiles along a diagonal, which the seam check cannot express, so it reports no axis. |
| Balcony (a projecting slab) | Top-left of the top face, like a floor | Its walking surface still lands at storey level; it is exempt from MSH015 because projecting is the point. → Balconies |
The fourteen classes are the shapes a Kit piece comes in. A Kit piece takes its pivot from
whichever one it is. Everything that is not a Kit piece takes prop:
bottom-centre, including everything a character holds.
Where the hand attaches is a socket on the prefab, not a property of the mesh. That is why there
is no longer a grip pivot here to get wrong.
Insert, trim and corner exist because one element is often several pieces. A facade is
panels, window inserts, a cornice and corner piers, not one wall. → Splitting a Piece
A collection only gets checked once it says which class it is. Set that in the Blender add-on's
Asset Rules panel (N-sidebar). Guessing "prop" for everything would flag every wall in the file on
the first run, which is how a check gets switched off and never switched back on.
Which one is it?
The table above cannot be read backwards. Four of the fourteen classes share bottom-left, so
knowing where the origin goes does not tell you whether the piece is a wall, a corner, a gable, a
stair or a roof. The class also decides four other things besides the pivot: the grid footprint
(MSH015), the connecting faces (MSH013), the tiling axis, and the box New Kit Piece hands you.
So the rules carry a decision ladder: thirteen mechanical questions, read top to bottom, stop at
the first yes, each with the near miss it must not swallow. Does it stand proud of a seam? is
trim; a pilaster stands proud too and runs up a bay rather than along a seam, so it is a wall.
? on the Asset Class panel, or F3 ▸ Which Class?Type the thing you are making (plinth, verge, trapdoor, shutter) and it goes straight to
the class that owns the word. Every class in the dropdown explains itself on hover too.
MSH018 is the check, and it is info on purpose. It can tell you a piece named Plinth is
probably not a wall. Nothing can tell you that a piece named Wall_01 is really a cornice, and no
severity fixes that.
→ Asset Rules (Generated) > The class ladder ·
Asset Rules (Generated) > Every class, and the words that mean it ·
Asset Rules (Generated) > Settled class edge cases
Bevels
Every face that connects to another modular piece is completely flat. No bevel, no rounding, at
all, and it lands on a regular grid increment.
MSH013 checks it. A 2 mm bevel is invisible while you model and a visible seam in a finished
corridor, which is the worst possible ratio of "easy to do" to "hard to notice".
Split a wall horizontally and its top and bottom faces become connecting faces, which they were
not the moment before. That is why a projecting cornice cannot stay on the panel it crowns. It
also means MSH013 names which part has to come off without anyone having to decide.
→ Splitting a Piece > Where the seam goes
Budgets
Two budgets, measured differently and enforced differently. The vertex one blocks an export;
the triangle one warns.
Vertex density
Measured in vertices per square metre of surface, so it scales with the object rather than
punishing big things for being big.
MSH007 is an error and blocks the export.
Since Environment was removed as a category, Prop covers a teacup and a cliff, and the
prop tier is four times the environment tier.
Anything at world scale (trees, cliffs, terrain features, whole one-off buildings) must
declare environment, or architecture for the very largest, instead of taking the Prop
default.
MSH007 will not catch this. The name says Prop, the default for Prop says 10000, and
10000 verts/m² on a cliff is not a budget. This is the one rule on the page that cannot detect its
own most common failure, so it is on you.
environment describes a density class: large built geometry. It kept its name when the
Environment category was removed on 2026-09-12, because the class it names still exists and
Kit is not the only thing in it.
The byCategory mapping is the tier a category takes when nothing says otherwise. Declaring a
tier per asset, or per kit, still overrides it.
Triangle budgets
MSH008 is a warning. A kit piece gets kitPiece; the default is smallProp.
UV channels
Channel 0 is the texture. Channels 1 and 2 carry overlays (see
Modular 3D Assets - Texturing > Additional UV channels). MSH010 reports a channel used for
something other than its assigned purpose.
If a piece will use an overlay, it needs the extra channel before you export, not after.
Unity's Generate Lightmap UVs also writes to channel 1. A mesh that uses overlays and wants
baked lighting has a real collision, and nothing currently resolves it.
This is flagged in the rules repo as something to settle before lightmapping starts on a
project. If you are about to bake lighting on a kit that uses overlays, raise it rather than
working around it quietly.
What blocks an export
Six mesh rules are errors, and error blocks export and import. These are the ones that will
stop you rather than nag you, and all six are ordinary Blender hygiene:
| Rule | Check before you export | |
|---|---|---|
MSH001 |
Collection must have exactly one root object | One root per collection. This is what makes a composite kit file exportable |
MSH002 |
Object scale must be applied | Ctrl+A → Scale. Tolerance 0.0001. |
MSH003 |
Every mesh must have a material | Even a placeholder |
MSH005 |
Every mesh must have a UV map | |
MSH006 |
UV map must be actually unwrapped, not collapsed | A UV map that exists but sits in a single point counts as missing |
MSH007 |
Mesh exceeds its vertices-per-square-metre budget | → Vertex density |
And five warnings worth clearing at the same time: MSH004 empty material slots, MSH009 pivot
position, MSH013 a connecting face that is not flat, and, on a piece that is part of a split
element, MSH015 a footprint that does not fit its grid cell and MSH016 trim that projects too
far. → Splitting a Piece > The footprint rule
The canonical list, with every value, is Asset Rules (Generated) > Meshes.
A new rule always lands as a warning. That is what stops the system becoming the thing everyone
works around. You can add a rule on Monday, see the real backlog on Tuesday, and decide about
blocking later. So a warning today is a plausible error tomorrow.
If an asset genuinely needs to break a rule, that is what exceptions.json is for, and an
exception records who added it, why, and when it gets looked at again.
→ Check Your Work > When a rule is wrong
Kit Modeling Pipeline
One file or many
Kit source files can either be a folder of .blend files, or one .blend holding the whole
kit. For the single-file version, put each piece in its own collection with the correct
collection-export settings, and name the file for the kit:
SM_Kit_AbandonedTavern_01_Kit.blend
Our Blender add-on then exports one .fbx per collection. Remember MSH001: each collection needs
exactly one root object, which is the constraint that makes this work at all.
The trade-off is locking. A composite file is locked as a unit, so one person edits the whole kit
at a time. That is usually fine since kits tend to have a single owner, but it is worth knowing before
planning a week around it. → Version Control > Locking
Ownership
Since kits often live in one file and are fairly specific, every kit has one owner who manages
it, knows it, and confirms changes to it.
The owner also carries the two decisions no tool can check:
- The texel density tier for the whole kit. Every piece is by definition seen beside its
siblings, and mismatched density next to itself is far more visible than low density everywhere.
Nothing in the pipeline knows what ends up next to what. → Texture Workflow > Adjacency - The grid the kit is built on, where it differs from the base.
Coordination beyond the file lock is the owner's responsibility.
Kit Piece Naming
Every kit piece is the Kit category. That is what the category is for, and as of 2026-09-12 it
is the only thing in it. A Kit name always carries a Set and a SetNum, and no SubCategory: the
Set is the kit, and it is what puts the file in Meshes/Kit/<Set>.
See Naming Conventions for the file name, then follow it with:
_[Part]_[Variant]
Part and variant can each span multiple words:
SM_Kit_AbandonedTavern_01_Wall_Interior_HalfHeight_01
SM_Kit_AbandonedTavern_01_Ceiling_WoodBeams_01_Square
SM_Kit_AbandonedTavern_01_Ceiling_WoodBeams_01_HalfCircle
Saying how big it is
A kit usually holds more than one length of the same part. Named only by their number, they are
Wall_01, Wall_02 and Wall_03, and a level designer has to open all three to find out which
is which.
The size goes in the Variant, as a word:
SM_Kit_AbandonedTavern_01_Wall_Full_01
SM_Kit_AbandonedTavern_01_Wall_Half_01
SM_Kit_AbandonedTavern_01_Wall_Quarter_01
Five words, and only five, because they are the only fractions a piece can actually be built
at. Every one lands on the 0.25 m sub-grid, and Third would be 1.333 m, which lands on nothing.
A bare word is about the axis the piece tiles on. A Half wall is half as long, which is
what it means on a street, and never half as thick. Compound it with an axis word to claim that
axis instead:
SM_Kit_AbandonedTavern_01_Wall_Interior_HalfHeight_01
SM_Kit_AbandonedTavern_01_Floor_Interior_HalfWidth_01
Width is the other axis a floor or a ceiling tiles on. A wall has only one, so asking a wall
for its width is a claim about an axis it does not have, and nothing is measured.
Wall_4m looks helpful and is a trap. A name is a second source of truth, and it goes stale
silently: nudge that wall to 3.75 m and the name is lying. It lies in the .fbx filename, and so in the
Unity asset path, the .meta, every prefab that references it and the material beside it.
Correcting it is a rename across all of them.
A word can be checked against the piece. A measurement in a filename is a promise nothing keeps.
The Descriptor token is PascalCase partly for this reason, and stays that way.
A curved piece is a fraction of a circle rather than of the module, through the same parser:
SM_Kit_AbandonedTavern_01_Wall_Tower_QuarterTurn_01. The legal fractions are a different list,
because they depend on what they are a fraction of. A Third of a turn is 120° and closes
perfectly, while a third of a 4 m segment lands on nothing. → Curves
MSH019 and MSH020 only ever fire on a name that parses as a size claim.
So Wall_Quarterturn_01, one capital short, does not report a wrong size.
It reports nothing at all, and the piece is never measured again.
MSH021 is the rule that reports the nearly: a capital missing, two size words
in one name, a size word that is not last, a bare Turn with no fraction, and
a HalfDepth whose axis nobody named. It needs no geometry, so it fires on the
first pass, and the New Kit Piece dialog says the same sentences while you are
still typing.
It is case only, never spelling: Hall is one letter from Half and a
rule that reported real words is a rule people switch off. Halfpipe and
Quarterdeck are left alone.
Two of the five come with a cure, because two of them have a certain
answer: a capital missing, and a size word out of place. One click renames the
collection and its objects, but not the material, which the whole kit shares. The
other three are questions for whoever named the piece, and get no button.
MSH019 compares each piece's word against the piece. It works by agreement rather than
against a fixed number: a 2 m Half implies a 4 m wall, and every piece of that part should
imply the same whole. The one that does not is named, so a kit whose wall is not 4 m needs no
configuration and a kit with only one size is never asked.
Wall_HalfWidth is reported too, and for a reason worth knowing: Width is the
other axis a floor tiles on, and a wall has one. That piece used to drop out
of the comparison in silence.
Large, Small, Long, Short, Tall, Tiny, Wide and Narrow are legal Variants and
always were. The library uses them 140 times. Nothing checks them, because no validator can say
whether a thing is Large.
A fraction on the wrong axis is reported: Third on a length, or ThreeQuarterTurn, which is
a legal shape that no run of itself can ever close.
The mesh lands in Meshes/Kit/AbandonedTavern, the Set without its number. A texture set for
the same kit keeps it: Textures/Trim/AbandonedTavern_01. Not a typo in either direction.
→ Project Folder Structure > Where does my file go?
ENV / EnvironmentThe Environment category was removed on 2026-09-12 and split in two. Kit pieces (anything that
tiles, snaps and connects to its siblings) are Kit. Everything else that used to be
Environment, like a one-off building, a tree or a cliff, is a Prop. If it is large it
declares its own vertex-density tier rather than taking the Prop default. See
Vertex density, because that part is not automatic and no check catches it.
See also
- Make a Kit Piece · Make a Prop: the steps, in order
- Splitting a Piece: when one element is several parts: panels, inserts, trim, corners
- Roofs: pitch, the parts of a roof, and the gable that closes it
- Openings: doors, windows, shutters and trapdoors
- Balconies: the projecting slab, its railing, and what holds it up
- Curves: walls that are part of a circle. the turn, and why the radius is a family
- Modular 3D Assets - Texturing: trim sheets, vertex painting, RGB masking
- Bubbly Games/Workflows/3D Models/3D Models: the Blender-side conventions that are not modular-specific
- Project Folder Structure: where a kit's files land
- Check Your Work: running the checks