Guide
Dart part models
The darts in the game (the match, Your setup, the designers, the render studio) are assembled from four part models: a point, a barrel, a stem and a flight. Each is a glTF binary file (.glb) with a line in a parts manifest. The core pack's parts are in StreamingAssets/darts/ (parts.json and models/*.glb), written by tools/blender/build_darts.py. They are the worked example for anyone making their own.
Status. The models, the picking and the assembly are in the game, and so is the mod side's loading: a pack ships its parts as dartParts records, the loader checks each model file, and an equipment item names its part in look.model. Checked by unit tests only so far; an example mod chosen in the designer, bought in the shop and thrown in a match has not been run yet. What is still missing is listed under Not built yet. This page has the same shape as Trophy models) and Stage models).
The part frame#
- Millimetres (or metres with
"units": "m"in the manifest, or"scale"per part). - The dart's axis is +z, from the tip to the flights. The frame is glTF's own: right-handed, y up.
- A part's front joint is at the origin and its back joint at
z = length. The point's front joint is the tip. - A part may reach past its joints: a point's tang goes into the barrel, a stem's thread sits at negative z.
- In Blender (z up, with the exporter's "+Y up" on, the default) the part lies along -Y from the origin.
The game puts the parts where its measurements say, so a dart's length never depends on the models:
| Part | From | Length |
|---|---|---|
| point | 0 | the point's length (item stats.length, 32 by default) |
| barrel | the point's back | the barrel's length (stats.length, 40 to 56) |
| stem | the barrel's back | the stem's length (35 by default; an integrated flight's shaft length) |
| flight | flightSeat mm before the stem's back (13; 8 on a moulded shaft) | its own |
A part is fitted to the length the look asks for. With "stretch": [from, to] only that stretch of the part (mm along z) takes up the difference and the rest keeps its shape: use it for a stem's plain shaft or a point's shank, so threads, crowns and grooves don't deform. Without it the whole part scales along z. A flight is never stretched.
Materials are roles#
The game colours a part from the dart's look (the item's look, a designed flight's print), by material name:
| Material name | Drawn as |
|---|---|
point | the point's metal, in its colour |
barrel | the barrel's finish (bare tungsten or its coating) |
barrel_cut | the bare metal the grip cuts expose under a coating |
stem | the stem's material and colour (the plastic of an integrated flight) |
collar | the aluminium ring that holds a film flight |
flight | the flight's print (the face the print reads true on) |
flight_back, flight_edge | the back face and the edge of a moulded vane |
Any other material name is drawn as authored: its base colour, metallic and roughness factors, its emissive factor and its base colour texture (embedded in the glb). If your modelling tool names materials differently, map them in the manifest: "materials": { "Steel.001": "point" }.
A flight's uvs are (distance from the axis / the widest point, distance from the front / the length): the square the flight prints are drawn in. A film flight is a single sheet per vane, wound counter-clockwise in that (s, z) plane; the game draws both sides.
In a mod: dartParts records#
A mod's parts are ordinary content records, in data/dart-parts.json (or files under data/dart-parts/), one record per part, with the glb anywhere in the mod:
[
{
"id": "acme.dartpart.barrel.meteor",
"kind": "barrel",
"name": "Meteor barrel",
"model": "darts/models/meteor-barrel.glb",
"node": "Barrel",
"lods": ["Barrel", "Barrel.lod1"],
"length": 50,
"match": { "profile": "torpedo", "grip": [0.45, 0.75] }
}
]The fields are those of the manifest below, with units ("mm", the default, or "m") and scale on each record. Being records, they follow the rules of all content: an id that exists already (the game's own or another mod's) is replaced, a patch can change one field, and the Mods screen counts them.
An equipment item is drawn with a part by naming it in its look:
{ "id": "acme.equipment.meteor-23", "kind": "darts", "look": { "primary": "#8a8f98", "metal": true, "model": "acme.dartpart.barrel.meteor" } }look.model on a darts item is its barrel, on a stems item its stem, on a flights item its flight, on a points item its point. Left out, or naming a part that is not there, the game picks a part by the item's shape and material as it does for its own items. A moulded (integrated) flight keeps its own shaft: a stem item's model does not apply to it.
A bad file never breaks the game. When the pack loads, a model that is missing, is not a .glb or is over 20 MB is refused with a warning on the Mods screen naming the file and the reason; the record loads without its model and the part is not offered. A record that breaks the contract (an unknown kind or material role, no length) is refused the same way. A file that passes those checks and then cannot be read when a dart is first drawn (Draco or meshopt compression, a missing node, no normals) is logged once, shown on the Mods screen against its mod with what was drawn instead, and the generated part is drawn.
The manifest#
The game's own parts are listed in StreamingAssets/darts/parts.json:
{
"format": 1,
"units": "mm",
"parts": [
{
"id": "acme.dartpart.barrel.meteor",
"kind": "barrel",
"name": "Meteor barrel",
"model": "models/meteor-barrel.glb",
"node": "Barrel",
"lods": ["Barrel", "Barrel.lod1"],
"length": 50,
"match": { "profile": "torpedo", "grip": [0.45, 0.75] }
}
]
}- **
id**: unique and namespaced. A part with an id that exists already replaces it. - **
kind**:point,barrel,stemorflight. - **
model: the glb, relative to the manifest.node**: the node with the mesh (left out: the first mesh in the file). - **
lods**: lighter versions of the node, the full one first. The second is used at the low and medium 3D quality settings. - **
length**: joint to joint, in mm. - **
stretch,flightSeat(stems),scale,materials**: see above. - **
match**: which looks the part suits. Every key must hold: a string or a boolean equal, a number inside[min, max), a string among a list. More matched keys win, then the nearest length, then the part added last (so a mod's part with the same match replaces the core one). A part withoutmatchis only used when a look names it.
| Kind | Match keys |
|---|---|
| point | texture (smooth, grooved, knurled, diamond), grooved, metal |
| barrel | profile (straight, torpedo, bomb, scallop, front, pencil), grip (0 to 1), coated |
| stem | metal, integrated, material (nylon, aluminium, carbon, titanium...) |
| flight | shape (standard, slim, kite, pear, fantail, vortex), integrated, material |
File rules#
Triangles only, with normals and one uv set. No Draco or meshopt compression, no sparse accessors. A node's own transform (and its parents') is applied. Keep a barrel under about 40,000 triangles and give it a light version: six darts can be on the board at once.
A part that fails to load is logged once ([darts] part ... did not load) and the game draws its generated part instead, so a bad file never breaks a match.
What stays generated#
- Designed barrels (signature darts from the 3D designer, an item's
dartDesign): the cut, the zones, the rings and the engraving are the designer's, so the barrel is generated to the design. Its point, stem and flight are models. - Flight prints: drawn into the flight's uv square from the look's colours and pattern.
Making the core parts again#
"C:\Program Files\Blender Foundation\Blender 5.2\blender.exe" --background --python tools/blender/build_darts.py
# one part while working on it: -- --only barrel.torpedoNot built yet#
- The example mod in the game. example-dart-parts (four parts, four shop items) loads cleanly with the game's pack in a unit test (
DartPartRecordTests,PartModelLookTests); bought in the shop, fitted and thrown in a match has not been run. The dart designer does not offer part models at all, the game's own or a mod's: it sets a dart's shape, cut and colours, and the part is then picked bymatch. So a mod's part reaches a dart through a shop item'slook.model, or by itsmatch; choosing a part model by name in the designer would be a new control. - A check of the glb's contents at pack load. The loader checks that the file is there, its type and its size; compression, missing nodes and missing normals are only found when the part is first drawn.
- A flight of a new shape. The print's outline and the dart's total length still come from the six built-in shapes; a model flight of another shape needs its outline and length taken from the manifest.
- Shop product shots of integrated flights still draw the generated model.