How to generate, rig, animate, and wire custom 3D models and units into Shards of Stone using Meshy.ai, Blender, and data overrides.
Page 14 of 17
Shards of Stone features a dual-presentation architecture: a pure, deterministic
lockstep simulation running on a background Web Worker, rendered in the browser
through a high-performance Three.js 3D viewport (with an automatic 2D canvas sprite
fallback).
Every unit, building, and prop in the 3D world is driven by standard glTF / GLB
assets loaded on demand through ModelManager. Using AI generation tools like
Meshy.ai, standard DCC packages like Blender, and the game's Data Overrides
system, modders and AI agents can create completely new, fully animated, custom 3D
units without editing any engine source code.
flowchart LR A["Meshy.ai / Blender\n(3D Gen & Rig)"] --> B["Dual GLB Pipeline\n(character + animations)"] B --> C["Local / R2 Storage\n(public/assets/models/...)"] C --> D["ModelManager\n(Clip Normalization & LODs)"] D --> E["Data Overrides\n(defOverrides.units)"] E --> F["Three.js 3D Viewport\n(Sockets, VFX & Lockstep Sync)"]
Modern generative AI makes producing RTS-ready 3D models fast and accessible. When
generating models via Meshy.ai (Text-to-3D or Image-to-3D), adhering to these
guidelines ensures your models integrate cleanly into the engine's lighting, LOD, and
animation systems.
Units in an RTS are viewed from an isometric, elevated camera (typically 45° pitch,
zoomed out). Subtle surface details wash out at distance; strong silhouettes and
high-contrast materials are essential.
Style Keywords: Include stylized fantasy RTS, hand-painted textures,
bold chunky proportions, clean silhouette, high contrast PBR, low-poly game asset.
Proportions: Exaggerate weapons, pauldrons, heads, and hands by 20–30% so they
read clearly at distance. Thin weapons or delicate antennae disappear when zoomed out.
Color Zoning: Reserve clear areas on armor or cloaks for faction/team colors.
Avoid muddy gradients or hyper-detailed noisy noise maps.
The game engine features an automatic mesh-decimation and Level of Detail (LOD)
pipeline. Author your high-tier source models to the following polygon budgets:
[!TIP]
Do not author models above 35k triangles. The runtime normalizes 1x1 ground units
to a baseline height of 24–32 world pixels (UNIT_MODEL_HEIGHT_PIXELS). Extreme
polycounts waste GPU bandwidth and memory without improving visual fidelity.
Channels: Standard glTF Metallic-Roughness PBR. Use Base Color (Albedo),
Roughness, Metallic, and Normal maps.
Texture Dimensions: Source textures should be 1024×1024 pixels. The asset
pipeline automatically optimizes these into compressed KTX2 / Basis Universal tiers
(1024, 512, 256, 128) during build.
Topology: Ensure manifold geometry with clean edge loops around bending joints
(knees, elbows, hips, shoulders) to prevent mesh tearing when animated.
Origin & Anchoring: The model's feet must rest on the ground plane at Y = 0
(or Z = 0 in Blender before export) centered at (0, 0). The pivot point is the
unit's ground center.
File Structure:
A single flat GLB file. For units: character.glb. For buildings: <buildingId>.glb.
No animations.glb Required:
The game engine evaluates unitHasAnimations(def). Units with roles siege, trader,
or naval_trader, or with flags isFlying: true or isNaval: true, or whose IDs end
in _ram, _cannon, _wagon, _wheel, _tank, _engine, _shredder, _chariot,
etc., automatically bypass animations.glb loading.
Procedural Animation:
The 3D renderer handles these dynamically:
Naval vessels rock realistically with water currents and leave particle wakes.
Siege engines recoil procedurally upon firing.
Wheeled vehicles roll in sync with movement velocity.
Units with deathStyle: 'shatter' dynamically shatter into physics chunks.
For humanoid warriors, monsters, workers, and quadrupeds, the engine uses a dual-file
architecture:
character.glb:
Contains the textured 3D mesh, the vertex skin weights, and the bone armature in
its rest / bind pose (or a basic idle clip).
animations.glb:
Contains only the animation tracks bound to the exact same bone names. Mesh
geometry is stripped out to save memory and allow multiple skins to share one
animation set.
[!IMPORTANT]
When exporting character.glb and animations.glb, the bone hierarchy, bone names,
and rest poses must match identically. If a bone named RightHand exists in
character.glb but is named Hand.R in animations.glb, that limb will fail to animate.
3. The 9 Canonical Animation States & Substring Matching#
In traditional game engines, an animator must explicitly wire every clip into a complex
state machine graph. Shards of Stone uses an intelligent, zero-configuration
substring-matching resolver inside ModelManager.
When a model's animations.glb loads:
Every clip name is normalized: converted to lowercase and stripped of all
punctuation, spaces, underscores, and symbols:
function normalizeClipName(name: string): string { return name.toLowerCase().replace(/[^a-z0-9]/g, '');}
Behavior:
Plays while the unit is stationary without an active attack target or build order.
If your model has multiple idle variations (e.g. idle_breathe, idle_scratch,
idle_look_around), ModelManager automatically loops through them in random sequence,
giving units organic ambient life!
Behavior:
Plays whenever the unit has an active move command and its velocity is greater than 0.
The animation playback speed automatically scales with the unit's speed attribute.
Note: Meshy frequently exports single-loop animations named "Armature|Unreal Take|baselayer".
The aliases baselayer and unreal ensure raw Meshy exports work out of the box.
Behavior:
Triggered when the unit attacks an enemy. On every swing or shot, Model3DRendererpicks a random variant from the attack pool. Clips named attack_slash,
attack_thrust, strike_down, or hit_2 will automatically alternate during battle!
Behavior:
Plays once when the unit's HP reaches 0. The animation clamps and freezes at its
final frame, where the unit lies as a physical corpse. The corpse remains until its
decay timer expires (or until an undead worker consumes/resurrects it), after which it
sinks through the terrain.
Behavior:
Plays when a worker unit is harvesting resources (quarrying stone, mining gold, or
felling timber). Also used as a generic channeling posture for spellcasters.
Behavior:
Dedicated jumping animation for assault units and jetpack infantry (such as the
Dwarf Ironjumper) equipped with canHop: true. Clamps during mid-air flight and
blends back to walk/idle upon touchdown.
Yes, completely and without modifying engine code.
Because ModelManager populates dynamic arrays (THREE.AnimationClip[]) for each
state pool, you can add as many animations as you want to your animations.glb. The
engine automatically leverages them through three core mechanics:
If you export multiple idle clips in animations.glb:
Armature|Idle
Armature|Idle_Scratch
Armature|Idle_Taunt
Armature|Idle_Dance
All four clips contain the substring idle and are added to the unit's idlePool.
When the unit is idle, it plays one clip. Upon completion (mixer.addEventListener('finished')),
the engine selects another random idle from the pool. Units will stand guard, occasionally
stretch, glance around, or scratch their beards!
If you download an animation library containing separate .glb files from Meshy or
Mixamo, you can merge them into a production-ready animations.glb using the included
tool:
To ensure projectile trails, spell bursts, and muzzle flashes emerge from the right
points in 3D space, Model3DRenderer resolves named sockets on your model.
Sockets can be bones in your armature or Empty / Dummy objects parented to bones.
Names are matched case-insensitively with punctuation removed.
Usage: The exact origin point where arrows, bullets, fireballs, and catapult
stones spawn. Also anchors the 4-ray starburst muzzle flash particle effect.
Usage: Used by EntityUI3D for overhead health bars, level badges, status
crowns, and seating points for cavalry riders.
[!TIP]
Authoring in Blender: Add an Empty (Plain Axes) object, parent it to your
character's right hand bone (or rifle barrel), and name it muzzle or weapon_tip.
When exported into character.glb, the engine finds it automatically. If no socket
is found, the engine uses an intelligent procedural fallback offset based on the
unit's bounding box and facing direction.
[!NOTE]
In Example 2, baseRotationY: 1.5708 ($\pi/2$ radians) rotates the model 90 degrees
clockwise at load time. Use this if your Meshy export faced right (+X) instead of
forward (+Y / down).
Example 3: Caster Hero with Dedicated Abilities (high_elf_archmage)#
An elven caster wielding special_1 (Fireball) and special_2 (Blizzard):
In production and local development, assets are served from the Cloudflare R2 CDN bucket
(assets.shardsofstone.com). The asset pipeline automatically handles atlas packaging,
texture compression (AVIF/WebP and KTX2 Basis Universal), and content-hash cache-busting.
You never have to worry about missing models crashing a match:
If a custom 3D model fails to download or returns a 404, ModelManager catches the
error internally, logs a non-fatal warning, and falls back immediately to the 2D
canvas sprite (spriteKey).
The lockstep game simulation remains 100% synchronized regardless of whether a client
is rendering high-polygon 3D meshes or 2D pixel-art sprites.
Follow this step-by-step checklist to take a concept from prompt to playable 3D unit:
Step 1: Generate Model in Meshy.ai • Prompt: "Stylized dwarf iron warrior, heavy runic plate armor, warhammer" • Target 15k–25k triangles. Download the Character GLB and Animation Pack.Step 2: Inspect & Clean in Blender • Check pivot: Feet must stand on ground plane (Z=0 in Blender). • Facing: Unit should face front (-Y in Blender). • Add an Empty object parented to right hand named "weapon_tip" or "muzzle".Step 3: Merge Animations • Run: node tools/merge_meshy_animations.js --src meshy/warrior --out public/assets/models/dwarf/units/dwarf_warrior --fun-idles • Verify character.glb and animations.glb are produced.Step 4: Create Data Override in The Forge • Open The Forge (Map Editor) and switch to the Data tab (F9). • Create a Clone of "dwarf_ironguard", set id to "dwarf_warrior". • Set modelId to "dwarf_warrior". • Adjust HP, damage, speed, and renderScale.Step 5: Test in Real-Time • Press Ctrl + F9 (or Ctrl + Enter) to launch instant lockstep playtest. • Spawn your unit and observe idle cycling, walking, combat swinging, and death!