DEMIENGINEDOCUMENTATION Everything is free
Start here

In this guide

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.

Common questions

“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": [] }
  }
}
  • assets is startup preload only. It must not cause every project asset to load automatically.
  • Scene entries may omit path when it can be inferred from the scene ID.
  • physics.layers is 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_budgets is project data for measurement; it never silently changes quality.
  • build.application_id uses 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;
  • null removes a field, component, or entity;
  • dotted keys such as body.Transform2D.position set 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.