Skip to content

Project format and loading

The project and scene XML format, its parser, and building scenes from parsed data.

Files: project_format.odin, project_loader.odin.

From project_format.odin:

Project and scene files: the XML format the editor saves and the engine loads.

A project is a folder

MyGame/
project.xml settings, the scene list and the asset list
scenes/Menu.scene.xml one file per scene
assets/... images, audio, fonts

project.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project formatVersion="5" name="Asteroids" width="1280" height="720"
scaleMode="Letterbox" background="#0f121e" startScene="MenuScene"
pixelsPerMeter="100" gravityX="0" gravityY="980">
<scene name="MenuScene" path="scenes/MenuScene.scene.xml"/>
<asset path="assets/ship.png" kind="Image"/>
<asset path="data/fast_ship.data.json" kind="Data"/>
<global script="GameState">
<prop name="lives" value="3"/>
</global>
<action name="jump" deadzone="0.5">
<key key="Space"/>
<gamepad button="South"/>
<axis axis="LeftY" direction="-1"/>
</action>
</project>

The optional renderer attribute picks the renderer games draw with: a RenderBackend name (Auto, the default: SDL’s choice; OpenGL, OpenGLES2, Vulkan, GPU, Direct3D11, Direct3D12, Metal, Software). See render_backend.odin.

<action> elements are the game’s input actions (input.odin): a name, a deadzone for sticks, and its bindings: <key key="..."/> (a Key name), <mouse button="..."/> (MouseButton), <gamepad button="..." device="0"/> (GamepadButton; no device = any gamepad) and <axis axis="..." direction="1|-1" device="0"/> (GamepadAxis).

A scene file holds a tree of nodes. Nesting is the hierarchy, and a node’s x, y, rotation and scale are relative to its parent:

<?xml version="1.0" encoding="UTF-8"?>
<scene formatVersion="4" name="PlayScene">
<node kind="Node2D" name="World" x="0" y="0">
<node kind="Camera" name="Camera" x="640" y="360" zoom="1"/>
<node kind="Sprite" name="Ship" x="640" y="430" texture="assets/ship.png">
<node kind="AnimatedSprite" name="Flame" x="0" y="40" texture="assets/flame.png"
columns="4" rows="1" fps="12"/>
</node>
<node kind="AnimatedSprite" name="Hero" x="200" y="400" texture="assets/hero.png"
columns="4" rows="2" margin="0" spacing="2" autoplay="walk">
<animation name="walk" start="0" end="3" fps="10" loop="true"/>
<animation name="jump" start="4" end="7" fps="12" playMode="PingPong"/>
</node>
</node>
<node kind="Label" name="Title" x="640" y="180" text="ASTEROIDS" fontSize="64"
color="#ebf0ff" centered="true" layer="7"/>
<node kind="Menu" name="MainMenu" x="640" y="300" layer="7">
<item label="Play" action="scene:PlayScene"/>
<item label="Quit" action="quit"/>
</node>
<node kind="Sprite" name="Player" x="640" y="600" texture="assets/ship.png">
<script name="PlayerController">
<prop name="speed" value="250"/>
</script>
</node>
<node kind="StaticBody" name="Ground" x="640" y="700" collisionLayer="1">
<node kind="CollisionShape" shape="Rectangle" width="1280" height="40"/>
</node>
<node kind="DynamicBody" name="Crate" x="600" y="100" restitution="0.3">
<node kind="Sprite" texture="assets/crate.png"/>
<node kind="CollisionShape" shape="Rectangle" width="48" height="48"/>
</node>
<node kind="Area" name="Goal" x="1200" y="640" collisionMask="2">
<node kind="CollisionShape" shape="Polygon" points="-20,20 20,20 0,-20"/>
</node>
<node kind="ParticleEmitter" name="Sparks" x="640" y="500" texture="assets/spark.png">
<particles amount="80" angle="270" angleSpread="40" angleVariance="1"
colorRamp="0:#ffe664 0.5:#e63228 1:#28282800"/>
</node>
</scene>

An AnimatedSprite’s texture is a sprite sheet of columns x rows cells (margin pixels around the grid, spacing between cells), numbered left to right, top to bottom from 0. Its <animation> elements are named ranges of cells (start, end inclusive; end -1 is the last cell), each with its fps, loop and playMode; autoplay names the one it plays from the start (else the first). Without <animation> elements it plays one animation, “default”: every cell, at the node’s fps, loop and playMode (the format before version 7). An animation’s durations (milliseconds per frame) override its fps, and repeat plays a non-looping one that often. With animations="assets/hero.aseprite" (an animation asset: an .anim.xml file or an Aseprite file, AnimationSetData) it plays the asset’s sheet and animations instead of its own.

ParticleEmitter nodes (particles.odin) keep their settings in a <particles> child element: one attribute per ParticleSettings field that differs from DEFAULT_PARTICLE_SETTINGS, named like the field, in ParticleSettings_Get’s format. The node’s texture is the Texture shape’s image.

Physics (physics.odin, physics_nodes.odin): body nodes (StaticBody, DynamicBody, CharacterMover, Area) get their shape from the CollisionShape nodes under them. Collision layers are lists of layer numbers 1-64 (collisionLayer="1 3"), all, or empty for none.

Attributes every node may have: kind, name, x, y, rotation (degrees), scaleX, scaleY, layer (0-7), visible, processMode (a ProcessMode: Inherit, Pausable, WhenPaused, Always, Disabled), blendMode (a BlendMode: Blend, Add, Multiply, Modulate, None), zIndex (draw order in the layer), ySort (children drawn by their y), groups (names, separated by commas: groups="enemies,flying"). Kind-specific attributes are listed on SceneNodeData. Missing attributes use the defaults in DEFAULT_SCENE_NODE. Paths are relative to the project folder and use /. Colors are #rrggbb or #rrggbbaa. Booleans are true or 1; any other value is false. A number or color that can’t be read keeps the default.

A scene’s root element can carry its own settings (SceneSettings, format 13): screenLayers (layers drawn without the camera: HUDs, menus), background (instead of the project’s) and gravityX / gravityY (instead of the project’s).

Current limits

  • A scene’s Camera applies to every node on a layer that isn’t one of its screenLayers, Labels and Menus included.
  • Labels and Menus follow their position only; their rotation and scale are ignored.
  • Assets aren’t preloaded: images load with the nodes that use them, sounds and music with their sound nodes (and from code on first use); Font assets are listed but not used.

