Core: application, game, scenes and nodes
The application and its frame loop, the game and its scenes, nodes and their transforms, events, timers and the package’s memory model.
Files: app.odin, game.odin, scene.odin, node.odin, transform.odin, event.odin, node_events.odin, timer.odin, ticker.odin, layers.odin, custom_nodes.odin.
File overviews
Section titled “File overviews”node_events.odin
Section titled “node_events.odin”Node events for tools and scene files. Every event worth listening to is an EventSubject field
of a node type, tagged with what it means and how to read its data:
onFinished: EventSubject `event:"It finished" data:"index := data.(int)"`,NodeType_Events lists a type’s tagged subjects (its own, those of the types it embeds, and those
of struct fields such as an AnimatedSprite’s animation, named “animation.onAnimationEnded”), so
an editor shows them and scene files name them.
Scene files connect them to scripts: <listener event target handler/> inside a node
(NodeListenerData). A script registers its handlers by name with Script_Listener; just before
the scene’s scripts get ready, each listener’s handler is subscribed (bindListeners), and is
called with the listening script, the node whose event it is, and the event’s data. The
listening node is looked up by id on each call: a removed one is skipped.
Hero_onCoinCollected :: proc(self: ^Hero, source: ^e.Node, data: e.EventData) { self.coins += 1}
@(init)register_Hero_onCoinCollected :: proc "contextless" () { e.Script_Listener(Hero, "onCoinCollected", Hero_onCoinCollected)}custom_nodes.odin
Section titled “custom_nodes.odin”Custom node types: a struct of the game’s own that starts with a built-in node type, registered
under its name, and used in scene files as a kind of its own (kind="Player").
Player :: struct { using sprite: e.Sprite, // the base kind: everything a Sprite is and does speed: f32 `export:"default=200"`, // its own settings, set from scene files lives: int `export:"default=3"`,}
@(init)register_Player :: proc "contextless" () { e.NodeType_Register(Player, .Sprite, { update = proc(self: ^Player, events: []sdl.Event, dt: f64) { self.position.x += self.speed * f32(dt) // self is the node }, })}- What it is: a node of the base kind, built from the scene file’s attributes as usual (a
Sprite’s texture, a body’s shapes…), allocated as the bigger struct;
node.typeis the custom type (Node_Is(node, Player),cast(^Player)node), andNode_Iswith the base type is false: test withSceneNodeKind_IsTypeortypeEmbedsfor “a kind of Sprite”. - Its hooks and fields are a script’s (
ScriptHooks,exporttags: script.odin), with the node itself asself, run before any scripts attached to it (scripts attach as usual). Its exported fields’ values are<prop name value/>children of the node. - Scene files say
kind="Player" baseKind="Sprite": where Player isn’t registered (an editor, a player without the game’s scripts) the node is built as its base kind. - The base type must be the struct’s first field (
using sprite: e.Sprite); one that isn’t, or whose base kind’s type differs, isn’t registered (printed on stderr).
Application
Section titled “Application”Application :: struct { // The window opened by `Application_Setup` window: ^sdl.Window, // The window's renderer, shared by every game the application runs renderer: ^sdl.Renderer, // The audio mixer from `Audio_Init` mixer: ^mix.Mixer, // True while the frame loop runs; false once the game sets `shouldQuit` running: bool, // The game being played. nil until the first frame after `App_SetGame` game: ^Game, // The game `App_SetGame` asked for; the next frame switches to it queuedGame: ^Game,}The program’s window, renderer and audio device, and the game they run.
Set it up with Application_Setup, run it with Application_Run and free it with
Application_Cleanup. One application runs one Game at a time; App_SetGame swaps it.
struct. Source: app.odin:19
ScaleMode
Section titled “ScaleMode”ScaleMode :: enum { // Keep the aspect ratio, fill the rest with black bars Letterbox, // Fill the window, distorting the aspect ratio Stretch, // Scale by whole multiples only, for crisp pixel art IntegerScale, // Keep the aspect ratio, crop the edges that don't fit Overscan, // No scaling: game coordinates are window coordinates None,}How the game’s fixed resolution (Game.size) is fit into the window
enum. Source: game.odin:13
Game :: struct { // The audio mixer (from the application) mixer: ^mix.Mixer, // The window the game draws into (from the application) window: ^sdl.Window, // The renderer the game draws with (from the application) renderer: ^sdl.Renderer, // Set it to end the game: the application stops after the current frame. Closing the window // sets it shouldQuit: bool, // Quit when Escape is pressed. Set to false in build if the game uses Escape itself quitOnEscape: bool, // Developer keys, for games run from the editor (the player's --dev; never in exports): // F9 shows / hides the physics shapes (DEV_KEY_PHYSICS_DEBUG), F10 the debug overlay // (DEV_KEY_DEBUG_OVERLAY) devKeys: bool, // Show the debug overlay (debug.odin): frame rate, frame times, the scene's counts debugOverlay: bool, // Recent frame times, for the overlay (`Game_GetFrameStats`) frameStats: FrameStats, // Translation tables and the language in use (localization.odin; in the game's arena) localization: Localization, // Set by `Game_Init` initialized: bool, // The color the window is cleared to each frame (black by default) backgroundColor: sdl.Color, // Resolution the game is designed for. All drawing and event positions are in this // coordinate space. Leave as zero to use the window size at init size: [2]f32, // How `size` is fit into the window. Change it with `Game_SetScaleMode` scaleMode: ScaleMode, // Current size of the window, kept up to date as it is resized windowSize: [2]f32, // Textures, sounds and fonts loaded by path (resource_manager.odin) resourceManager: ResourceManager, // Scenes are heap allocated so pointers to them stay valid as more scenes are added scenes: map[string]^Scene, // The scene `Game_SwitchScene` asked for; entered at the start of the next update nextScenePtr: ^Scene, // The scene being played. nil before the first switch currentScene: ^Scene, // The game's name (the project's name when loaded from one); names its save folder (save.odin) name: string, // Global scripts, in their start order (globals.odin) globals: [dynamic]GlobalInstance, // Data resources loaded so far, by path (data.odin) data: map[string]DataInstance, // Memory that lives as long as the game: globals, data resources (Game_GetAllocator) arena: MemoryArena, // Whether `arena` has been set up (on the first `Game_GetAllocator`) arenaReady: bool, // How its scenes' physics behave (physics.odin); zero means DEFAULT_PHYSICS. Set before // scenes create physics nodes physics: PhysicsSettings, // The keyboard, mouse, gamepads and the project's actions (input.odin) input: InputState, // Mix buses, Master first, and every sound node (audio_nodes.odin) buses: [dynamic]AudioBus, // Achievements, stats and cloud files (steam.odin) steam: SteamState, // Every sound player of every scene (audio_nodes.odin); the players belong to their scenes audioPlayers: [dynamic]^AudioPlayer, // Components read so far, by path (nil: couldn't be read); in the game's memory (project_loader.odin) components: map[string]^SceneData, // Animation sets read so far, by path (nil: couldn't be read); in the game's memory // (animation_sets.odin) animationSets: map[string]^AnimationSetData, // Atlases read so far, by path (nil: couldn't be read); in the game's memory (atlas.odin) atlases: map[string]^AtlasData, // Dialogues read so far, by path (nil: couldn't be read; dialogue_box.odin), and the // conversations' variables every DialogueBox shares dialogues: map[string]^DialogueData, dialogueVariables: DialogueVariables, // Scene changes with transitions, scenes over scenes, the loading screen (scene_flow.odin) flow: SceneFlow, // The live link with an editor (live.odin; nil unless the player runs with `--live`) live: ^LiveLink, // How long the parts of recent frames took (live.odin) profile: GameProfile, // UI themes read so far, by path (nil: couldn't be read); in the game's memory (ui_theme.odin) themes: map[string]^ThemeData, // Physics materials read so far, by path (nil: couldn't be read); in the game's memory // (physics_material.odin) physicsMaterials: map[string]^PhysicsMaterial, // The project's UI theme (project.xml's `uiTheme`; "": the default), used by controls with none uiTheme: string, // Aseprite files read for their slices and events, by full path (aseprite_nodes.odin; each // in its own arena) aseprites: map[string]AsepriteEntry, // LDtk projects read so far, by path (nil: couldn't be read); each in its own arena (ldtk_nodes.odin) ldtk: map[string]^LDtkProject, // Development runs: the LDtk files' modification times when read, by path ldtkTimes: map[string]i64, // Development runs: LDtk projects replaced by a reload (kept until the end: entity nodes // point into them) ldtkRetired: [dynamic]^LDtkProject, // Development runs: seconds until the LDtk files are looked at again ldtkWatchTimer: f64, // Seconds to the next look at the texture files (development runs) textureWatchTimer: f64, // The folder asset paths are relative to (owned; set by Game_LoadProject, "" for the working directory) projectDir: string, // Seconds left of a hitstop (Game_HitStop): scenes and globals get a delta time of 0 hitStop: f64, // Seconds the game has run (scaled like scenes' time; stopped by hitstops): shaders' `time` time: f64, // The shaders loaded, by path (shaders.odin; in the game's arena) shaders: map[string]^ShaderProgram, // GPU particles' pipelines (gpu_particles.odin) gpuParticles: GPUParticleResources, // A hot reload's snapshot whose nodes are still to restore (hot_reload.odin; the game's arena) pendingSnapshot: string, // Multiplies the time scenes and globals see: 0.5 is slow motion, 2 double speed // (`Game_SetTimeScale`). 0 counts as 1, so a game that never sets it runs normally; to stop // time, pause the scene (`Scene_SetPaused`) timeScale: f32, // Each scene's file, by scene name (Game_LoadProject; in the game's arena): a scene entered // again is rebuilt from it sceneFiles: map[string]string, // Called every frame by the application (`Game_Update`, set by `Game_Init`) update: proc(game: ^Game, events: []sdl.Event, deltaTime: f64), // Called every frame after `update` (`Game_Draw`, set by `Game_Init`) draw: proc(game: ^Game), // `Game_CreateScene`, set by `Game_Init`: `game->createScene("Level")` createScene: proc(game: ^Game, name: string) -> ^Scene, // `Game_SwitchScene`, set by `Game_Init`: `game->switchScene("Level")` switchScene: proc(game: ^Game, name: string) -> bool, // Set it before init: `Game_Init` calls it once to create the game's scenes and switch to // the first one build: proc(game: ^Game),}A game: its resolution and scaling, its scenes by name, and what lives as long as the game (resources, input, mix buses, globals, data resources, its memory arena).
Allocate it (new(e.Game)), set size, scaleMode and build, and hand it to
Application_Run (or call Game_Init). The update, draw, createScene and switchScene
procedures are set by Game_Init and called with ->. Game_Destroy frees what the game owns,
but not the Game itself.
struct. Source: game.odin:41
Scene :: struct { // Unique id id: EntityId, // Its name in the game (`Game_CreateScene`); "" for scenes made another way name: string, // The game it belongs to (set by `Game_CreateScene`; nil for scenes made another way) game: ^Game, // Every node in the scene, children included, in the order they were added (updates run in // this order) nodes: [dynamic]^Node, // Nodes added since the last update; they join the scene at the start of the next one addQueue: [dynamic]^Node, // Ids of nodes removed since the last update; they're freed at the start of the next one removeQueue: [dynamic]EntityId, // Nodes made with `CreateNode(..., addToScene = false)` and not added yet: the scene still // owns them, so destroying the scene frees them detached: [dynamic]^Node, // Every node the scene owns (in it, queued, detached) by id: Scene_GetNode and removals index: map[EntityId]^Node, // Its running tweens (tween.odin), updated after the nodes tweens: [dynamic]^Tween, // Debug shapes queued to draw over it (debug.odin) debugShapes: [dynamic]DebugShape, // The scene's memory (`Scene_GetAllocator`), freed by `Scene_Destroy` arena: MemoryArena, // Optional camera, nil by default. When set, every layer not in `screenLayers` is drawn // through it, and its pans and bounds are applied after each update. Create with `Scene_CreateCamera` camera: ^Camera2D, // Layers drawn in screen space, ignoring `camera`. Put HUDs and menus on these layers screenLayers: bit_set[RenderLayer], // The color the screen is cleared with while it plays (nil: the game's `backgroundColor`) background: Maybe(sdl.Color), // Its physics world's gravity, px/s² (nil: the game's `physics.gravity`). Set before the world // is made; afterwards use `Physics_SetGravity` gravity: Maybe([2]f32), // The physics world (physics.odin), nil until the scene's first physics node is created physics: ^PhysicsWorld, // Don't step the physics (bodies still exist and can be queried). Editors set it: their // previews update scenes for animations, and bodies mustn't fall there physicsPaused: bool, // Paused (`Scene_SetPaused`): physics doesn't step, and only nodes whose process mode allows it // update (`Node_CanProcess`); everything still draws paused: bool, // Not built yet: a project with a loading scene builds scenes when they're entered (scene_flow.odin) unbuilt: bool, // UI controls' focus and layout passes (ui_controls.odin) ui: SceneUI, // IK chains (skeleton.odin), solved after the nodes update, in the order they were made ikChains: [dynamic]^IKChain, // ScreenEffect nodes (screen_effects.odin), in the order they were added, and their pictures screenEffects: [dynamic]^ScreenEffect, effects: ScreenEffectTargets, // 2D lighting (lighting.odin): `Scene_SetLighting` lighting: SceneLighting, // Camera zones' bounds (camera_tools.odin) cameraZones: CameraZoneState, // It has been the current scene before: entering it again rebuilds it from its file // (Game.sceneFiles), so every visit starts fresh played: bool, // `Scene_AddNode`: `scene->addNode(node)` addNode: proc(scene: ^Scene, node: ^Node), // `Scene_RemoveNode`: `scene->removeNode(node.id)` removeNode: proc(scene: ^Scene, nodeId: EntityId), // `Scene_GetNode`: `scene->getNode(id)` getNode: proc(scene: ^Scene, nodeId: EntityId) -> ^Node, // `Scene_ProcessQueues`: `scene->processQueues()` processQueues: proc(scene: ^Scene), // `Scene_Update`, called by the game every frame while it's the current scene update: proc(scene: ^Scene, events: []sdl.Event, deltaTime: f64), // `Scene_Draw`, called by the game every frame while it's the current scene draw: proc(scene: ^Scene, renderer: ^sdl.Renderer), // Notified when the game switches to the scene (source: the scene) onEnter: EventSubject, // Notified when the game leaves the scene or is destroyed (source: the scene) onExit: EventSubject,}A scene: a tree of nodes, the queues that add and remove them between frames, and what the scene shares (camera, physics world, tweens, its memory arena).
Make scenes with Game_CreateScene (game->createScene(name)), which owns them, and fill them
with CreateNode. Each update applies the queues first, so nodes added or removed during a
frame join or leave at the start of the next one. The procedures are called with ->.
struct. Source: scene.odin:18
TransformMode
Section titled “TransformMode”TransformMode :: enum { // Position, rotation and scale (parents' and the camera's included) Full, // Only the position: drawn upright and unscaled at the node's screen position. Used by UI // nodes (Label, Menu), whose input handling works in unrotated screen space Position_Only,}How a node’s world transform applies when it draws.
enum. Source: node.odin:12
ProcessMode
Section titled “ProcessMode”ProcessMode :: enum u8 { // Its parent's mode (a top-level node's: Pausable) Inherit, // Updates while the scene runs, stops while it's paused (gameplay) Pausable, // Updates only while the scene is paused (a pause menu) WhenPaused, // Updates whether the scene is paused or not Always, // Never updates (it still draws) Disabled,}Whether a node updates (its update procedure, its script’s update), depending on whether its
scene is paused (Scene_SetPaused). Drawing isn’t affected.
enum. Source: node.odin:24
BlendMode
Section titled “BlendMode”BlendMode :: enum u8 { // Normal alpha blending Blend, // Adds its colors (glows, fire, light) Add, // Multiplies the colors under it (shadows, tinting) Multiply, // Multiplies the colors under it by its own; its alpha is ignored Modulate, // Replaces what's under it, alpha included None,}How a node’s drawing combines with what’s under it. Not inherited by children.
enum. Source: node.odin:40
NodeArena
Section titled “NodeArena”NodeArena :: runtime.ArenaA node’s memory: an arena of heap blocks (Odin’s runtime.Arena) holding the node itself and
whatever it allocates with Node_GetAllocator, freed in one step with the node. Heap blocks, not
virtual memory: making and freeing a node then costs no system calls (scenes, games and LDtk
projects, which are few and large, keep MemoryArena).
type. Source: node.odin:63
Node :: struct { // Unique id, set by `CreateNode`. Pass it to `Scene_RemoveNode` and `Scene_GetNode` id: EntityId, // The type `CreateNode` made it as (`Sprite`, `DynamicBody`, ...); see `Node_Is` type: typeid, // The name from the scene file ("" for nodes made in code unless set). Used by // `Scene_FindNode` and `Node_FindChild`; owned by the node's arena when set by `Scene_Build` name: string, // The root of a component instance: the component's file (`Scene_AddComponent`, // `Node_GetComponent`; in the node's arena). "" for any other node component: string, // The layer it draws on (Layer1 first, Layer8 on top). Not inherited by children layer: RenderLayer, // The scene that made it (`CreateNode`) scene: ^Scene, // Optional. Called every frame while the node is in its scene and may process // (`Node_CanProcess`); `deltaTime` is in seconds update: proc(node: ^Node, events: []sdl.Event, deltaTime: f64), // Optional. Draws the node in its local space: (0, 0) is its position, and the scene applies // its world transform, the camera and its tint to every Draw* call draw: proc(node: ^Node, renderer: ^sdl.Renderer), // Optional. Called just before the node's memory is freed, to release anything that is // not in the node's arena (e.g. memory from another allocator, SDL resources it owns) destroy: proc(node: ^Node), // Optional. The rectangle the node draws, in its local space. Editors use it for picking and // selection boxes; see `Node_GetLocalBounds` getBounds: proc(node: ^Node) -> (bounds: sdl.FRect, ok: bool),
// Transform relative to the parent (or the world, for top-level nodes) position: [2]f32, // Degrees, clockwise rotation: f32, // Scale on each axis ({1, 1} by default; a negative x mirrors it) scale: [2]f32, // UI nodes use .Position_Only: they follow their world position but aren't rotated or scaled transformMode: TransformMode, // Whether it updates while its scene is paused or running (`Node_CanProcess`) processMode: ProcessMode, // How its drawing combines with what's under it blendMode: BlendMode, // Its shader and its parameters (shaders.odin; nil none): `Node_SetShader` shader: ^ShaderMaterial, // Draw order within its layer: higher draws later (on top). Equal values keep tree order zIndex: i32, // Its children draw sorted by their world y (lower first: things further down the screen // draw on top), for top-down games ySort: bool, // Groups it belongs to (`Node_AddToGroup`, `Scene_GetGroup`); names in the node's arena groups: [dynamic]string, // A hidden node and its children aren't drawn. They still update; check // `Node_IsVisibleInTree` in update code that should pause while hidden visible: bool, // Multiplies the colors of everything the node and its children draw tint: sdl.Color, // Its parent, nil for a top-level node. Set with `Node_AddChild` / `Node_SetParent` parent: ^Node, // Its children, in the order they were added (in the node's arena) children: [dynamic]^Node, // Set while the node is part of its scene (after it was added, until it's removed) inScene: bool, // Set while it waits to be added (Scene_AddNode until the next Scene_ProcessQueues) queued: bool, // The script attached to the node, if any (script.odin), and the ones attached after it (each // a different script; in its arena). Their hooks run in that order script: NodeScript, moreScripts: [dynamic]NodeScript, // A custom node type's hooks and fields (custom_nodes.odin): its data is the node itself custom: NodeScript, // Scripts listening to its events, subscribed before the scene's scripts get `ready` // (`Node_AddListener`, node_events.odin; in its arena) listeners: [dynamic]NodeListenerData, // Its listeners were subscribed (`listeners` added later aren't) listenersBound: bool,
// Notified when the node joins its scene, at the start of the update after it was added // (source: the node) onEnter: EventSubject `event:"It joined the scene (the update after it was added)"`, // Notified when the node leaves its scene (removed, or the scene destroyed), before it's // freed (source: the node) onExit: EventSubject `event:"It's leaving the scene (removed, or the scene ended), before it's freed"`, // Holds this node and everything allocated with Node_GetAllocator arena: NodeArena,}Base type of everything in a scene. Node types embed it as their first field:
Player :: struct { using node: e.Node, ...}Create nodes with CreateNode. Each node owns a memory arena that holds the node itself
and anything allocated with Node_GetAllocator: its event listener lists, animation
players, strings, etc. When the node is destroyed (removed from its scene, or the scene
is destroyed) the arena is freed in one step, so none of that needs freeing by hand.
Every node has a transform (position, rotation, scale) relative to its parent, set with
Node_AddChild / Node_SetParent. A node’s draw procedure draws in its own local space: the
scene applies its world transform (and the camera) to every Draw* call. Children also inherit
visible (hidden nodes aren’t drawn, but still update) and tint. Removing a node removes its
children too.
struct. Source: node.odin:84
Transform2D
Section titled “Transform2D”Transform2D :: struct { // Where the local X axis points; its length is the X scale x: [2]f32, // Where the local Y axis points; its length is the Y scale y: [2]f32, // Where the local (0, 0) lands origin: [2]f32,}A 2D affine transform: maps a point p to x * p.x + y * p.y + origin.
x and y are where the local X and Y axes point (their lengths are the scale) and origin is
where the local (0, 0) lands. Composing transforms of nodes with rotation and non-uniform scale can
produce skew, which a matrix represents exactly.
Example
t := e.Transform2D_Make({100, 50}, 90, {2, 2})p := e.Transform2D_Apply(t, {1, 0}) // {100, 52}: one unit along local X, rotated 90° clockwise, doubledstruct. Source: transform.odin:18
EventData
Section titled “EventData”EventData :: union { int, u32, u64, f32, f64, bool, rawptr, string,}The value an event carries to its listeners (Event_Notify). nil when there’s none. A string
is only valid during the notification: clone it to keep it
union. Source: event.odin:9
EntityId
Section titled “EntityId”EntityId :: uuid.IdentifierA unique id (a version 4 UUID) for a node or an event listener. Made by NewEntityId
type. Source: event.odin:21
ListenerHandle
Section titled “ListenerHandle”ListenerHandle :: EntityIdIdentifies a subscription, returned by Event_Subscribe; pass it to Event_Unsubscribe
type. Source: event.odin:39
EventListener
Section titled “EventListener”EventListener :: struct { // The handle `Event_Subscribe` returned id: ListenerHandle, // Called on each notification with the subject, the notifier's source and data, and // `userData`. nil while a removal waits for the notification to end callback : proc(event: ^EventSubject, source: rawptr, data: EventData, userData: rawptr), // Passed back to `callback` as it was given to `Event_Subscribe` userData : rawptr,}One subscription to an EventSubject
struct. Source: event.odin:42
EventSubject
Section titled “EventSubject”EventSubject :: struct { // The subscriptions, in the order they subscribed listeners : [dynamic]EventListener, // Notifications running (nested ones count): while one runs, unsubscribing only marks the // listener, and the list is compacted when the last one ends notifying: int, // A listener was marked for removal while notifying compact: bool, // `Event_Subscribe`, set by `EventSubject_Init` subscribe: proc(subject: ^EventSubject, callback: proc(event: ^EventSubject, source: rawptr, data: EventData, userData: rawptr), userData: rawptr) -> ListenerHandle , // `Event_Unsubscribe`, set by `EventSubject_Init` unsubscribe: proc(subject: ^EventSubject, handle: ListenerHandle), // `Event_Notify`, set by `EventSubject_Init` notify: proc(subject: ^EventSubject, source: rawptr, data: EventData),}An event that listeners subscribe to (the observer pattern). Scenes and nodes use them for
hooks such as onEnter and onExit.
Set it up with EventSubject_Init; the procedures are called with ->:
subject->subscribe(callback, userData), subject->notify(source, data),
subject->unsubscribe(handle). Free it with Event_Destroy.
struct. Source: event.odin:60
NodeEventInfo
Section titled “NodeEventInfo”NodeEventInfo :: struct { // Its name in scene files: the field's ("onFinished"), through a struct field // ("animation.onAnimationEnded") name: string, // What it means (the `event` tag) description: string, // Code reading its data (the `data` tag: "index := data.(int)"); "" when it carries none dataHint: string, // The subject's byte offset in the node offset: uintptr,}An event a node type offers: one of its EventSubject fields tagged event:"..."
struct. Source: node_events.odin:35
ScriptListenerProc
Section titled “ScriptListenerProc”ScriptListenerProc :: proc(self: rawptr, source: ^Node, data: EventData)A script’s handler for an event: the script struct, the node whose event it is, its data
type. Source: node_events.odin:180
Timer :: struct { // Time run since it (re)started elapsed: time.Duration, // Whether `update` advances it running: bool, // How long until it expires duration: time.Duration, // Start over when it expires (otherwise it stops) loop: bool, // Every update while running; the data is the elapsed seconds (f32) onTick: EventSubject, // When it expires; the data is the overshoot in seconds (f32) onExpire: EventSubject, // `TimerNode_Start`: `timer->start()` start: proc(timer: ^Timer), // `TimerNode_SetDuration`: `timer->setDuration(seconds)` setDuration: proc(timer: ^Timer, durationSec: f32), // Advances a running timer by `deltaTime` seconds: `timer->update(dt)`. Call it every frame update: proc(timer: ^Timer, deltaTime: f32),}A countdown driven by the delta time it’s given (timer->update(timer, deltaTime), usually from a
node’s update): it sends onTick on every update while running, and onExpire when duration
has passed. A looping timer starts over (keeping any overshoot, so it doesn’t drift); otherwise
it stops. Being driven by delta time, it pauses with a hitstop and anything else that stops time.
Example
package mygame
import e "engine:."import "core:time"import sdl "vendor:sdl3"
Spawner :: struct { using node: e.Node, timer: e.Timer,}
CreateSpawner :: proc(scene: ^e.Scene) -> ^Spawner { spawner := e.CreateNode(scene, Spawner) e.TimerNode_Init(&spawner.timer, 2 * time.Second, auto_start = true, loop = true, allocator = e.Node_GetAllocator(spawner)) spawner.timer.onExpire->subscribe(proc(_: ^e.EventSubject, _: rawptr, _: e.EventData, user: rawptr) { spawner := cast(^Spawner)user _ = spawner // every two seconds: spawn something }, spawner) spawner.update = proc(node: ^e.Node, _: []sdl.Event, deltaTime: f64) { spawner := cast(^Spawner)node spawner.timer->update(f32(deltaTime)) } return spawner}struct. Source: timer.odin:37
TickerCallBack
Section titled “TickerCallBack”TickerCallBack :: proc(ticker: ^Ticker, currentTick: int, user_data: rawptr)A tick callback: the ticker, its tick count and user data. (Not used by Ticker, which notifies onTick)
type. Source: ticker.odin:6
Ticker
Section titled “Ticker”Ticker :: struct { // Ticks per second (0 to 120). Change it with `setFPS` framesPerSecond: f32, // Time between ticks (0: never ticks) secondsPerFrame: time.Duration, // Time since the last tick elapsedTime: time.Duration, // When `process` last measured the time lastTick: time.Tick, // Ticks so far currentTick: int, // Free for the game's use; the ticker doesn't read it user_data: rawptr, // Whether `process` advances it running: bool, // `Ticker_SetFPS` setFPS: proc(ticker: ^Ticker, framesPerSecond: f32), // `Ticker_Start` start: proc(ticker: ^Ticker, resetCounter: bool), // `Ticker_Stop` stop: proc(ticker: ^Ticker), // `Ticker_Pause` pause: proc(ticker: ^Ticker), // `Ticker_Resume` resume: proc(ticker: ^Ticker), // `Ticker_TogglePause` togglePause: proc(ticker: ^Ticker), // `Ticker_Reset` reset: proc(ticker: ^Ticker), // Measures the time and ticks if a tick is due. Call it every frame process: proc(ticker: ^Ticker), // Notified on each tick (source: the ticker; data: the tick count, f32) onTick: EventSubject,}Sends onTick at a fixed rate, measured in real (wall clock) time: unlike Timer, it doesn’t
follow the delta time, so the time scale, hitstops and paused scenes don’t slow it.
Set it up with Ticker_Init, then call ticker->process() every frame; it ticks at most once
per call. The data sent with onTick is the tick count (f32). The procedures are called with
->.
Example
// Given: ticker: ^e.Tickere.Ticker_Init(ticker, allocator = e.Node_GetAllocator(node))ticker->setFPS(4)ticker.onTick->subscribe(proc(_: ^e.EventSubject, _: rawptr, data: e.EventData, _: rawptr) { log.infof("tick %v", data)}, nil)// Every frame:ticker->process()struct. Source: ticker.odin:26
RenderLayer
Section titled “RenderLayer”RenderLayer :: enum { Layer1, Layer2, Layer3, Layer4, Layer5, Layer6, Layer7, Layer8}The layer a node draws on (Node.layer). A scene draws Layer1 first and Layer8 last (on top);
within a layer, nodes draw by zIndex, and in tree order among equal ones. Layers in Scene.screenLayers
ignore the camera.
enum. Source: layers.odin:8
CollisionLayers
Section titled “CollisionLayers”CollisionLayers :: bit_set[RenderLayer]A set of layers. (Not used by the engine: physics bodies use u64 collision layers and masks)
bit_set. Source: layers.odin:20
NodeTypeInfo
Section titled “NodeTypeInfo”NodeTypeInfo :: struct { // Its name, hooks and fields, as a script's (data: the node itself) using info: ScriptInfo, // The built-in kind it extends base: SceneNodeKind,}A registered custom node type
struct. Source: custom_nodes.odin:44
Procedures
Section titled “Procedures”Application_Setup
Section titled “Application_Setup”Application_Setup :: proc(app: ^Application, windowTitle: string, windowSize: [2]f32, backend := RenderBackend.Auto, window := DEFAULT_WINDOW_SETTINGS, audio := DEFAULT_AUDIO_OUTPUT) -> boolStarts SDL, opens the window with a renderer, the audio device and SDL_ttf.
The window is resizable; the game’s resolution is scaled into it (see Game_SetScaleMode).
Call it once, before creating a game; free everything with Application_Cleanup.
Inputs
- app: The application to fill in
- windowTitle: The window’s title
- windowSize: The window’s size in pixels (ignored when
window.fullscreenis set) - backend: The renderer to use (
RenderBackend)..Autolets SDL choose - window: Fullscreen, vsync and high-DPI settings (vsync is applied later on the web)
- audio: The sound output’s format and device (
Audio_OpenMixerfalls back when it won’t open)
Returns
- false (logged) if SDL, the window, the renderer, the audio device or SDL_ttf couldn’t start
Example
app := e.Application{}if !e.Application_Setup(&app, "My Game", {1280, 720}) do returndefer e.Application_Cleanup(&app)e.Application_Run(&app, game)procedure. Source: app.odin:57
Application_Run
Section titled “Application_Run”Application_Run :: proc(app: ^Application, game: ^Game = nil)Runs the game until it quits: Application_Frame in a loop.
The browser can’t block like this; there, the page calls Application_Frame once per animation
frame (see Player_StartWeb).
Inputs
- app: The application, set up with
Application_Setup - game: The game to play (queued with
App_SetGame; initialized on the first frame if needed). nil keeps the game already set
Example
app := e.Application{}if !e.Application_Setup(&app, "My Game", {1280, 720}) do returndefer e.Application_Cleanup(&app)e.Application_Run(&app, game)procedure. Source: app.odin:113
Application_Frame
Section titled “Application_Frame”Application_Frame :: proc(app: ^Application)Runs one frame: switches to a queued game, polls events, updates, draws and presents, and frees
the frame’s temporary memory (context.temp_allocator).
Switching destroys the previous game (Game_Destroy) and initializes the new one if it isn’t
yet. Event positions are converted to the game’s resolution. Sets app.running to false when
the game sets shouldQuit.
Inputs
- app: The application, set up with
Application_Setup
procedure. Source: app.odin:138
Application_Cleanup
Section titled “Application_Cleanup”Application_Cleanup :: proc(app: ^Application)Destroys the current game, then closes audio, SDL_ttf, the renderer, the window and SDL.
A game that is only queued (queuedGame) is not destroyed: the caller owns it.
Inputs
- app: The application to shut down
procedure. Source: app.odin:223
App_SetGame
Section titled “App_SetGame”App_SetGame :: proc(app: ^Application, game: ^Game)Queues a game to play. The next Application_Frame destroys the current game (if it’s a
different one) and switches to this one, initializing it if needed.
Inputs
- app: The application
- game: The game to switch to. Setting the current game again only re-applies its scaling
procedure. Source: app.odin:248
Game_Init
Section titled “Game_Init”Game_Init :: proc(game: ^Game, window: ^sdl.Window, renderer: ^sdl.Renderer, mixer: ^mix.Mixer)Starts a game in the application’s window: sets up its resources and procedures, fits its
resolution into the window, then calls game.build.
Application_Frame calls it for a game set with App_SetGame (or Application_Run) that
isn’t initialized yet, so most games don’t call it themselves. Set size (zero: the window’s
size), scaleMode and build before.
Inputs
- game: The game to start
- window: The application’s window
- renderer: The application’s renderer
- mixer: The application’s audio mixer
Example
game := new(e.Game)game.size = {640, 360}game.scaleMode = .IntegerScalegame.build = proc(game: ^e.Game) { game->createScene("Title") _ = game->switchScene("Title")}e.Game_Init(game, app.window, app.renderer, app.mixer)procedure. Source: game.odin:198
Game_GetAllocator
Section titled “Game_GetAllocator”Game_GetAllocator :: proc(game: ^Game) -> runtime.AllocatorMemory that lives as long as the game (globals and data resources use it). Freed by
Game_Destroy.
Inputs
- game: The game
Returns
- The allocator of the game’s arena (set up on the first call)
procedure. Source: game.odin:236
Game_Destroy
Section titled “Game_Destroy”Game_Destroy :: proc(game: ^Game)Frees what the game owns: the current scene exits, every scene and its nodes are destroyed, then the globals, input, LDtk projects, mix buses, resources and the game’s arena.
Application_Cleanup and switching games (App_SetGame) call it. The Game itself isn’t freed.
Inputs
- game: The game to destroy
procedure. Source: game.odin:252
Game_CreateScene
Section titled “Game_CreateScene”Game_CreateScene :: proc(game: ^Game, name: string) -> ^SceneCreates an empty scene and adds it to the game under a name. Call it as game->createScene(name).
The game owns the scene (freed by Game_Destroy) and keeps its own copy of the name.
Inputs
- game: The game
- name: The scene’s name, unique in the game
Returns
- The new scene, or nil (logged) if the game already has a scene with that name
Example
level := game->createScene("Level")level.screenLayers = {.Layer8}_ = game->switchScene("Level")procedure. Source: game.odin:315
Game_SwitchScene
Section titled “Game_SwitchScene”Game_SwitchScene :: proc(game: ^Game, name: string) -> boolSwitches to a scene at the start of the next update. Call it as game->switchScene(name).
The scene being left gets its onExit and its sounds stop; the new one gets onEnter. A scene
that has been played before is rebuilt from its file (switching to the current scene restarts
it); a scene made by code is entered as it is.
Inputs
- game: The game
- name: The scene’s name
Returns
- false (logged) if the game has no scene with that name
Example
if !game->switchScene("GameOver") do log.error("no GameOver scene")procedure. Source: game.odin:348
Game_Update
Section titled “Game_Update”Game_Update :: proc(game: ^Game, events: []sdl.Event, deltaTime: f64)Updates the game for one frame. The application calls it (as game->update).
In order: input, audio, window and quit events (and the developer keys), a pending scene switch, the time scale and hitstop, the globals, then the current scene.
Inputs
- game: The game
- events: This frame’s SDL events, positions in game coordinates
- deltaTime: Real seconds since the last frame
procedure. Source: game.odin:370
Game_SetWindowIcon
Section titled “Game_SetWindowIcon”Game_SetWindowIcon :: proc(game: ^Game, path: string) -> boolChanges the game window’s icon (the taskbar’s, the window list’s): an image in the project
(PNG, JPEG…), read from the data pack when the game is packed. Projects set one with icon
(Project Settings > General > Icon); games without one show the Crossfire logo. Browsers show the
page’s favicon instead: this does nothing there.
Inputs
- game: The game (its window)
- path: The image, relative to the project folder
Returns
- false when there’s no window, the image can’t be read (logged), or the window system doesn’t
take icons (Wayland compositors without the xdg-toplevel-icon protocol, which show the
.desktopentry’s icon instead)
Example
e.Game_SetWindowIcon(game, "assets/icon.png")procedure. Source: game.odin:499
Game_SetFullscreen
Section titled “Game_SetFullscreen”Game_SetFullscreen :: proc(game: ^Game, fullscreen: bool) -> boolSwitches the game’s window between fullscreen (the desktop’s resolution, the game scaled into it) and windowed.
Inputs
- game: The game
- fullscreen: true for fullscreen, false for windowed
Returns
- false if the window couldn’t change (logged), or there’s no window
procedure. Source: game.odin:520
Game_IsFullscreen
Section titled “Game_IsFullscreen”Game_IsFullscreen :: proc(game: ^Game) -> boolWhether the game’s window is fullscreen.
Inputs
- game: The game
Returns
- true if it’s fullscreen; false if windowed or there’s no window
procedure. Source: game.odin:538
Game_SetVSync
Section titled “Game_SetVSync”Game_SetVSync :: proc(game: ^Game, vsync: bool)Turns vsync on (frames wait for the display’s refresh: no tearing) or off (as fast as it can). Does nothing in the browser, where frames always follow the display, or without a renderer.
Inputs
- game: The game
- vsync: true to wait for the display’s refresh
procedure. Source: game.odin:550
Game_SetTimeScale
Section titled “Game_SetTimeScale”Game_SetTimeScale :: proc(game: ^Game, scale: f32)Sets how fast time runs for scenes and globals (physics, animations, particles, timers, tweens, scripts’ delta time): 1 is normal, 0.5 slow motion, 2 double speed. Hitstops and camera shakes run in real time.
Inputs
- game: The game
- scale: The speed (above 0; 0 or less becomes 0.0001. To stop time, pause the scene)
Example
e.Game_SetTimeScale(game, 0.5) // slow motionprocedure. Source: game.odin:566
Game_HitStop
Section titled “Game_HitStop”Game_HitStop :: proc(game: ^Game, seconds: f64)Freezes the game for a moment: a hitstop, the short pause that makes a hit feel heavy. For
seconds of real time, scenes and globals update with a delta time of 0: physics, animations,
particles, timers and anything scripts move by the delta stop, while input, drawing and the
camera’s shake (Camera2D_Shake) go on. A longer hitstop extends a running one; a shorter one
doesn’t cut it.
Inputs
- game: The game
- seconds: How long, in real seconds (around 0.05 to 0.12 for a hit)
Example
e.Game_HitStop(game, 0.08)e.Camera2D_Shake(scene.camera, 3, 0.15)procedure. Source: game.odin:585
Game_Draw
Section titled “Game_Draw”Game_Draw :: proc(game: ^Game)Draws the game for one frame. The application calls it (as game->draw) after the update.
Draws the current scene, then the globals that draw (in screen space), then the debug overlay when it’s on.
Inputs
- game: The game
procedure. Source: game.odin:598
Game_ApplyScaling
Section titled “Game_ApplyScaling”Game_ApplyScaling :: proc(game: ^Game)Tells the renderer to draw at game.size and scale the result into the window with
game.scaleMode. The renderer recalculates the scale on its own whenever the window is resized.
Game_SetSize and Game_SetScaleMode call it; call it after changing size or scaleMode
directly.
Inputs
- game: The game
procedure. Source: game.odin:624
Game_SetSize
Section titled “Game_SetSize”Game_SetSize :: proc(game: ^Game, size: [2]f32)Sets the game’s resolution and applies it (Game_ApplyScaling). Drawing and event positions
are in this coordinate space.
Inputs
- game: The game
- size: The resolution, in pixels
procedure. Source: game.odin:646
Game_SetScaleMode
Section titled “Game_SetScaleMode”Game_SetScaleMode :: proc(game: ^Game, mode: ScaleMode)Sets how the game’s resolution is fit into the window and applies it (Game_ApplyScaling).
Inputs
- game: The game
- mode: The scale mode
Example
e.Game_SetScaleMode(game, .IntegerScale)procedure. Source: game.odin:661
Game_WindowToGame
Section titled “Game_WindowToGame”Game_WindowToGame :: proc(game: ^Game, point: [2]f32) -> [2]f32Converts a point in window coordinates to game coordinates (through the scaling).
Events already arrive in game coordinates; this is for positions read from SDL directly.
Inputs
- game: The game
- point: The point, in window pixels
Returns
- The point in game coordinates
procedure. Source: game.odin:678
Game_GetMousePosition
Section titled “Game_GetMousePosition”Game_GetMousePosition :: proc(game: ^Game) -> [2]f32The mouse position in game coordinates, for nodes that poll instead of using events.
Inputs
- game: The game
Returns
- The mouse position in game coordinates (screen space: the camera isn’t applied)
procedure. Source: game.odin:693
Scene_Init
Section titled “Scene_Init”Scene_Init :: proc(scene: ^Scene)Prepares an empty scene: its arena, procedures, node index and onEnter / onExit.
Game_CreateScene calls it; call it yourself only for a scene outside a game (editors, tests).
Panics if the arena can’t be set up.
Inputs
- scene: The scene (zeroed)
procedure. Source: scene.odin:103
Scene_GetAllocator
Section titled “Scene_GetAllocator”Scene_GetAllocator :: proc(scene: ^Scene) -> mem.AllocatorAllocator backed by the scene’s arena. Anything allocated with it is freed all at once when
the scene is destroyed, so it suits data shared by the whole scene. For data that belongs
to one node, prefer Node_GetAllocator so it is freed when that node is removed.
Inputs
- scene: The scene
Returns
- The scene arena allocator
procedure. Source: scene.odin:130
Scene_Clear
Section titled “Scene_Clear”Scene_Clear :: proc(scene: ^Scene)Empties a scene: every node is destroyed and its physics world goes, as Scene_Destroy does, but
the scene itself stays (its name, game, screen layers, physicsPaused), ready to be built again.
Listeners on its onEnter / onExit are dropped with it. Procedures the game replaced
(update, draw…) stay replaced.
Inputs
- scene: The scene
procedure. Source: scene.odin:143
Scene_Destroy
Section titled “Scene_Destroy”Scene_Destroy :: proc(scene: ^Scene)Destroys every node of a scene and frees what the scene owns (its lists, physics world and arena);
the Scene struct itself is the caller’s. Nodes in the scene get onExit first, as removed nodes
do; then every node it made (in it, waiting to be added, or never added) is destroyed, children
before their parents, so a destroy hook can still reach its parent.
Inputs
- scene: The scene
procedure. Source: scene.odin:164
CreateNode
Section titled “CreateNode”CreateNode :: proc(scene: ^Scene, $T: typeid, addToScene: bool = true) -> ^TCreates a node of type T in its own memory arena and (by default) adds it to the scene.
T must be Node or a struct that embeds it as its first field (using node: e.Node).
The node is freed, along with everything allocated with Node_GetAllocator(node), when
it is removed from the scene or the scene is destroyed.
Inputs
- scene: The scene the node belongs to
- T: The node type
- addToScene: Add it to the scene (at the start of the next update). Pass false to add it later with
scene->addNode
Returns
- The new node, zero-initialized apart from its id, scene and event subjects
Example
package mygame
import e "engine:."import sdl "vendor:sdl3"
Bullet :: struct { using node: e.Node, velocity: [2]f32,}
Fire :: proc(scene: ^e.Scene, from: [2]f32) { bullet := e.CreateNode(scene, Bullet) bullet.position = from bullet.velocity = {0, -600} bullet.update = Bullet_update}
Bullet_update :: proc(node: ^e.Node, events: []sdl.Event, deltaTime: f64) { bullet := cast(^Bullet)node bullet.position += bullet.velocity * f32(deltaTime) if bullet.position.y < 0 { node.scene->removeNode(node.id) // Freed at the start of the next update }}procedure. Source: scene.odin:243
Scene_AddNode
Section titled “Scene_AddNode”Scene_AddNode :: proc(scene: ^Scene, node: ^Node)Adds a node to its scene at the start of the next update (it gets onEnter then). For nodes made
with CreateNode(..., addToScene = false); adding a node twice, or one of another scene, does
nothing.
Inputs
- scene: The node’s scene
- node: The node
procedure. Source: scene.odin:273
Scene_RemoveNode
Section titled “Scene_RemoveNode”Scene_RemoveNode :: proc(scene: ^Scene, nodeId: EntityId)Removes a node, with its children, at the start of the next update: they get onExit, then their
destroy hooks, then their memory is freed. Pointers to them are invalid after that. Removing
a node twice, or one already gone, does nothing.
Inputs
- scene: The node’s scene
- nodeId: The node’s
id
procedure. Source: scene.odin:293
Scene_GetNode
Section titled “Scene_GetNode”Scene_GetNode :: proc(scene: ^Scene, nodeId: EntityId) -> ^NodeA node of the scene by its id: in the scene, waiting to be added, or made without being added.
Also scene->getNode(id).
Inputs
- scene: The scene
- nodeId: The node’s
id
Returns
- The node, or nil (never made here, or already removed)
procedure. Source: scene.odin:308
Scene_ProcessQueues
Section titled “Scene_ProcessQueues”Scene_ProcessQueues :: proc(scene: ^Scene)Applies queued removals, then queued additions (onEnter for each, in the order they were
added). Scene_Update does it first; editors and tests call it to apply changes without
updating.
Inputs
- scene: The scene
procedure. Source: scene.odin:320
Scene_Update
Section titled “Scene_Update”Scene_Update :: proc(scene: ^Scene, events: []sdl.Event, deltaTime: f64)Updates the scene for one frame. The game calls it (as scene->update) for its current scene.
In order: expired debug shapes go, the add and remove queues are applied, new physics nodes get
their bodies, scripts get ready, physics steps (unless paused), every node that may process
updates (its update, then its script’s), tweens advance, then the camera’s pan and bounds are
applied.
Inputs
- scene: The scene
- events: This frame’s SDL events
- deltaTime: Seconds since the last frame (after the game’s time scale and hitstop)
procedure. Source: scene.odin:405
Scene_SetPaused
Section titled “Scene_SetPaused”Scene_SetPaused :: proc(scene: ^Scene, paused: bool)Pauses or resumes a scene: while paused, physics doesn’t step and only nodes that may process then update (process mode WhenPaused or Always, or inheriting one); drawing goes on.
Inputs
- scene: The scene
- paused: true to pause, false to resume
Example
e.Scene_SetPaused(scene, !scene.paused) // the pause keyprocedure. Source: scene.odin:461
Scene_GetGroup
Section titled “Scene_GetGroup”Scene_GetGroup :: proc(scene: ^Scene, group: string) -> []^NodeThe scene’s nodes in a group, in the order they were added (nodes waiting to be added included).
Inputs
- scene: The scene
- group: The group’s name
Returns
- The nodes (temp allocated; empty if none)
Example
for enemy in e.Scene_GetGroup(scene, "enemies") do enemy.visible = falseprocedure. Source: scene.odin:479
Scene_Draw
Section titled “Scene_Draw”Scene_Draw :: proc(scene: ^Scene, renderer: ^sdl.Renderer)Draws the scene: layer by layer, and within a layer in tree order (a parent before its children,
siblings in the order they were added). Each node draws in its local space; the scene sets the
draw transform to the camera view combined with the node’s world transform (only the position,
for .Position_Only nodes), and the draw tint to its inherited tint. Hidden nodes and their
children are skipped. The game calls it (as scene->draw) for its current scene.
Inputs
- scene: The scene
- renderer: The renderer to draw with
procedure. Source: scene.odin:498
Scene_DrawWithView
Section titled “Scene_DrawWithView”Scene_DrawWithView :: proc(scene: ^Scene, renderer: ^sdl.Renderer, view: Transform2D, screenLayersUseView := false, positionOnlyScale: f32 = 1)Draws the scene through a given view transform (world to screen) instead of its camera. Editors use it to show a scene with their own pan and zoom.
Inputs
- scene: The scene
- renderer: The renderer to draw with
- view: World-to-screen transform for world layers
- screenLayersUseView: Also draw
scene.screenLayersthroughview(an editor shows HUD nodes where they sit relative to the game area); otherwise they’re drawn untransformed - positionOnlyScale: Size multiplier for
.Position_Onlynodes (labels, menus). The game uses 1; an editor passes its zoom so UI keeps its size relative to the game area
procedure. Source: scene.odin:516
Node_IsVisibleInTree
Section titled “Node_IsVisibleInTree”Node_IsVisibleInTree :: proc(node: ^Node) -> boolWhether a node and all of its ancestors are visible.
Inputs
- node: The node
Returns
- false if the node or any ancestor has
visibleoff
procedure. Source: scene.odin:593
Scene_FindNode
Section titled “Scene_FindNode”Scene_FindNode :: proc(scene: ^Scene, path: string) -> ^NodeFinds a node by its name, or by a path of names from a top-level node: “World/Ship/Flame”.
Inputs
- scene: The scene
- path: A name, or names separated by “/”
Returns
- The first match (nodes waiting to be added included), or nil. A bare name matches any node, in the order they were added; a path starts at the first top-level node with the first name
Example
if flame := e.Scene_FindNode(scene, "Ship/Flame"); flame != nil do flame.visible = falseprocedure. Source: scene.odin:703
Node_FindPath
Section titled “Node_FindPath”Node_FindPath :: proc(from: ^Node, path: string) -> ^NodeFinds a node by a path from another, like a file path: child names separated by “/” (“Gun”, “Arm/Hand”), “..” for the parent and “.” for the node itself (“../Camera”: a sibling). From a top-level node “..” leads to the scene’s top level. A path starting with “/” starts at the scene’s top level instead (“/World/Ship”). Script node fields use it.
Inputs
- from: The node the path starts at
- path: The path (“” gives nil)
Returns
- The node, or nil when no node is there
Example
// Given: self: ^e.Scriptcamera := e.Node_FindPath(self.node, "../Camera")procedure. Source: scene.odin:733
Node_FindChild
Section titled “Node_FindChild”Node_FindChild :: proc(node: ^Node, path: string) -> ^NodeFinds a descendant of a node by a path of child names: “Ship/Flame”. At each level the first child with the name is followed.
Inputs
- node: The node to start from
- path: Child names separated by “/”. “” returns
node
Returns
- The node, or nil
procedure. Source: scene.odin:788
Node_CanProcess
Section titled “Node_CanProcess”Node_CanProcess :: proc(node: ^Node) -> boolWhether a node updates now: its process mode (resolving Inherit through its parents; Pausable at the top) against whether its scene is paused. The scene checks it before each update.
Inputs
- node: The node
Returns
- true if the node updates this frame
Example
// Given: pauseMenu: ^e.NodepauseMenu.processMode = .WhenPausede.Scene_SetPaused(scene, true) // gameplay stops, the menu runsprocedure. Source: node.odin:203
Node_AddToGroup
Section titled “Node_AddToGroup”Node_AddToGroup :: proc(node: ^Node, group: string)Puts a node in a group (once): groups name sets of nodes to find or act on together, like
“enemies” or “pickups”. Find them with Scene_GetGroup.
Inputs
- node: The node
- group: The group’s name (copied into the node’s arena)
Example
e.Node_AddToGroup(node, "enemies")for enemy in e.Scene_GetGroup(scene, "enemies") do enemy.tint = {255, 80, 80, 255}procedure. Source: node.odin:233
Node_RemoveFromGroup
Section titled “Node_RemoveFromGroup”Node_RemoveFromGroup :: proc(node: ^Node, group: string)Takes a node out of a group (nothing if it isn’t in it).
Inputs
- node: The node
- group: The group’s name
procedure. Source: node.odin:245
Node_IsInGroup
Section titled “Node_IsInGroup”Node_IsInGroup :: proc(node: ^Node, group: string) -> boolWhether a node is in a group.
Inputs
- node: The node
- group: The group’s name
Returns
- true if it’s in the group
procedure. Source: node.odin:262
Node_GetAllocator
Section titled “Node_GetAllocator”Node_GetAllocator :: proc(node: ^Node) -> mem.AllocatorAllocator backed by the node’s arena. Everything allocated with it is freed when the node is destroyed, so use it for data the node owns: event subjects, animation players, animations, copies of strings.
Inputs
- node: A node made by
CreateNode
Returns
- The node’s arena allocator
Example
package mygame
import e "engine:."
Enemy :: struct { using node: e.Node, anim: e.AnimationPlayer, onHit: e.EventSubject,}
CreateEnemy :: proc(scene: ^e.Scene) -> ^Enemy { enemy := e.CreateNode(scene, Enemy) allocator := e.Node_GetAllocator(enemy) e.AnimationPlayer_Init(&enemy.anim, allocator) e.EventSubject_Init(&enemy.onHit, allocator) return enemy}procedure. Source: node.odin:297
Node_Destroy
Section titled “Node_Destroy”Node_Destroy :: proc(node: ^Node)Frees a node and everything in its arena, after calling its destroy callback.
The scene does this for you: for removed nodes at the start of the next update (after
onExit), and for all of its nodes when the scene is destroyed. Any pointer to the node
is invalid afterwards.
Inputs
- node: A node made by
CreateNode
procedure. Source: node.odin:311
Node_Is
Section titled “Node_Is”Node_Is :: proc(node: ^Node, $T: typeid) -> boolWhether a node was created as type T (CreateNode(scene, T)), so it can be cast to ^T.
Inputs
- node: The node (nil returns false)
- T: The node type
Returns
- true if the node’s type is exactly T
Example
if e.Node_Is(node, e.DynamicBody) do e.DynamicBody_ApplyImpulse(cast(^e.DynamicBody)node, {0, -300})procedure. Source: node.odin:369
Node_GetLocalBounds
Section titled “Node_GetLocalBounds”Node_GetLocalBounds :: proc(node: ^Node) -> (sdl.FRect, bool)The rectangle a node draws, in its local space (before its transform).
Inputs
- node: The node
Returns
- The bounds, and false for nodes that draw nothing measurable (no
getBounds)
procedure. Source: node.odin:382
Transform2D_Make
Section titled “Transform2D_Make”Transform2D_Make :: proc(position: [2]f32, rotation: f32, scale: [2]f32) -> Transform2DBuilds a transform from a position, rotation and scale. Scale is applied first, then rotation, then the position.
Inputs
- position: Where the local origin goes
- rotation: Degrees, clockwise (screen Y points down)
- scale: Size multiplier per axis
Returns
- The transform
procedure. Source: transform.odin:42
Transform2D_Multiply
Section titled “Transform2D_Multiply”Transform2D_Multiply :: proc(a, b: Transform2D) -> Transform2DCombines two transforms: the result applies b first, then a. A child’s world transform is
Transform2D_Multiply(parentWorld, childLocal).
Inputs
- a: The transform applied second (the parent’s)
- b: The transform applied first (the child’s)
Returns
- The combined transform
Example
world := e.Transform2D_Multiply(parentWorld, e.Node_GetLocalTransform(node))_ = worldprocedure. Source: transform.odin:70
Transform2D_Apply
Section titled “Transform2D_Apply”Transform2D_Apply :: proc(t: Transform2D, p: [2]f32) -> [2]f32Transforms a point.
Inputs
- t: The transform
- p: The point
Returns
t.x * p.x + t.y * p.y + t.origin
procedure. Source: transform.odin:88
Transform2D_ApplyVector
Section titled “Transform2D_ApplyVector”Transform2D_ApplyVector :: proc(t: Transform2D, v: [2]f32) -> [2]f32Transforms a direction or size: like Transform2D_Apply, without the translation.
Inputs
- t: The transform
- v: The direction or size
Returns
t.x * v.x + t.y * v.y
procedure. Source: transform.odin:102
Transform2D_Inverse
Section titled “Transform2D_Inverse”Transform2D_Inverse :: proc(t: Transform2D) -> Transform2DThe transform that undoes t. A transform that collapses space (zero scale) has no inverse;
identity is returned for it.
Inputs
- t: The transform
Returns
- The inverse transform, or
TRANSFORM2D_IDENTITYwhen there is none
procedure. Source: transform.odin:116
Transform2D_Rotation
Section titled “Transform2D_Rotation”Transform2D_Rotation :: proc(t: Transform2D) -> f32Rotation of the transform’s X axis.
Inputs
- t: The transform
Returns
- Degrees, clockwise, from -180 to 180
procedure. Source: transform.odin:137
Transform2D_Scale
Section titled “Transform2D_Scale”Transform2D_Scale :: proc(t: Transform2D) -> [2]f32Length of each axis: the scale, ignoring any mirroring.
Inputs
- t: The transform
Returns
- The X and Y scale, both positive
procedure. Source: transform.odin:150
Transform2D_UniformScale
Section titled “Transform2D_UniformScale”Transform2D_UniformScale :: proc(t: Transform2D) -> f32How much the transform scales areas, as a single length factor (the square root of the area scale). Used for line widths and text.
Inputs
- t: The transform
Returns
- The length factor (1 for no scaling)
procedure. Source: transform.odin:164
Transform2D_IsAxisAligned
Section titled “Transform2D_IsAxisAligned”Transform2D_IsAxisAligned :: proc(t: Transform2D) -> boolWhether the transform keeps axis-aligned rectangles axis-aligned (no rotation or skew; mirroring is fine).
Inputs
- t: The transform
Returns
- true if its axes are along X and Y
procedure. Source: transform.odin:178
Node_GetLocalTransform
Section titled “Node_GetLocalTransform”Node_GetLocalTransform :: proc(node: ^Node) -> Transform2DThe node’s transform relative to its parent, built from position, rotation and scale.
Inputs
- node: The node
Returns
- The local transform
procedure. Source: transform.odin:194
Node_GetWorldTransform
Section titled “Node_GetWorldTransform”Node_GetWorldTransform :: proc(node: ^Node) -> Transform2DThe node’s transform in world coordinates: its local transform combined with all of its ancestors’. Computed from the current values, so it’s correct even right after something moved during an update. The camera isn’t included.
Inputs
- node: The node
Returns
- The world transform
procedure. Source: transform.odin:209
Node_GetWorldPosition
Section titled “Node_GetWorldPosition”Node_GetWorldPosition :: proc(node: ^Node) -> [2]f32The node’s origin in world coordinates.
Inputs
- node: The node
Returns
- The world position
Example
// Given: target: ^e.NodetoTarget := e.Node_GetWorldPosition(target) - e.Node_GetWorldPosition(node)node.rotation = math.to_degrees(math.atan2(toTarget.y, toTarget.x))procedure. Source: transform.odin:231
Node_SetWorldPosition
Section titled “Node_SetWorldPosition”Node_SetWorldPosition :: proc(node: ^Node, world: [2]f32)Moves the node so its origin is at a world position, whatever its parent’s transform is.
Inputs
- node: The node
- world: Target position in world coordinates
Example
// Given: target: ^e.Nodee.Node_SetWorldPosition(node, e.Node_GetWorldPosition(target) + {0, -32})procedure. Source: transform.odin:246
Node_GetScreenPosition
Section titled “Node_GetScreenPosition”Node_GetScreenPosition :: proc(node: ^Node) -> [2]f32Where the node’s origin appears on screen (game resolution coordinates): through the scene’s
camera, unless the node’s layer is in scene.screenLayers. Use it to hit-test UI nodes against
mouse events.
Inputs
- node: The node
Returns
- The screen position, in game coordinates
procedure. Source: transform.odin:266
Node_SetParent
Section titled “Node_SetParent”Node_SetParent :: proc(child: ^Node, parent: ^Node, keepWorld := false) -> boolMakes child a child of parent, or a top-level node when parent is nil. Both must be in
the same scene.
Inputs
- child: The node to move
- parent: New parent, or nil
- keepWorld: Keep the node where it is in the world (its local position, rotation and scale are recalculated). Otherwise its local values are kept, so it moves with the new parent. Skew from a non-uniformly scaled parent can’t be kept
Returns
- false if
parentischildor one of its descendants (that would make a loop), or is in another scene (logged)
Example
// Given: hand: ^e.Node, sword: ^e.Nodee.Node_SetParent(sword, hand, keepWorld = true) // picked up where it laye.Node_SetParent(sword, nil, keepWorld = true) // dropped where it isprocedure. Source: transform.odin:293
Node_AddChild
Section titled “Node_AddChild”Node_AddChild :: proc(parent, child: ^Node) -> boolAdds a child to a node. Same as Node_SetParent(child, parent): the child keeps its local values.
Inputs
- parent: The new parent
- child: The node to add
Returns
- false if that would make a loop, or the nodes are in different scenes (see
Node_SetParent)
Example
// Given: ship: ^e.Nodeflame := e.CreateNode(scene, e.Sprite)e.Node_AddChild(ship, flame)flame.position = {0, 40} // 40 below the ship, wherever the ship goesprocedure. Source: transform.odin:338
GetDrawTransform
Section titled “GetDrawTransform”GetDrawTransform :: proc() -> Transform2DThe transform the Draw* helpers currently apply: from the drawing node’s local space to the screen. Identity outside of scene drawing. Use it in custom drawing code that calls SDL directly.
Example
MyNode_draw :: proc(node: ^e.Node, renderer: ^sdl.Renderer) { p := e.Transform2D_Apply(e.GetDrawTransform(), {0, 0}) sdl.RenderPoint(renderer, p.x, p.y)}procedure. Source: transform.odin:391
SetDrawState
Section titled “SetDrawState”SetDrawState :: proc(transform: Transform2D, tint := sdl.Color{255, 255, 255, 255}, blend := BlendMode.Blend)Sets the transform, tint and blend mode the Draw* helpers apply (on the calling thread). Scenes do this for each node; call it only when drawing outside a scene, and restore it afterwards.
Inputs
- transform: Local space to screen
- tint: Multiplies every color drawn (white: unchanged)
- blend: The blend mode
DrawTextureuses (shapes use the renderer’s draw blend mode)
Example
e.SetDrawState(e.Transform2D_Make({200, 100}, 45, {2, 2}))e.DrawCircle(renderer, {0, 0}, 10, {255, 255, 255, 255})e.SetDrawState(e.TRANSFORM2D_IDENTITY)procedure. Source: transform.odin:409
SDLBlendMode
Section titled “SDLBlendMode”SDLBlendMode :: proc(blend: BlendMode) -> sdl.BlendModeThe SDL blend mode of a BlendMode.
Inputs
- blend: The engine’s blend mode
Returns
- The matching SDL blend mode
procedure. Source: transform.odin:424
GetDrawBlend
Section titled “GetDrawBlend”GetDrawBlend :: proc() -> sdl.BlendModeThe blend mode of the node drawing now, for drawing code that sets its own (geometry)
procedure. Source: transform.odin:436
NewEntityId
Section titled “NewEntityId”NewEntityId :: proc() -> EntityIdA new random id (a version 4 UUID) from Odin’s fast per-thread generator. Ids needn’t be
secret, and the OS’s cryptographic source (what uuid.generate_v4 insists on) cost a system call
per node and per listener.
Returns
- The new id
procedure. Source: event.odin:31
EventSubject_Init
Section titled “EventSubject_Init”EventSubject_Init :: proc(subject: ^EventSubject, allocator := context.allocator)Prepares an event subject for use.
Inputs
- subject: The subject to initialize
- allocator: Allocator for the listener list. Use the owner’s allocator (e.g.
Node_GetAllocator(node)) so the list is freed along with the owner
procedure. Source: event.odin:84
Event_Subscribe
Section titled “Event_Subscribe”Event_Subscribe :: proc(subject: ^EventSubject, callback: proc(event: ^EventSubject, source: rawptr, data: EventData, userData: rawptr), userData: rawptr) -> ListenerHandleAdds a listener to a subject. Call it as subject->subscribe(callback, userData).
A listener added while a notification runs is first called by the next notification.
Inputs
- subject: The subject to listen to
- callback: Called on each notification with the subject, the source and data passed to
Event_Notify, anduserData - userData: Any pointer, passed back to
callback(nil when not needed)
Returns
- The handle to unsubscribe with (
Event_Unsubscribe)
Example
// Given: subject: ^e.EventSubjecthandle := subject->subscribe(proc(event: ^e.EventSubject, source: rawptr, data: e.EventData, userData: rawptr) { if score, ok := data.(int); ok do log.infof("score: %d", score)}, nil)subject->notify(nil, 42)subject->unsubscribe(handle)procedure. Source: event.odin:113
Event_Unsubscribe
Section titled “Event_Unsubscribe”Event_Unsubscribe :: proc(subject: ^EventSubject, handle: ListenerHandle)Removes a listener from a subject. Call it as subject->unsubscribe(handle).
Safe during a notification: the listener isn’t called again, and the list is compacted when the notification ends. The other listeners keep their order. Logs a warning if the handle isn’t subscribed.
Inputs
- subject: The subject
- handle: The handle
Event_Subscribereturned
procedure. Source: event.odin:135
Event_Notify
Section titled “Event_Notify”Event_Notify :: proc(subject: ^EventSubject, source: rawptr, data: EventData)Calls every listener, in the order they subscribed. A listener may subscribe or unsubscribe
(itself or others) while being called: ones that subscribe then are first called by the next
notification; ones unsubscribed then aren’t called again, this time included. Call it as
subject->notify(source, data).
Inputs
- subject: The subject
- source: Who sent it (a node, a scene…), passed to each listener; may be nil
- data: A value passed to each listener; nil for none
procedure. Source: event.odin:162
Event_Destroy
Section titled “Event_Destroy”Event_Destroy :: proc(subject: ^EventSubject)Frees the subject’s listener list. Not needed when the list was made with an arena allocator that’s freed anyway (a node’s or scene’s).
Inputs
- subject: The subject
procedure. Source: event.odin:190
NodeType_Events
Section titled “NodeType_Events”NodeType_Events :: proc(type: typeid, allocator := context.temp_allocator) -> []NodeEventInfoThe events of a node type: its tagged EventSubject fields, those of the types it embeds
(Node’s onEnter and onExit last) and of its struct fields.
Inputs
- type: The node type (
Sprite,CharacterMover…;node.type) - allocator: For the list
Returns
- The events, the type’s own first
procedure. Source: node_events.odin:58
SceneNodeKind_Events
Section titled “SceneNodeKind_Events”SceneNodeKind_Events :: proc(kind: SceneNodeKind, allocator := context.temp_allocator) -> []NodeEventInfoThe events nodes of a scene file’s kind offer (a Component instance: a Node’s; its root’s type decides the rest).
Inputs
- kind: The kind
- allocator: For the list
Returns
- The events (
NodeType_Events)
procedure. Source: node_events.odin:92
Node_FindEvent
Section titled “Node_FindEvent”Node_FindEvent :: proc(node: ^Node, name: string) -> ^EventSubjectA node’s event by name.
Inputs
- node: The node
- name: The event (
NodeEventInfo.name)
Returns
- Its subject, or nil when the node’s type has no such event
Example
// Given: emitter: ^e.Nodeif finished := e.Node_FindEvent(emitter, "onFinished"); finished != nil do log.info("it can finish")procedure. Source: node_events.odin:110
Script_FindEvent
Section titled “Script_FindEvent”Script_FindEvent :: proc(data: rawptr, info: ^ScriptInfo, name: string) -> ^EventSubjectA script’s event by name: one of its struct’s EventSubject fields tagged event:"...".
Inputs
- data: The script struct (a node’s script, or a global)
- info: Its script
- name: The event, “script.<field>”
Returns
- Its subject, or nil
procedure. Source: node_events.odin:136
Node_AddListener
Section titled “Node_AddListener”Node_AddListener :: proc(node: ^Node, listener: NodeListenerData)Adds a listener to a node: before the scene’s scripts next get ready (for a node built from a
scene file: before they first do), the target’s script’s handler is subscribed to the event.
Scene files’ <listener> elements come here.
Inputs
- node: The node whose event it is
- listener: The event, the listening node (a path from
node) and the handler’s name (copied)
procedure. Source: node_events.odin:172
Script_Listener
Section titled “Script_Listener”Script_Listener :: proc "contextless" ($T: typeid, name: string, handler: proc(self: ^T, source: ^Node, data: EventData))Registers a script’s event handler by name, for scene files’ listeners (<listener handler=...>)
to call. Like Script_Register, call it from an @(init) proc "contextless"; the editor writes
these when you attach a listener in the inspector.
Inputs
- T: The script struct
- name: The handler’s name, as scene files give it
- handler: Called with the listening script, the node whose event it is, and the event’s data
Example
Hero :: struct { using script: e.Script, coins: int,}Hero_onCoinCollected :: proc(self: ^Hero, source: ^e.Node, data: e.EventData) { self.coins += 1}register_Hero_onCoinCollected :: proc "contextless" () { e.Script_Listener(Hero, "onCoinCollected", Hero_onCoinCollected)}procedure. Source: node_events.odin:207
Script_FindListener
Section titled “Script_FindListener”Script_FindListener :: proc(type: typeid, name: string) -> ScriptListenerProcA script’s handler registered with Script_Listener.
Inputs
- type: The script struct’s type
- name: The handler’s name
Returns
- The handler, or nil
procedure. Source: node_events.odin:224
TimerNode_Init
Section titled “TimerNode_Init”TimerNode_Init :: proc(timer: ^Timer, duration: time.Duration, auto_start: bool = false, loop: bool = false, allocator := context.allocator)Prepares a timer: its events and procedures. Its elapsed time starts at zero.
Inputs
- timer: The timer
- duration: How long until it expires
- auto_start: Start running now
- loop: Start over each time it expires
- allocator: For the event listener lists; inside a node, pass
Node_GetAllocator(node)
procedure. Source: timer.odin:68
TimerNode_Create
Section titled “TimerNode_Create”TimerNode_Create :: proc(durationSeconds: f32, auto_start: bool = false, loop: bool = false, allocator := context.allocator) -> ^TimerMakes and prepares a timer.
Inputs
- durationSeconds: How long until it expires
- auto_start: Start running now
- loop: Start over each time it expires
- allocator: For the timer and its listener lists; inside a node, pass
Node_GetAllocator(node)
Returns
- The timer (freed with the allocator)
procedure. Source: timer.odin:92
TimerNode_Start
Section titled “TimerNode_Start”TimerNode_Start :: proc(timer: ^Timer)Starts (or restarts) a timer from zero. Also timer->start().
Inputs
- timer: The timer
procedure. Source: timer.odin:104
TimerNode_Stop
Section titled “TimerNode_Stop”TimerNode_Stop :: proc(timer: ^Timer)Stops a timer where it is (timer.running = true resumes it).
Inputs
- timer: The timer
procedure. Source: timer.odin:115
TimerNode_SetDuration
Section titled “TimerNode_SetDuration”TimerNode_SetDuration :: proc(timer: ^Timer, durationSec: f32)Sets how long a timer runs before it expires (its elapsed time is kept). Also
timer->setDuration(seconds).
Inputs
- timer: The timer
- durationSec: The new duration, in seconds
procedure. Source: timer.odin:152
Ticker_Init
Section titled “Ticker_Init”Ticker_Init :: proc(ticker: ^Ticker, auto_start: bool = true, allocator := context.allocator)Prepares a ticker: its procedures and onTick, at 1 tick per second.
Inputs
- ticker: The ticker
- auto_start: Start it now (counter at 0)
- allocator: Holds the event listener list. Inside a node, pass
Node_GetAllocator(node)
procedure. Source: ticker.odin:86
Ticker_Start
Section titled “Ticker_Start”Ticker_Start :: proc(ticker: ^Ticker, resetCounter: bool = true)Starts the ticker: the time to the next tick starts from now. Also ticker->start(resetCounter).
Inputs
- ticker: The ticker
- resetCounter: Set the tick count back to 0
procedure. Source: ticker.odin:109
Ticker_Stop
Section titled “Ticker_Stop”Ticker_Stop :: proc(ticker: ^Ticker)Stops the ticker. Also ticker->stop().
Inputs
- ticker: The ticker
procedure. Source: ticker.odin:121
Ticker_TogglePause
Section titled “Ticker_TogglePause”Ticker_TogglePause :: proc(ticker: ^Ticker)Pauses a running ticker, or resumes a stopped one. Also ticker->togglePause().
Inputs
- ticker: The ticker
procedure. Source: ticker.odin:131
Ticker_Pause
Section titled “Ticker_Pause”Ticker_Pause :: proc(ticker: ^Ticker)Pauses the ticker (the same as Ticker_Stop). Also ticker->pause().
Inputs
- ticker: The ticker
procedure. Source: ticker.odin:142
Ticker_Resume
Section titled “Ticker_Resume”Ticker_Resume :: proc(ticker: ^Ticker)Resumes the ticker where it was paused: the time it was paused doesn’t count toward the next
tick. Also ticker->resume().
Inputs
- ticker: The ticker
procedure. Source: ticker.odin:152
Ticker_SetFPS
Section titled “Ticker_SetFPS”Ticker_SetFPS :: proc(ticker: ^Ticker, framesPerSecond: f32)Sets the tick rate. Also ticker->setFPS(rate).
Inputs
- ticker: The ticker
- framesPerSecond: Ticks per second, clamped to 0 to 120 (0 never ticks)
procedure. Source: ticker.odin:164
Ticker_Reset
Section titled “Ticker_Reset”Ticker_Reset :: proc(ticker: ^Ticker)Sets the tick count and the time since the last tick back to 0. Also ticker->reset().
Inputs
- ticker: The ticker
procedure. Source: ticker.odin:176
NodeType_Register
Section titled “NodeType_Register”NodeType_Register :: proc "contextless" ($T: typeid, base: SceneNodeKind, hooks: ScriptHooks(T), loc := #caller_location)Registers a custom node type: call it from an @(init) proc "contextless" in the game’s scripts.
Inputs
- T: The struct; its first field is the base kind’s type (
using sprite: e.Sprite) - base: The built-in kind it extends (
.Sprite) - hooks: Its procedures, as a script’s (self: the node)
- loc: Where it’s called from (the editor opens the file)
procedure. Source: custom_nodes.odin:67
NodeType_Find
Section titled “NodeType_Find”NodeType_Find :: proc(name: string) -> ^NodeTypeInfoThe custom node type registered under a name, or nil
procedure. Source: custom_nodes.odin:105
NodeType_All
Section titled “NodeType_All”NodeType_All :: proc() -> []^NodeTypeInfoEvery registered custom node type, by name (temp allocated)
procedure. Source: custom_nodes.odin:110
Constants and variables
Section titled “Constants and variables”MAX_EVENTS
Section titled “MAX_EVENTS”MAX_EVENTS :: 1000Most SDL events Application_Frame collects in one frame; the rest wait for the next frame
constant. Source: app.odin:11
DEV_KEY_PHYSICS_DEBUG
Section titled “DEV_KEY_PHYSICS_DEBUG”DEV_KEY_PHYSICS_DEBUG :: sdl.Scancode.F9With Game.devKeys: shows and hides the physics shapes while the game runs
constant. Source: game.odin:28
DEV_KEY_DEBUG_OVERLAY
Section titled “DEV_KEY_DEBUG_OVERLAY”DEV_KEY_DEBUG_OVERLAY :: sdl.Scancode.F10With Game.devKeys: shows and hides the debug overlay (debug.odin)
constant. Source: game.odin:30
NODE_ARENA_BLOCK_SIZE
Section titled “NODE_ARENA_BLOCK_SIZE”NODE_ARENA_BLOCK_SIZE :: 4 * mem.KilobyteInitial (and minimum) size of each block of a node’s arena. Small, because most nodes only hold a few event listener lists; the arena adds blocks if a node needs more
constant. Source: node.odin:55
TRANSFORM2D_IDENTITY
Section titled “TRANSFORM2D_IDENTITY”TRANSFORM2D_IDENTITY :: Transform2D{x = {1, 0}, y = {0, 1}}The transform that changes nothing: axes {1, 0} and {0, 1}, origin {0, 0}
constant. Source: transform.odin:28
SCRIPT_EVENT_PREFIX
Section titled “SCRIPT_EVENT_PREFIX”SCRIPT_EVENT_PREFIX :: "script."Script events are named with this before their field (“script.onCoinCollected”)
constant. Source: node_events.odin:123
