In this guide
- demi.project.jsonEvery project field explained, from scenes to build metadata.
- Input bindingsWhat bindings are, what vector does, deadzones, and presets.
- Physics layersThe named collision matrix and how bodies filter each other.
- Simulation & seedsFixed timestep, random seeds, and why determinism matters.
- Performance budgetsWhat budgets measure and how they help you ship.
- Scene filesEntities, children, instances, and the full scene document.
- Prefab filesPrefab documents, nesting, and override semantics.
- HUD filesHUD document structure, variables, and themes.
- MaterialsShared mesh and terrain surfaces, PBR maps, projection and normal conventions.
- Asset manifestsManifest fields, importers, settings, and groups.
- Build & platform settingsapplication_id, window, Android permissions, branding.
JSON document shapes, URI schemes, and conventional paths.
Project & file formats
Every durable document is JSON and keeps format_version. Unknown formats are rejected with clear diagnostics rather than silently ignored.
“What is a binding and what does vector mean?” → Input bindings. “What is a virtual input?” → same page and Virtual & touch input. “What do physics layers do?” → Physics layers. “Why specify simulation settings?” → Simulation & seeds. “What use are performance budgets?” → Performance budgets.
Project — demi.project.json
Required fields: format_version, name, scenes.
{
"format_version": 1,
"name": "My Game",
"main_scene": "scene://game/main",
"scenes": [
{ "id": "scene://game/main", "path": "scenes/main.scene.json" }
],
"assets": ["asset-group://startup"],
"packages": { "demi.gameplay.health": "^1.0.0" },
"package_registry": "https://demiengine.de",
"network_contract": "asset://network/arena_contract",
"input": {
"presets": ["wasd_arrows", "confirm", "gamepad_confirm"],
"actions": {
"move": {
"type": "vector2",
"context": "gameplay",
"bindings": [
{ "input": "key:a", "vector": [-1, 0] },
{ "input": "gamepad:stick:left", "deadzone": 0.18 },
{ "input": "virtual:move" }
]
}
}
},
"physics": {
"layers": {
"goal": ["player"],
"player": ["goal", "wall"],
"wall": ["player"]
}
},
"simulation": {
"fixed_timestep": 0.016666667,
"random_seed": 1,
"maximum_fixed_steps_per_frame": 4
},
"display": { "vsync": true },
"performance_budgets": {
"maximum_frame_ms": 16.7,
"maximum_draw_calls": 800,
"maximum_resident_assets": 256
},
"debug": {
"colliders": false,
"contacts": false,
"grid": false,
"entity_ids": false,
"profiler_hud": false
},
"build": {
"application_id": "com.example.mygame",
"display_name": "My Game",
"executable_name": "my-game",
"version_name": "0.1.0",
"version_code": 1,
"icon": "asset://branding/icon",
"splash": "asset://branding/splash",
"window": { "width": 1280, "height": 720, "mode": "windowed" },
"android": { "orientation": "landscape", "permissions": [] }
}
}
assetsis startup preload only. It must not cause every project asset to load automatically.- Scene entries may omit
pathwhen it can be inferred from the scene ID. physics.layersis an object collision matrix (maximum 16 properties). Each key is a layer name; the value lists the layer names it collides with, and both sides must accept each other. The parser sorts layers by name — authored order does not assign bit indices.performance_budgetsis project data for measurement; it never silently changes quality.build.application_iduses reverse-DNS form and is required before packaging.
Scene — *.scene.json
Required: format_version, id (scene://…), and entities. The shared list contains ordinary entities and prefab placements and may be empty.
{
"format_version": 1,
"id": "scene://game/main",
"name": "Main",
"hud": "main.hud.json",
"entities": [
{
"id": "player",
"name": "Player",
"tags": ["hero"],
"components": {
"Transform2D": { "position": [4, 8] },
"Sprite": { "texture": "asset://sprites/player" },
"LuaScript": {
"module": "script://scripts/player.lua",
"properties": { "speed": 6.0 }
}
},
"children": [
{ "id": "player/sword", "components": { "Sprite": { "texture": "asset://sprites/sword" } } }
]
},
{
"id": "doorway",
"prefab": "prefab://doorway",
"overrides": {
"frame": { "components": { "Transform2D": { "position": [12, 0] } } }
}
}
]
}
Optional entity fields: enabled (default true), persistent (default false), layer, preset shorthand expanded before parse: static_box_3d accepts size, trigger_sphere_3d accepts radius, prop_2d accepts texture, and character_3d accepts no shorthand (it only expands components).
Prefab — *.prefab.json
Author entity prefabs with format_version, a prefab://… identity, and an entities array containing ordinary entities and prefab references. Use children for meaningful local hierarchies. The loader also accepts a separate instances array, but it is unnecessary for this workflow.
{
"format_version": 1,
"id": "prefab://characters/player",
"entities": [
{
"id": "body",
"components": {
"Transform3D": { "position": [0, 0, 0] },
"CapsuleCollider3D": { "radius": 0.3, "height": 1.8 },
"CharacterController3D": { "slope_limit": 45 }
},
"children": [
{ "id": "visual", "components": { "MeshRenderer": { "model": "asset://models/hero" } } }
]
}
]
}
Instance expansion prefixes IDs: local body with instance ID player becomes player/body. Nested prefab cycles are errors with a file chain. Override semantics:
- absent field inherits;
- objects merge recursively;
- arrays replace;
nullremoves a field, component, or entity;- dotted keys such as
body.Transform2D.positionset a single field.
HUD — *.hud.json
Required: format_version and a root node (or top-level children when the root is omitted). Sizes use canvas_size units.
{
"format_version": 1,
"canvas_size": [1280, 720],
"root": {
"id": "menu",
"type": "container",
"stack": "column",
"dock": "fill",
"gap": 12,
"children": [
{ "id": "title", "type": "label", "text": "${game_title}", "font_size": 32 },
{
"id": "start",
"type": "button",
"text": "Start",
"action": "confirm",
"prefab": "ui-prefab://controls/menu_button",
"arguments": { "label": "Start" }
}
]
}
}
Node types include container, panel, label, text, image, button, toggle, slider, text_input, scroll, list, progress, modal, virtual_button, and virtual_stick. Prefer dock, stack, pad, and at before raw anchors.
UI prefab — *.ui.prefab.json
{
"format_version": 1,
"id": "ui-prefab://controls/menu_button",
"parameters": {
"label": { "type": "string", "default": "Continue" }
},
"root": {
"id": "button",
"type": "button",
"text": "${label}"
}
}
Instances use {"prefab": "ui-prefab://menu_button", "arguments": {...}, "overrides": {...}}. Expanded descendant IDs use confirm.hint style paths.
Asset manifest — *.asset.json
{
"format_version": 1,
"id": "asset://textures/brick",
"type": "Texture2D",
"source": "brick.png",
"importer": "image",
"importer_version": 1,
"source_hash": "fnv1a64:...",
"dependencies": [],
"settings": {
"filter": "nearest",
"wrap": "clamp",
"mipmaps": false,
"platform_overrides": {
"android": { "compression": "astc" }
}
},
"license": "CC0-1.0",
"attribution": "Kenney"
}
Asset types: Texture2D, TextureAtlas2D, SpriteMetadata2D, Font2D, FontAtlas2D, Model3D, Collider3D, Terrain, AudioClip, VideoClip, Text, DataAsset, DataSchema, NetworkContract, Material, Shader, RenderTarget, Tilemap2D, Icon2D, SvgTexture2D, GifAnimation2D, ImageAnimation2D.
Asset groups (*.asset-group.json) declare addressable roots and streaming budgets:
{
"format_version": 1,
"id": "asset-group://level_one",
"roots": ["asset://levels/one", "scene://game/level_one"],
"budget": { "resident_mb": 192, "decoded_mb": 384, "upload_ms_per_frame": 3 }
}
Tilemap — *.tilemap.json
Versioned tile data referenced by a Tilemap2D asset: tile_size, map_size, tilesets (first_tile, animations), object_layers, and flat row-major layers with parallax, opacity, collision, collision_layer, navigation_blocked, and navigation_cost. Tile ID 0 is empty; positive IDs are global one-based; row 0 is the top row.
Saves — *.save.json
Slot-based JSON saves with schema validation and optional migrations. Saves live in the platform user-data directory ($XDG_DATA_HOME/<game> on Linux), never in the project source tree. Slots survive install, upgrade, and rollback.
Packages
Project packages declares constraints; demi.packages.lock.json pins resolved versions. Package archives are .demipkg files with audited content roots. Portable asset bundles are .demipack containers (versioned JSON index + sorted file data with checksums).
Cook output
demi cook writes generated/cooked/<platform> plus cook.manifest.json listing every included path and hash. Cook keys come from importer identity, settings, source hashes, platform, and package provenance — not timestamps.