Version history

  • 13: every node’s processMode, blendMode, zIndex, ySort and groups; CollisionShape oneWay; the project’s window: fullscreen, vsync (default true), highDPI; assets of kind “Translation” (CSV tables: localization.odin), the project’s locale and icon, and the editor’s <grid width height majorEvery opacity majorOpacity/> (GridSettings; games ignore it); the project’s sound output format (audioFrequency, audioChannels: AudioOutputSettings, 0 for the device’s own; stereo 48 kHz without them); scenes’ own settings (SceneSettings: screenLayers, background, gravityX / gravityY on the root element); animation assets (.anim.xml files, kind “Animation”, and Aseprite files: animation_sets.odin), AnimatedSprite animations, <animation> durations and repeat and events, the PingPongReverse play mode; TileMap nodes (tilemap, frame, layers, tileCollision, animate, tag: tilemap.odin); Sprite and AnimatedSprite layers and pivotSlice, Sprite nineSlice, nineWidth, nineHeight, CollisionShape slice (aseprite_nodes.odin); assets’ filter and the project’s textureFilter (asset_import.odin); atlases (.atlas.xml, kind “Atlas”) and Sprite region (atlas.odin); skeletons: Skeleton2D, Bone2D (boneLength), SkinnedMesh (texture, region, a &lt;mesh vertices uvs triangles bones weights/> child) and IKChain (ikTip, ikTarget, ikLength, ikBend, ikEnabled) nodes (skeleton.odin); DialogueBox nodes (dialogue, startNode, boxWidth, boxHeight, typeSpeed, textSize, boxFont, boxPadding, boxColor, textColor, nameColor, pickColor, boxAutostart, hideWhenDone) and dialogue assets (.dialogue.xml, kind “Dialogue”: dialogue.odin); 2D lighting: scenes’ lighting, ambient, bodyShadows, Light2D (lightKind, lightColor, energy, lightRadius, spotAngle, shadows) and LightOccluder (a CollisionShape’s shape attributes) nodes (lighting.odin); NavRegion (width, height, cellSize, agentRadius, diagonal) and NavAgent (speed, arriveDistance, avoidRadius, autoMove) nodes (navigation.odin); VirtualJoystick and TouchButton nodes (touchActions, touchRadius, deadzone, zoneWidth, zoneHeight, floating, touchVisibility, touchMouse, touchColor, touchOpacity; a button’s label text: touch.odin); ParallaxLayer (factorX, factorY, repeatX, repeatY), CameraZone (width, height, zoneTransition) and CameraView (width, height, viewCamera, viewZoom, border) nodes (camera_tools.odin); StateMachine nodes (start, animates, <param>, <state>, <transition>: state_machine.odin); UI controls (Panel, Button, ProgressBar, TextField, ScrollBox, RichText: width, height, anchor, margin, layout, align, justify, columns, padding, spacing, expand, disabled, value, min, max, direction, showValue, maxLength, patchMargins, textSize, textAlign, theme, placeholder, action: ui_controls.odin), assets of kind “Theme” (.theme.xml: ui_theme.odin) and the project’s uiTheme; the project’s transition, transitionTime, transitionColor and loadingScene, ProgressBar showLoading, Menu and Button actions push:<Name> and pop (scene_flow.odin); Animator nodes (autoplay, speed, <clip> / <track> / <key>: animator.odin); LDtkLevel oneWayValues, areaValues and <entity make script noScript>; bodies’ material (assets of kind “PhysicsMaterial”, .material.xml: physics_material.odin); buses’ <effect> children (audio_effects.odin) and a MusicPlayer’s <layer> children (music_layers.odin); VirtualJoystick and TouchButton nodes (touch.odin); the project’s steamAppId, steamCloud and <achievement> children (steam.odin); <players max keyboard joinAction/> (players.odin); every node’s shader and <shaderParam name value/> children, ScreenEffect nodes (effectScreenLayers), assets of kind “Shader” (.frag, compiled beside them as .frag.shader) and the project’s shaders (shaders.odin, screen_effects.odin); Line2D and Trail2D nodes (lineWidth, endWidth, lineColor, endColor, joint, roundCaps, closed, textureMode, lifetime, minDistance, emitting; a line’s points: lines.odin); several <script> elements a node (script.odin); custom node types (kind="Player" baseKind="Sprite", <prop> children: custom_nodes.odin). Version 12 files are valid version 13 files.
  • 12: LDtkLevel nodes (a level, or a whole world, of an LDtk project: ldtk_nodes.odin) and assets of kind “LDtk”. Version 11 files are valid version 12 files.
  • 11: the project’s renderer. Version 10 files are valid version 11 files.
  • 10: sound nodes (AudioPlayer, AudioPlayer2D, MusicPlayer), mix buses (<bus> in project.xml), assets of kind “Music”. Version 9 files are valid version 10 files.
  • 9: components (<component> files, assets of kind “Component”) and Component nodes. Version 8 files are valid version 9 files.
  • 8: input actions (<action> in project.xml). Version 7 files are valid version 8 files.
  • 7: AnimatedSprite <animation> elements, margin, spacing and autoplay. Version 6 files are valid version 7 files.
  • 6: ParticleEmitter nodes and their <particles> element. Version 5 files are valid version 6 files.
  • 5: global scripts (<global script="..."><prop name value/></global> in project.xml) and assets of kind “Data” (data resources, data/<name>.data.json: data.odin). Version 4 files are valid version 5 files.
  • 4: physics: StaticBody, DynamicBody, CharacterMover, Area, CollisionShape, RayCast and Joint nodes, and the project’s pixelsPerMeter, gravityX, gravityY, physicsDebug and physicsSmoothing. Version 3 files are valid version 4 files.
  • 3: a node can have a script: a <script name="..."> child element holding &lt;prop name="..." value="..."/> values for its exported fields (script.odin). Version 2 files are valid version 3 files without scripts.
  • 2: positions, rotation and scale are relative to the parent. A Camera’s rotation turns the view the way a rotated camera would (the world appears to turn the other way).
  • 1: positions were world coordinates and parents had no effect; Camera rotation rotated the world. Version 1 scenes are converted when parsed.

Parsing (this file) only turns XML into plain data, without SDL, so it can be tested and used by the editor. project_loader.odin builds engine scenes and nodes from that data.

SceneNodeKind :: enum {
// Groups other nodes; draws nothing
Node2D,
// A texture
Sprite,
// A sprite sheet animation
AnimatedSprite,
// Configures the scene's camera; not a drawn node
Camera,
// Text
Label,
// Vertical button menu whose buttons switch scenes or quit
Menu,
// Physics bodies (physics_nodes.odin); their CollisionShape children give them a shape
StaticBody,
DynamicBody,
CharacterMover,
Area,
CollisionShape,
// Casts a ray every physics step and reports the first hit
RayCast,
// Connects two physics bodies: a hinge, rope, spring, slider, weld or wheel
Joint,
// Emits particles: sparks, smoke, fire, rain (particles.odin)
ParticleEmitter,
// An instance of a component (a saved node tree): its nodes are built in its place
Component,
// Sound nodes (audio_nodes.odin): a sound; a sound in the world; streamed music
AudioPlayer,
AudioPlayer2D,
MusicPlayer,
// A level (or every level of a world) of an LDtk project: tiles, entities, collision
LDtkLevel,
// The tilemap layers of an Aseprite file: tiles, and collision from the tiles' properties
// (format 13; tilemap.odin)
TileMap,
// Keyframe animations of other nodes' properties: clips of tracks (format 13; animator.odin)
Animator,
// 2D lighting (format 13; lighting.odin): a point or spot light; a shape that casts shadows
Light2D,
LightOccluder,
// Navigation (format 13; navigation.odin): where characters can walk; a path finder moving its
// parent
NavRegion,
NavAgent,
// A state machine: states, transitions on parameters, animations per state (format 13;
// state_machine.odin)
StateMachine,
// Camera tools (format 13; camera_tools.odin): a background moving slower than the world; a
// room the camera stays inside; another camera's view in a rectangle
ParallaxLayer,
CameraZone,
CameraView,
// UI controls (format 13; ui_controls.odin): a box laying out controls; a button; a bar
// showing a value; a line of text to type; a scrolling column; text with colors and sizes
Panel,
Button,
ProgressBar,
TextField,
ScrollBox,
RichText,
// Touch controls (format 13; touch.odin): an on-screen stick pushing input actions; a round
// button pressing one
VirtualJoystick,
TouchButton,
// A shader over the whole scene: CRT, bloom, color grading (format 13; screen_effects.odin)
ScreenEffect,
// A thick line through points; a ribbon left behind its parent (format 13; lines.odin)
Line2D,
Trail2D,
// Skeletal animation (format 13; skeleton.odin): a skeleton's root, a bone, a picture bent over
// bones, a chain of bones reaching for a target
Skeleton2D,
Bone2D,
SkinnedMesh,
IKChain,
// A conversation's box: lines typed out, options to choose (format 13; dialogue_box.odin)
DialogueBox,
}

Node kinds a scene file can contain.

enum. Source: project_format.odin:215

SceneNodeCategory :: enum {
// Structure: grouping, the camera, component instances, levels
Scene,
// What draws: images, animations, particles
Graphics,
// Text and menus
UI,
// Bodies, shapes, rays and joints
Physics,
// Sounds and music
Audio,
}

The groups tools list node kinds in (the editor’s Add Node dialog), in their display order.

enum. Source: project_format.odin:298

SceneNodeKindInfo :: struct {
// One line describing what the node does
description: string,
// The group it's listed in
category: SceneNodeCategory,
}

What a node kind is, for tools that let users pick one (the editor’s Add Node dialog).

struct. Source: project_format.odin:323

NodeScriptData :: struct {
name: string,
props: [dynamic]ScriptPropData,
}

A script after a node’s first, from a scene file: its name and field values

struct. Source: project_format.odin:389

NodeListenerData :: struct {
// The event: an EventSubject field of the node's type (`NodeType_Events`), "onFinished" or
// through a field, "animation.onAnimationEnded"
event: string,
// The node whose script listens: a path from this node (`Node_FindPath`; "." for itself)
target: string,
// The handler's name (`Script_Listener`)
handler: string,
// A global's event instead of this node's: the global script's name ("GameState"). The
// listener is then on the listening node (`target` is "." or empty)
global: string,
}

A script listening to a node’s event, from a scene file: <listener event="onFinished" target="../Hero" handler="onBloodFinished"/> inside the node. Before the scene’s scripts get ready, the target node’s script’s handler (registered with Script_Listener) is subscribed to the event (node_events.odin).

struct. Source: project_format.odin:400

MenuItemData :: struct {
// The button's text
label: string,
// `scene:<SceneName>` switches to that scene, `quit` quits the game
action: string,
}

A button in a Menu node: <item label="Play" action="scene:PlayScene"/>.

struct. Source: project_format.odin:416

SceneNodeData :: struct {
// `kind`: what node it becomes (unknown or missing: Node2D)
kind: SceneNodeKind,
// `name` (missing: the kind's name, "Sprite")
name: string,
// Index of the parent node, -1 for top-level nodes. Nodes are stored depth-first:
// every node's children follow it directly
parent: int,
// `x`, `y`: position relative to the parent, in pixels. Sprites and labels are centered on it
// (labels unless `centered` is false)
position: [2]f32,
// Degrees clockwise, relative to the parent
rotation: f32,
// `scaleX`, `scaleY`: scale relative to the parent (1: unscaled)
scale: [2]f32,
// The render layer, 0-7 (out-of-range values are clamped)
layer: int,
// Hidden nodes, and everything under them, aren't drawn
visible: bool,
// Whether it updates while its scene is paused (Node.processMode)
processMode: ProcessMode,
// How its drawing combines with what's under it (Node.blendMode)
blendMode: BlendMode,
// Draw order within its layer (Node.zIndex)
zIndex: i32,
// Its children draw sorted by their y (Node.ySort)
ySort: bool,
// Groups it belongs to (Node.groups): `groups="enemies,flying"`
groups: [dynamic]string,
// Sprite, AnimatedSprite, ParticleEmitter (its Texture shape): image path relative to the project folder
texture: string,
// Label: its text
text: string,
// Label: font size (default 24)
fontSize: f32,
// Label: text color (`#rrggbb` or `#rrggbbaa`; default white)
color: sdl.Color,
// Label: centered on its position (false: its top-left corner is there)
centered: bool,
// Camera: zoom (1: none, 2: twice as big)
zoom: f32,
// AnimatedSprite: the texture is a columns x rows grid of cells (at least 1 each)
columns: int,
// AnimatedSprite: rows of the sheet's grid (see `columns`)
rows: int,
// AnimatedSprite without `<animation>` elements: frames per second of its "default" animation
fps: f32,
// AnimatedSprite without `<animation>` elements: the "default" animation loops (default true).
// Sound nodes read `loop` too, into `audio` (default false there)
loop: bool,
// AnimatedSprite without `<animation>` elements: the "default" animation's play mode
playMode: AnimationPlayMode,
// AnimatedSprite: the sheet's grid margin and spacing (pixels), its animations (none: one
// "default" animation of every cell, from fps/loop/playMode above), and the one played first.
// `margin`: pixels around the grid
margin: f32,
// AnimatedSprite: pixels between cells
spacing: f32,
// AnimatedSprite: its `<animation>` child elements, in order
animations: [dynamic]AnimationData,
// AnimatedSprite: the animation it plays from the start ("" or unknown: the first; with an
// animation asset, "" is the asset's own)
autoplay: string,
// AnimatedSprite: `animations="assets/hero.anim.xml"`, an animation asset (an `.anim.xml` file,
// or an Aseprite file: its tags) whose sheet and animations it plays instead of its own
// (format 13; animation_sets.odin)
animationSet: string,
// Sprite, AnimatedSprite with an Aseprite image: `layers="Body,Sword"`, the layers drawn (by
// name; a group's name brings its layers), "" for every visible one (format 13;
// aseprite_nodes.odin)
asepriteLayers: string,
// Sprite, AnimatedSprite: `pivotSlice`, an Aseprite slice whose pivot goes on the node's origin
// (frame by frame; "" for the image's middle)
pivotSlice: string,
// Sprite: `nineSlice`, a 9-patch Aseprite slice drawn stretched to `nineWidth` x `nineHeight`
nineSlice: string,
// Sprite: `region`, the region of the atlas its `texture` names (an `.atlas.xml` file:
// atlas.odin; format 13)
region: string,
// Sprite: the 9-patch's size in pixels
nineSize: [2]f32,
// CollisionShape: `slice`, an Aseprite slice of its sprite whose rectangle it takes, frame by
// frame (a hitbox; off where the slice is hidden; aseprite_nodes.odin)
slice: string,
// Menu: its `<item>` child elements, top to bottom
items: [dynamic]MenuItemData,
// Any kind: the script attached to the node (`<script name="...">`; "" for none)
script: string,
// Any kind: the script's exported field values (its `<prop>` elements)
props: [dynamic]ScriptPropData,
// Any kind: more scripts after the first (`<script>` elements after the first, format 13)
moreScripts: [dynamic]NodeScriptData,
// A custom node type's name (`kind="Player"`, `kind` then is its `baseKind`; "" none) and its
// exported fields' values (`<prop name value/>` children of the node): custom_nodes.odin
customKind: string,
customProps: [dynamic]ScriptPropData,
// Any kind: scripts listening to its events (`<listener event target handler/>`, format 13)
listeners: [dynamic]NodeListenerData,
// StaticBody, DynamicBody, CharacterMover, Area: the layers it's on and collides with
// (`collisionLayer="1 3"`, `collisionMask="all"`), and its surface.
// `collisionLayer`: the layers it's on, as bits (layer n is bit n-1; default layer 1)
collisionLayer: u64,
// Bodies: the layers it collides with (default all). RayCast: the layers it hits
collisionMask: u64,
// Bodies: surface friction (default 0.6)
friction: f32,
// Bodies: bounciness, 0 to 1 (default 0)
restitution: f32,
// Bodies: density, for the mass of dynamic bodies (default 1)
density: f32,
// Bodies: a physics material (`.material.xml`, physics_material.odin) whose friction,
// restitution and density replace the three above ("": none)
material: string,
// DynamicBody: multiplies the project's gravity (default 1)
gravityScale: f32,
// DynamicBody: slows its movement over time (0: none)
linearDamping: f32,
// DynamicBody: slows its spin over time (0: none)
angularDamping: f32,
// DynamicBody: it doesn't rotate
fixedRotation: bool,
// DynamicBody: continuous collision for fast bodies (doesn't pass through thin walls)
bullet: bool,
// CharacterMover: the steepest floor, degrees
floorMaxAngle: f32,
// CollisionShape: `shape`; Rectangle `width` and `height`; Circle `radius`; Capsule `radius`
// and `height` (overall); Polygon and Segment `points="x,y x,y ..."`; `disabled`; `oneWay`
// (format 13: only its top stops what comes down on it).
// `shape`: Rectangle (default), Circle, Capsule, Polygon or Segment
shape: CollisionShapeKind,
// CollisionShape: `width`, `height` in pixels (Rectangle; a Capsule's overall height is `.y`)
shapeSize: [2]f32,
// CollisionShape: Circle and Capsule radius, in pixels
radius: f32,
// CollisionShape: Polygon and Segment points, in the node's space (`points="x,y x,y ..."`)
points: [dynamic][2]f32,
// CollisionShape: left out of its body
disabled: bool,
// CollisionShape (format 13): a one-way platform, only its top stops what comes down on it
oneWay: bool,
// RayCast: `targetX`, `targetY` (relative to the node), `enabled`, `excludeParent`
// (collisionMask is the layers it hits).
// `targetX`, `targetY`: the ray's end, relative to the node
target: [2]f32,
// RayCast: casting (default true)
enabled: bool,
// RayCast: ignore the body it's under (default true)
excludeParent: bool,
// Joint (physics_joints.odin): `joint` (the kind), `bodyA` / `bodyB` (names or paths; bodyA
// "" = the body it's under, or the world), `offsetX`/`offsetY` (Distance: body B's anchor),
// `limit`, `lower`, `upper`, `motor`, `motorSpeed`, `maxMotor`, `spring`, `hertz`, `damping`,
// `collideConnected`.
// `joint`: the joint's kind (default Revolute)
jointKind: JointKind,
// Joint: name or path of body A ("" = the body the joint is under, or the world)
bodyA: string,
// Joint: name or path of body B
bodyB: string,
// Joint: `offsetX`, `offsetY`: Distance joints' anchor on body B, relative to the joint
jointOffset: [2]f32,
// Joint: `limit`: `lower` and `upper` apply
enableLimit: bool,
// Joint: the lower limit (Revolute: degrees; Distance: length in px; Prismatic, Wheel: px)
lower: f32,
// Joint: the upper limit, in `lower`'s units
upper: f32,
// Joint: `motor`: the motor runs
enableMotor: bool,
// Joint: motor speed (Revolute, Wheel: degrees per second; Distance, Prismatic: px/s)
motorSpeed: f32,
// Joint: the motor's maximum torque (Revolute, Wheel) or force (Distance, Prismatic)
maxMotor: f32,
// Joint: `spring`: the joint is springy
enableSpring: bool,
// Joint: the spring's frequency, in hertz
hertz: f32,
// Joint: `damping`: the spring's damping ratio (default 0.5)
dampingRatio: f32,
// Joint: the two bodies still collide with each other
collideConnected: bool,
// ParticleEmitter: its `<particles>` element (particles.odin)
particles: ParticleSettings,
// Component: the component file it's an instance of (relative to the project folder)
component: string,
// AudioPlayer, AudioPlayer2D, MusicPlayer: the sound (or music) file, the bus ("" Master),
// and `volume`, `pitch`, `pan`, `loop`, `autostart`, `maxDistance`, `attenuation`, `fadeIn`, `fadeOut`.
// `sound`: the sound (or music) file, relative to the project folder
sound: string,
// MusicPlayer: layers played in step with it (`<layer name sound volume/>` children:
// music_layers.odin)
musicLayers: [dynamic]MusicLayerData,
// Sound nodes: the mix bus it plays through ("" for Master)
bus: string,
// Sound nodes: `volume`, `pitch`, `pan`, `loop`, `autostart`, `maxDistance`, `attenuation`,
// `fadeIn`, `fadeOut` (missing ones from DEFAULT_AUDIO_SETTINGS)
audio: AudioSettings,
// LDtkLevel: `ldtk` (the .ldtk file), `level`, `allLevels`, `intGridCollision`,
// `collisionValues` (solid), `oneWayValues`, `areaValues` (format 13), `spawnEntities`,
// `background`, `smoothTiles`, and `<entity name="..." component="..." make="..." script="..."
// noScript="true"/>` children (`make` and the script ones format 13: LDtkEntityRule); the
// generated collision uses `collisionLayer`, `collisionMask`, `friction`, `restitution`
// (ldtk_nodes.odin)
ldtk: LDtkLevelSettings,
// TileMap: `tilemap` (an Aseprite file), `frame`, `layers`, `tileCollision` (tilemap.odin); its
// collision uses `collisionLayer`, `collisionMask`, `friction`, `restitution` too
tilemap: TileMapSettings,
// Animator: `autoplay` (the clip it starts with), `speed`, and `<clip name length loop>`
// children holding `<track node property step>` children holding `<key time value ease/>`
// (animator.odin)
animator: AnimatorSettings,
// UI controls (Panel, Button, ProgressBar, TextField, ScrollBox, RichText): their
// `UISettings` as attributes of the same names (`width` / `height` for size, `patchMargins`
// "l t r b"); their text in `text`, an image in `texture` (ui_controls.odin)
ui: UISettings,
// Light2D: `lightKind`, `color`... see lighting.odin (`LightSettings`: `lightKind`,
// `lightColor`, `energy`, `lightRadius`, `spotAngle`, `shadows`). A LightOccluder's shape uses
// `shape`, `width` / `height`, `radius` and `points` like a CollisionShape's
light: LightSettings,
// NavRegion (its area: `width`, `height`) and NavAgent: `NavSettings` as `cellSize`,
// `agentRadius`, `diagonal`, `speed`, `arriveDistance`, `avoidRadius`, `autoMove`
// (navigation.odin)
nav: NavSettings,
// StateMachine: `start`, `animates` (its AnimatedSprite's path), and `<param name type value>`,
// `<state name animation x y>`, `<transition from to when after onFinished>` children
// (state_machine.odin)
stateMachine: StateMachineSettings,
// ParallaxLayer (`factorX`, `factorY`, `repeatX`, `repeatY`; its image `texture`), CameraZone
// (`width`, `height`, `zoneTransition`), CameraView (`width`, `height`, `viewCamera`,
// `viewZoom`, `border`): camera_tools.odin
cameraTools: CameraToolSettings,
// CameraView: the Camera node it shows (a path from it)
viewCamera: string,
// VirtualJoystick, TouchButton: `TouchControlSettings` as `touchRadius`, `deadzone`,
// `zoneWidth`, `zoneHeight`, `floating`, `touchVisibility`, `touchMouse`, `touchColor`,
// `touchOpacity` (touch.odin; a button's label is `text`)
touch: TouchControlSettings,
// VirtualJoystick, TouchButton: the input actions it presses, comma-separated (a joystick's
// left, right, up, down; a button's one): `touchActions`
touchActions: string,
// Any node: its shader (`shader`: "builtin:outline" or a project path; "" none) and its
// parameters' values (`<shaderParam name value/>` children): shaders.odin
shader: string,
shaderParams: [dynamic]ScriptPropData,
// ScreenEffect: the effect covers the screen layers too (`effectScreenLayers`, default true)
effectScreenLayers: bool,
// Line2D (its `points`, `texture`) and Trail2D (`texture`): `LineSettings` as `lineWidth`,
// `endWidth`, `lineColor`, `endColor`, `joint`, `roundCaps`, `closed`, `textureMode`,
// `lifetime`, `minDistance`, `emitting` (lines.odin)
line: LineSettings,
// Bone2D: `boneLength` (skeleton.odin)
boneLength: f32,
// SkinnedMesh (its `texture`, `region`): the `<mesh vertices uvs triangles bones weights/>` child
mesh: MeshData,
// IKChain: `ikTip`, `ikTarget` (paths from it), `ikLength`, `ikBend` ("positive" / "negative"),
// `ikEnabled`
ik: IKSettings,
ikTip, ikTarget: string,
// DialogueBox: `dialogue` (a `.dialogue.xml` asset), `startNode`, and `DialogueBoxSettings` as
// `boxWidth`, `boxHeight`, `typeSpeed`, `textSize`, `boxFont`, `boxPadding`, `boxColor`,
// `textColor`, `nameColor`, `pickColor`, `boxAutostart`, `hideWhenDone`
dialogue, dialogueStart: string,
dialogueBox: DialogueBoxSettings,
}

One node from a scene file. Fields that don’t apply to a node’s kind are ignored. Each field is read from the attribute of the same name unless its comment names another one; a missing (or unreadable) attribute keeps the value in DEFAULT_SCENE_NODE.

struct. Source: project_format.odin:428

AnimationData :: struct {
// The name scripts play it by (missing: "default")
name: string,
// First and last cell played, inclusive; end -1 is the sheet's last cell
start: int,
// The last cell played (inclusive; -1, the default: the sheet's last cell)
end: int,
// Frames per second (default 8)
fps: f32,
// Plays again from the start when it ends (default false, unlike the node's `loop`)
loop: bool,
// Forward (default), or another `AnimationPlayMode`
playMode: AnimationPlayMode,
// Seconds each frame shows (`durations`, milliseconds in the file; empty: 1 / fps each)
durations: [dynamic]f32,
// Times a non-looping animation plays (0 or 1: once)
repeat: int,
// Each frame's events (`events="step;;hit,shake"`: frames apart by ';', a frame's events by
// ','; "" for none): an Aseprite file's from its cels' text or `event` property
events: [dynamic]string,
}

An AnimatedSprite’s animation (or an animation asset’s): a named range of its sheet’s cells.

<animation name="walk" start="0" end="3" fps="10" loop="true" playMode="Forward"/>
<animation name="hit" start="4" end="6" durations="100 60 200" repeat="2"/>

durations (milliseconds per frame, format 13) override fps for the frames they cover; repeat plays a non-looping animation that many times.

struct. Source: project_format.odin:703

AnimationSetData :: struct {
// The sheet: an image path relative to the project folder (an Aseprite sheet's ends in
// ASEPRITE_SHEET_SUFFIX)
texture: string,
// The sheet's grid
columns, rows: int,
// Pixels around the grid, and between cells
margin, spacing: f32,
// The animation played first ("": the first)
autoplay: string,
// Its animations, in order
animations: [dynamic]AnimationData,
}

An animation asset: a sheet and named animations over its cells, which AnimatedSprite nodes (and scripts: AnimatedSprite_SetAnimations) share. An .anim.xml file (asset kind “Animation”):

<?xml version="1.0" encoding="UTF-8"?>
<animations formatVersion="13" texture="assets/hero.png" columns="8" rows="2" autoplay="idle">
<animation name="idle" start="0" end="3" fps="6" loop="true"/>
<animation name="run" start="8" end="15" fps="12" loop="true"/>
</animations>

An Aseprite file is one too, read by AnimationSet_FromAseprite: its frames on a sheet, a animation per tag.

struct. Source: project_format.odin:738

ScriptPropData :: struct {
// The exported field's name
name: string,
// The value in its text form (see `Script_SetField`)
value: string,
}

A script field’s value in a scene file: <prop name="speed" value="250"/>

struct. Source: project_format.odin:828

SceneData :: struct {
// The root element's `name`
name: string,
// Every node, depth-first: a node's children follow it directly (`SceneNodeData.parent`)
nodes: [dynamic]SceneNodeData,
// The root element's scene-wide settings (format 13)
settings: SceneSettings,
}

A parsed scene (or component) file: ParseScene, ParseComponent. Free with SceneData_Destroy.

struct. Source: project_format.odin:878

SceneSettings :: struct {
// Layers drawn in screen space, ignoring the camera (`Scene.screenLayers`): HUDs and menus.
// `screenLayers="7 8"` (layer numbers 1-8)
screenLayers: bit_set[RenderLayer],
// Clear the screen with `background` instead of the project's color (`background="#rrggbb"`)
hasBackground: bool,
// The scene's background color, when `hasBackground`
background: sdl.Color,
// Use `gravity` instead of the project's (`gravityX`, `gravityY`)
hasGravity: bool,
// The scene's gravity in px/s², y down, when `hasGravity`
gravity: [2]f32,
// 2D lighting (lighting.odin): the world layers are dark outside lights (`lighting="true"`)
lighting: bool,
// The light everywhere (`ambient="#rrggbb"`; zero: `DEFAULT_AMBIENT`)
ambient: sdl.Color,
// Static bodies' shapes cast shadows (`bodyShadows="true"`)
bodyShadows: bool,
}

A scene’s own settings, from its file’s root element (format 13): <scene name="Level" screenLayers="8" background="#203040" gravityX="0" gravityY="490">. Scene_Build applies them to the scene. The zero value is a scene without any.

struct. Source: project_format.odin:892

SceneRef :: struct {
// The scene's name (what `game->switchScene` and menu actions use)
name: string,
// Scene file path relative to the project folder
path: string,
}

A scene of a project: <scene name="MenuScene" path="scenes/MenuScene.scene.xml"/>

struct. Source: project_format.odin:957

AssetRef :: struct {
// The file, relative to the project folder
path: string,
// "Image", "Audio", "Music", "Font", "Data", "Component", "LDtk" or "Translation"
kind: string,
// An image's filtering (`filter="Pixel"`, format 13; Default: the project's
// `textureFilter`; asset_import.odin)
filter: TextureFilter,
// A TrueType font's MSDF atlas (`msdf="true"`, format 13): its text drawn sharp at any size on
// the GPU renderer (fonts.odin; editors make the atlas)
msdf: bool,
}

An asset a project lists: <asset path="assets/ship.png" kind="Image"/>

struct. Source: project_format.odin:965

ProjectData :: struct {
// The game's name (`name`)
name: string,
// `width`, `height`: the game's size in pixels (default 1280 x 720)
size: [2]f32,
// How the game fits its window (`scaleMode`; default Letterbox)
scaleMode: ScaleMode,
// The color behind everything (`background`; default black)
background: sdl.Color,
// The scene the game starts in (`startScene`; missing: the first scene listed)
startScene: string,
// Its scenes, in file order (`<scene>` elements)
scenes: [dynamic]SceneRef,
// Its assets, in file order (`<asset>` elements)
assets: [dynamic]AssetRef,
// pixelsPerMeter, gravityX / gravityY (px/s²), physicsDebug, physicsSmoothing
physics: PhysicsSettings,
// Global scripts, in start order: `<global script="GameState"><prop .../></global>` (globals.odin)
globals: [dynamic]GlobalData,
// Input actions, in order: `<action name="jump"><key key="Space"/></action>` (input.odin)
actions: [dynamic]InputActionData,
// Mix buses (Master's settings too, if listed): `<bus name="Music" volume="0.8"/>` (audio_nodes.odin)
buses: [dynamic]AudioBusData,
// Steam (steam.odin): `steamAppId`, `steamCloud`, and `<achievement name title description
// hidden/>` children (format 13)
steam: SteamSettings,
achievements: [dynamic]SteamAchievementData,
// Local players (players.odin): `<players max="4" keyboard="true" joinAction="join"/>`
players: PlayerSettings,
// The renderer games draw with (`renderer="Vulkan"`, format 11; Auto: SDL's choice, or the
// GPU renderer when the project uses shaders)
renderer: RenderBackend,
// Some scene uses a shader (`shaders="true"`, written by editors): Auto then means GPU
usesShaders: bool,
// The game's window (`fullscreen`, `vsync`, `highDPI`, format 13)
window: WindowSettings,
// The game window's icon: an image path relative to the project (`icon="assets/icon.png"`,
// format 13); "" for the engine's (the Crossfire logo)
icon: string,
// How images are filtered when they're drawn scaled or turned, unless an asset says otherwise
// (`textureFilter="Pixel"`, format 13; Default: Smooth, Aseprite files Pixel; asset_import.odin)
textureFilter: TextureFilter,
// The language to start in (`locale="fr"`, format 13); "" for the player's system language.
// The translation tables are the assets of kind "Translation" (localization.odin)
locale: string,
// The UI theme controls use when they and their parents name none (`uiTheme`, a `.theme.xml`
// asset, format 13; ui_theme.odin)
uiTheme: string,
// How scene changes look (`transition`: a `TransitionKind`, `transitionTime` seconds,
// `transitionColor`; format 13; scene_flow.odin)
transition: Transition,
// The scene shown while scenes load (`loadingScene`; "": none: every scene is built at start)
loadingScene: string,
// The editor's grid for this project (`<grid width="16" height="16"/>`, format 13); games
// don't use it
grid: GridSettings,
// The format the game's sound output asks for (`audioFrequency`, `audioChannels`, format 13;
// 0 for the device's own). The device isn't the project's: the player's `--audio-device`
audioOutput: AudioOutputSettings,
}

A parsed project.xml (ParseProject, LoadProjectFile). Free with ProjectData_Destroy.

struct. Source: project_format.odin:981

GridSettings :: struct {
// A cell's width and height, world units (GRID_SIZE_RANGE)
size: [2]int,
// A major (more opaque) line every this many cells; 0 = none (GRID_MAJOR_RANGE)
majorEvery: int,
// The lines' opacity, percent (the editor draws them dark gray)
opacity: int,
// The major lines' opacity, percent
majorOpacity: int,
}

A project’s grid, as tools show it and snap to it (the editor’s viewport). Games don’t use it.

struct. Source: project_format.odin:1045

WindowSettings :: struct {
// Start fullscreen (the desktop's resolution; the game scales into it)
fullscreen: bool,
// Wait for the display's refresh (no tearing; the frame rate follows the display)
vsync: bool,
// Use the display's full pixel density (sharper on high-DPI and Retina screens; more pixels to draw)
highDPI: bool,
}

How a game’s window starts. Games change fullscreen and vsync while running with Game_SetFullscreen / Game_SetVSync (an options menu).

struct. Source: project_format.odin:1068

ProjectError :: enum {
None,
// The file couldn't be read (`LoadProjectFile`, `LoadSceneFile`)
File_Not_Found,
// The text isn't well-formed XML, or is empty
Invalid_XML,
// The root element isn't <project> / <scene> / <component>, as the parser expected
Wrong_Root,
// formatVersion is newer than PROJECT_FORMAT_VERSION
Unsupported_Version,
}

What went wrong reading a project, scene or component file

enum. Source: project_format.odin:1081

Sprite :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// The image it draws (the resource manager's; nil draws nothing)
texture: ^sdl.Texture,
// Its Aseprite file, when the image is one (full path, node arena; "" otherwise): its slices
source: string,
// The slice whose pivot is drawn at the origin ("": the middle; aseprite_nodes.odin)
pivotSlice: string,
// A 9-patch slice it draws stretched to `nineSize` ("": the whole image)
nineSlice: string,
// The 9-patch's size (0: the slice's own)
nineSize: [2]f32,
// The part of `texture` it draws, in pixels: an atlas region (atlas.odin; zero: all of it)
region: sdl.FRect,
}

A texture drawn centered on the node’s origin. Created from Sprite nodes in scene files; its size comes from the texture and the node’s scale.

struct. Source: project_loader.odin:16

AnimatedSprite :: struct {
// The node part: transform, hierarchy, hooks
using node: Node,
// Its animations and the one playing (updated with the node; allocated in the node's arena)
animation: AnimationPlayer,
// Its Aseprite file, when its sheet is one (full path, node arena; "" otherwise): its slices
source: string,
// Its cells are the file's frames (an Aseprite file's animations), so slices follow them
sourceFrames: bool,
// The slice whose pivot is drawn at the origin, frame by frame ("": the middle)
pivotSlice: string,
// The Aseprite layers it draws ("": every visible one; node arena)
layers: string,
}

A sprite sheet animation drawn centered on the node’s origin. Created from AnimatedSprite nodes: its player holds the node’s animations (by name; “default” when the scene file has none) and starts the autoplay one. Scripts switch with AnimationPlayer_Play(&sprite.animation, "jump").

struct. Source: project_loader.odin:38

Scene_Instantiate :: Scene_AddComponent

Another name for Scene_AddComponent (its first name)

type. Source: project_loader.odin:341

ParseAnimationSet :: proc(text: string, allocator := context.allocator) -> (set: AnimationSetData, err: ProjectError)

Reads an .anim.xml file.

Inputs

  • text: The XML
  • allocator: For the result’s strings and lists

Returns

procedure. Source: project_format.odin:762

AnimationSetData_Destroy :: proc(set: ^AnimationSetData, allocator := context.allocator)

Frees an animation set’s strings and lists. Must use the allocator it was made with.

Inputs

  • set: The set
  • allocator: Its allocator

procedure. Source: project_format.odin:788

FormatScreenLayers :: proc(layers: bit_set[RenderLayer], allocator := context.allocator) -> string

Formats screen layers as scene files store them: layer numbers 1-8 separated by spaces (“” for none).

Inputs

  • layers: The layers
  • allocator: For the text

Returns

  • The text, e.g. “7 8”

procedure. Source: project_format.odin:923

ParseScreenLayers :: proc(text: string) -> (bit_set[RenderLayer], bool)

Parses screen layers: layer numbers 1-8 separated by spaces or commas (“” = none).

Inputs

  • text: The text

Returns

  • The layers, and false if a part wasn’t a layer number (the valid ones are kept)

procedure. Source: project_format.odin:941

ParseProject :: proc(text: string, allocator := context.allocator) -> (project: ProjectData, err: ProjectError)

Parses the text of a project.xml file.

Inputs

  • text: The XML
  • allocator: Allocator for the result’s strings and lists

Returns

Example

// Given: xmlText: string
project, err := e.ParseProject(xmlText)
if err != .None do log.errorf("Can't read the project: %v", err)
defer e.ProjectData_Destroy(&project)

procedure. Source: project_format.odin:1109

ParseScene :: proc(text: string, allocator := context.allocator) -> (scene: SceneData, err: ProjectError)

Parses the text of a scene file.

Inputs

  • text: The XML
  • allocator: Allocator for the result’s strings and lists

Returns

  • The scene data with its nodes in depth-first order, and .None or what went wrong. Free with SceneData_Destroy

Example

// Given: xmlText: string
data, err := e.ParseScene(xmlText)
if err != .None do log.errorf("Can't read the scene: %v", err)
defer e.SceneData_Destroy(&data)
for n in data.nodes do fmt.println(n.kind, n.name)

procedure. Source: project_format.odin:1262

ParseComponent :: proc(text: string, allocator := context.allocator) -> (component: SceneData, err: ProjectError)

Parses the text of a component file: one node tree saved to be reused in any scene.

<?xml version="1.0" encoding="UTF-8"?>
<component formatVersion="9" name="Ship">
<node kind="Sprite" name="Ship" texture="assets/ship.png">
<node kind="ParticleEmitter" name="Exhaust" y="30"/>
</node>
</component>

Its first top-level node is the root. A component has one root: top-level nodes after it are read as the root’s last children, their values as they are (an older editor let nodes be added beside the root; they were dropped). Scenes use it through a Component node, <node kind="Component" name="Ship2" x="100" y="50" component="components/ship.component.xml"/>: the component’s nodes are built in its place, the root taking the instance’s name, transform, layer and visibility (Scene_Build); Scene_AddComponent adds one from code.

Inputs

  • text: The XML
  • allocator: Allocator for the result’s strings and lists

Returns

  • The nodes (depth-first, the root first), and .None or what went wrong. Free with SceneData_Destroy

procedure. Source: project_format.odin:1316

ProjectData_Destroy :: proc(project: ^ProjectData, allocator := context.allocator)

Frees everything ParseProject allocated and zeroes the project. Must use the same allocator.

Inputs

  • project: The project data
  • allocator: The allocator it was parsed with

procedure. Source: project_format.odin:1340

SceneData_Destroy :: proc(scene: ^SceneData, allocator := context.allocator)

Frees everything ParseScene (or ParseComponent) allocated and zeroes the scene. Must use the same allocator.

Inputs

  • scene: The scene data
  • allocator: The allocator it was parsed with

procedure. Source: project_format.odin:1382

FormatColor :: proc(color: sdl.Color, allocator := context.allocator) -> string

Formats a color the way the format stores it: #rrggbb, or #rrggbbaa when not opaque.

Inputs

  • color: The color
  • allocator: Allocator for the string

Returns

  • The color text

procedure. Source: project_format.odin:1468

ParseColor :: proc(text: string) -> (sdl.Color, bool)

Parses #rrggbb or #rrggbbaa.

Inputs

  • text: The color text

Returns

  • The color, and false if the text isn’t a valid color

procedure. Source: project_format.odin:1482

FormatPhysicsLayers :: proc(layers: u64, allocator := context.allocator) -> string

Formats collision layers the way the format stores them: all, the layer numbers (“1 3”), or “” for none.

Inputs

  • layers: The layer bits (layer n is bit n-1)
  • allocator: Allocator for the string

Returns

  • The text

procedure. Source: project_format.odin:1502

ParsePhysicsLayers :: proc(text: string) -> (u64, bool)

Parses collision layers: all, or layer numbers 1-64 separated by spaces or commas (“” = none).

Inputs

  • text: The layers’ text

Returns

  • The layer bits (layer n is bit n-1), and false if a part isn’t a layer number 1-64 (it’s skipped; the others are still in the bits)

procedure. Source: project_format.odin:1523

FormatPoints :: proc(points: [][2]f32, allocator := context.allocator) -> string

Formats points the way the format stores them: “x,y x,y”.

Inputs

  • points: The points
  • allocator: Allocator for the string

Returns

  • The text

procedure. Source: project_format.odin:1550

ParsePoints :: proc(text: string) -> ([][2]f32, bool)

Parses points: “x,y x,y …”.

Inputs

  • text: The points’ text

Returns

  • The points (temp allocated), and false if one couldn’t be read (it’s skipped)

procedure. Source: project_format.odin:1568

LoadProjectFile :: proc(path: string, allocator := context.allocator) -> (ProjectData, ProjectError)

Reads and parses a project.xml file.

Inputs

Returns

  • The project data, and .None or what went wrong (.File_Not_Found when it can’t be read)

Example

project, err := e.LoadProjectFile("MyGame/project.xml")
if err != .None do log.errorf("Can't load the project: %v", err)
defer e.ProjectData_Destroy(&project)

procedure. Source: project_loader.odin:68

LoadSceneFile :: proc(path: string, allocator := context.allocator) -> (SceneData, ProjectError)

Reads and parses a scene file.

Inputs

  • path: Path to the .scene.xml file
  • allocator: Allocator for the result. Free with SceneData_Destroy

Returns

  • The scene data, and .None or what went wrong (.File_Not_Found when it can’t be read)

procedure. Source: project_loader.odin:84

Game_LoadProject :: proc(game: ^Game, project: ^ProjectData, projectDir: string) -> bool

Creates every scene of a project in a game and switches to its start scene. Also sets the game’s background color, physics settings, name (unless code set one), input actions, mix buses, translations and global scripts from the project. Call from the game’s build procedure, after game.size and game.scaleMode were set from the project (the scenes’ cameras and menus are positioned for that size).

Asset paths are resolved against the project folder, so the game can run from any directory.

Inputs

  • game: The game, during Game_Init
  • project: The parsed project.xml
  • projectDir: Folder containing project.xml

Returns

  • false if a scene file couldn’t be loaded (the others are still created)

Example

// In the game's build procedure (game.size and game.scaleMode already set from the project)
project, err := e.LoadProjectFile("MyGame/project.xml")
if err != .None do return
if !e.Game_LoadProject(game, &project, "MyGame") do log.warn("Some scenes didn't load")

procedure. Source: project_loader.odin:113

Scene_Build :: proc(scene: ^Scene, data: ^SceneData, projectDir: string, allocator := context.temp_allocator, attachScripts := true) -> []^Node

Creates a scene’s nodes from parsed scene data, with the same parent/child structure.

A Camera node becomes a Camera2D node; the first one becomes the scene’s camera. Hidden nodes are created hidden, so game code can show them later. Nodes keep their names (Scene_FindNode), and nodes with a script get it attached (it must be registered: see script.odin); its ready runs on the scene’s next update.

Inputs

  • scene: The scene to fill
  • data: The parsed scene
  • projectDir: Folder that asset paths are relative to
  • allocator: Allocator for the returned list
  • attachScripts: Attach scripts. The editor’s viewport passes false: scripts don’t run there

Returns

  • The created nodes, one per entry in data.nodes (same order). Editors use it to keep the nodes in sync with their own data; the caller owns the slice

procedure. Source: project_loader.odin:198

SceneNodeKind_IsType :: proc(kind: SceneNodeKind, typeName: string) -> bool

Whether nodes of a kind can go in a script’s node field pointing to typeName (a node type’s name without its package, as ScriptField.hint gives it): the kind’s type is that type, or embeds it (DynamicBody embeds PhysicsBody, every node type embeds Node). Editors use it to offer the nodes that fit a field.

Inputs

  • kind: The node kind
  • typeName: The type’s name (“Node”, “Sprite”, “PhysicsBody”…)

Returns

  • Whether the field accepts the kind’s nodes

Example

assert(e.SceneNodeKind_IsType(.DynamicBody, "PhysicsBody"))

procedure. Source: project_loader.odin:271

Scene_ApplySettings :: proc(scene: ^Scene, settings: SceneSettings)

Applies a scene file’s settings to a scene: its screen layers, and its background color and gravity (none: the project’s). Scene_Build does it.

Inputs

  • scene: The scene
  • settings: The settings

procedure. Source: project_loader.odin:291

Scene_AddComponent :: proc(scene: ^Scene, component: ComponentAsset, parent: ^Node = nil, position: [2]f32 = {}) -> ^Node

Adds an instance of a component to a scene: its whole node tree, built like a scene file’s, with its scripts. Like Scene_AddNode, the nodes join the scene at the start of its next update (onEnter, then the scripts’ ready). Call it as often as needed: each call makes another instance. The component’s file is read once per game and cached. Scene_RemoveComponent removes an instance.

Inputs

  • scene: The scene
  • component: The component (a COMPONENT_... constant, or an exported e.ComponentAsset field)
  • parent: The node to add it under (nil: the top level)
  • position: Where its root goes, relative to the parent

Returns

  • The instance’s root node (nil, logged, if the component can’t be read). It takes the component’s name; Node_GetComponent tells which component it is

Example

e.ComponentAsset
bullet := e.Scene_AddComponent(self.scene, COMPONENT_BULLET, position = e.Node_GetWorldPosition(self.node))

procedure. Source: project_loader.odin:325

Scene_RemoveComponent :: proc(scene: ^Scene, instanceId: EntityId) -> bool

Removes a component instance, its whole tree, like Scene_RemoveNode: at the start of the next update they get onExit, then their destroy hooks, then their memory is freed. Only an instance’s root is accepted (a node made by Scene_AddComponent, or a scene file’s Component node), so a stray id can’t remove part of an instance; use Scene_RemoveNode for any node.

Inputs

  • scene: The scene
  • instanceId: The instance root’s id

Returns

  • false (logged) when no node of the scene has that id, or it isn’t an instance’s root

Example

// Given: self: ^e.Script, bullet: ^e.Node (from e.Scene_AddComponent)
e.Scene_RemoveComponent(self.scene, bullet.id)

procedure. Source: project_loader.odin:360

Node_GetComponent :: proc(node: ^Node) -> (ComponentAsset, bool)

The component a node is the root of an instance of.

Inputs

  • node: The node

Returns

  • The component (its path, like the COMPONENT_... constants), and false for nodes that aren’t an instance’s root (nodes inside an instance included)

procedure. Source: project_loader.odin:384

Scene_GetComponentInstances :: proc(scene: ^Scene, component: ComponentAsset) -> []^Node

Every instance of a component in a scene: their roots, in the scene and waiting to join it (in tree order, then the order they were added). Removed instances are listed until the next update frees them.

Inputs

  • scene: The scene
  • component: The component

Returns

  • The instances’ roots (temp allocated)

Example

e.ComponentAsset
for bullet in e.Scene_GetComponentInstances(scene, COMPONENT_BULLET) do e.Scene_RemoveComponent(scene, bullet.id)

procedure. Source: project_loader.odin:405

Game_LoadComponent :: proc(game: ^Game, path: string) -> (^SceneData, bool)

A component’s parsed file, read the first time and kept for the game’s life, in the game’s arena (instances share it). A file that can’t be read is remembered too: it isn’t read again.

Inputs

  • game: The game
  • path: The component file, relative to the project folder (Game.projectDir)

Returns

  • The component’s nodes (don’t free them), and false (logged) if it can’t be read

procedure. Source: project_loader.odin:424

PROJECT_FORMAT_VERSION :: 13

Version written by the editor. This loader also reads older versions

constant. Source: project_format.odin:210

SCENE_NODE_CATEGORY_NAMES := [SceneNodeCategory]string{
.Scene = "Scene",
.Graphics = "Graphics",
.UI = "User interface",
.Physics = "Physics",
.Audio = "Sound",
}

Each category’s name as tools show it

variable. Source: project_format.odin:312

SCENE_NODE_KINDS := [SceneNodeKind]SceneNodeKindInfo{
.Node2D = {"Groups nodes; moving, rotating or scaling it moves its children", .Scene},
.Sprite = {"Draws an image", .Graphics},
.AnimatedSprite = {"Plays a sprite sheet animation", .Graphics},
.Camera = {"Chooses the part of the scene the game shows; can follow a parent node", .Scene},
.Label = {"Draws a line of text", .UI},
.Menu = {"A list of buttons that switch scenes or quit the game", .UI},
.StaticBody = {"A physics body that doesn't move by itself: walls, floors, platforms", .Physics},
.DynamicBody = {"A physics body moved by gravity, collisions and forces", .Physics},
.CharacterMover = {"A body moved by code that slides along walls and floors: players, enemies", .Physics},
.Area = {"A region that reports bodies entering and leaving it: pickups, triggers", .Physics},
.CollisionShape = {"The shape of the physics body above it: rectangle, circle, capsule, polygon, segment", .Physics},
.RayCast = {"A ray that reports the first physics body in its way: line of sight, ground checks", .Physics},
.Joint = {"Connects two physics bodies: a hinge, rope, spring, slider, weld or wheel", .Physics},
.ParticleEmitter = {"Emits particles that move, spin, grow and change color: sparks, smoke, fire, rain", .Graphics},
.Component = {"An instance of a component: a node tree saved in the assets, reused in any scene", .Scene},
.AudioPlayer = {"Plays a sound: volume, pitch, pan, looping, through a mix bus", .Audio},
.AudioPlayer2D = {"Plays a sound in the world: panned and quieter with distance from the camera", .Audio},
.MusicPlayer = {"Streams music: fades, looping, seeking, through a mix bus", .Audio},
.LDtkLevel = {"A level made in the LDtk level editor: its tile layers, entities and IntGrid collision", .Scene},
.TileMap = {"Tilemap layers drawn in Aseprite: their tiles, and collision from the tiles' properties", .Graphics},
.Panel = {"A box that lays out the controls in it (rows, columns, grids) on a themed or 9-patch background", .UI},
.Button = {"A button with text or a picture: switches scenes, quits, or tells scripts it was pressed", .UI},
.ProgressBar = {"A bar showing a value in its range: health, stamina, loading", .UI},
.TextField = {"A box the player types a line of text in: names, chat, codes", .UI},
.ScrollBox = {"A column of controls that scrolls: long lists, inventories, credits", .UI},
.RichText = {"Text with colors and sizes that wraps: dialogue, descriptions, tooltips", .UI},
.Light2D = {"A light in a lit scene: a point or a spot cone, colored, with shadows from occluders", .Graphics},
.ParallaxLayer = {"A background that moves slower than the world as the camera moves (depth), tiled if asked: skies, hills", .Graphics},
.CameraZone = {"A room the camera stays inside while it's in it; moving to another slides the view over", .Scene},
.CameraView = {"What another camera sees, in a rectangle on the screen: split screen, minimaps, picture in picture", .Graphics},
.StateMachine = {"Keeps one of a set of states (idle, run, jump) and moves between them on conditions; plays each state's animation", .Scene},
.NavRegion = {"Where characters can walk: a grid over an area, blocked by static bodies, for finding paths", .Scene},
.NavAgent = {"Finds paths through a NavRegion and moves its parent along them, around other agents", .Scene},
.LightOccluder = {"A shape that blocks light and casts shadows: walls, pillars, trees", .Graphics},
.Animator = {"Animates other nodes' properties over time with keyframes: cutscenes, moving platforms, UI, effects", .Graphics},
.VirtualJoystick = {"An on-screen stick for touch screens, pushing input actions (moving left, right, up, down)", .UI},
.TouchButton = {"An on-screen round button for touch screens, pressing an input action (jump, fire)", .UI},
.ScreenEffect = {"A shader over the whole scene as drawn: CRT, bloom, color grading (GPU renderer)", .Graphics},
.Line2D = {"A thick line through points: ropes, lasers, paths, with a color gradient, round joints, a texture", .Graphics},
.Trail2D = {"A ribbon left behind its parent as it moves: sword swings, dashes, comets, tire tracks", .Graphics},
.Skeleton2D = {"The root of a character's bones: animate the bones, and the pictures on them follow", .Graphics},
.Bone2D = {"A bone of a skeleton: its children (pictures, more bones) move with it; key it in the Timeline", .Graphics},
.SkinnedMesh = {"A picture bent over bones: a mesh whose points follow weighted bones (soft bodies, sleeves, tails)", .Graphics},
.DialogueBox = {"Shows a conversation: the speaker, each line typed out, options to choose (a dialogue asset, or Yarn)", .UI},
.IKChain = {"Turns a chain of bones so its tip reaches a target: arms reaching, feet planted on the ground", .Graphics},
}

Every node kind a scene file can contain, with its description. Tools list node kinds from this table, so a kind added to SceneNodeKind must be described here (the compiler enforces it).

Example

for kind in e.SceneNodeKind {
info := e.SCENE_NODE_KINDS[kind]
fmt.printfln("%s / %v: %s", e.SCENE_NODE_CATEGORY_NAMES[info.category], kind, info.description)
}

variable. Source: project_format.odin:340

DEFAULT_SCENE_NODE :: SceneNodeData{
parent = -1,
scale = {1, 1},
visible = true,
fontSize = 24,
color = {255, 255, 255, 255},
centered = true,
zoom = 1,
columns = 1,
rows = 1,
fps = 8,
loop = true,
collisionLayer = 1,
collisionMask = PHYSICS_ALL_LAYERS,
friction = 0.6,
density = 1,
gravityScale = 1,
floorMaxAngle = 45,
shapeSize = {32, 32},
radius = 16,
target = {0, 50},
enabled = true,
excludeParent = true,
jointOffset = {0, 100},
dampingRatio = 0.5,
particles = DEFAULT_PARTICLE_SETTINGS,
audio = DEFAULT_AUDIO_SETTINGS,
ldtk = DEFAULT_LDTK_LEVEL_SETTINGS,
tilemap = DEFAULT_TILEMAP_SETTINGS,
animator = DEFAULT_ANIMATOR_SETTINGS,
ui = DEFAULT_UI_SETTINGS,
light = DEFAULT_LIGHT_SETTINGS,
nav = DEFAULT_NAV_SETTINGS,
cameraTools = DEFAULT_CAMERA_TOOL_SETTINGS,
boneLength = 32,
ik = DEFAULT_IK_SETTINGS,
dialogueBox = DEFAULT_DIALOGUE_BOX_SETTINGS,
}

Values used for attributes a node doesn’t specify

constant. Source: project_format.odin:836

DEFAULT_GRID_SETTINGS :: GridSettings{size = {32, 32}, majorEvery = 4, opacity = 60, majorOpacity = 90}

A project’s grid without a <grid> element: 32 px cells, a major line every 4

constant. Source: project_format.odin:1057

GRID_SIZE_RANGE :: [2]int{2, 1024}

The values a grid’s cell size may take (parsing clamps to it)

constant. Source: project_format.odin:1060

GRID_MAJOR_RANGE :: [2]int{0, 64}

The values a grid’s major line spacing may take, in cells

constant. Source: project_format.odin:1062

DEFAULT_WINDOW_SETTINGS :: WindowSettings{vsync = true}

A window as games had it before these settings: windowed, vsync, not high-DPI

constant. Source: project_format.odin:1078

SCENE_NODE_TYPES := [SceneNodeKind]typeid{
.Node2D = Node,
.Sprite = Sprite,
.AnimatedSprite = AnimatedSprite,
.Camera = Camera2D,
.Label = Label,
.Menu = Menu,
.StaticBody = StaticBody,
.DynamicBody = DynamicBody,
.CharacterMover = CharacterMover,
.Area = Area,
.CollisionShape = CollisionShape,
.RayCast = RayCast,
.Joint = Joint,
.ParticleEmitter = ParticleEmitter,
.Component = Node,
.AudioPlayer = AudioPlayer,
.AudioPlayer2D = AudioPlayer,
.MusicPlayer = AudioPlayer,
.LDtkLevel = LDtkLevelNode,
.TileMap = TileMapNode,
.Animator = Animator,
.Panel = Panel,
.Button = Button,
.ProgressBar = ProgressBar,
.TextField = TextField,
.ScrollBox = ScrollBox,
.RichText = RichText,
.Light2D = Light2D,
.LightOccluder = LightOccluder,
.NavRegion = NavRegion,
.NavAgent = NavAgent,
.StateMachine = StateMachine,
.ParallaxLayer = ParallaxLayer,
.CameraZone = CameraZone,
.CameraView = CameraView,
.VirtualJoystick = VirtualJoystick,
.TouchButton = TouchButton,
.ScreenEffect = ScreenEffect,
.Line2D = Line2D,
.Trail2D = Trail2D,
.Skeleton2D = Skeleton2D,
.Bone2D = Bone2D,
.SkinnedMesh = SkinnedMesh,
.IKChain = IKChain,
.DialogueBox = DialogueBox,
}

The engine type a scene file’s node kind is built as (Node2D is a plain Node; a Component instance is its component’s root, so it has no type of its own: Node here).

variable. Source: project_loader.odin:207