Programming your game

Your first component

Create a .zig file inside your project's assets/ folder. The editor discovers components by scanning these files. Each component is a plain Zig struct with the is_component marker:

const engine = @import("engine");

pub const Rotator = struct {
    pub const is_component = true;

    speed: f32 = 90.0,

    pub fn update(self: *Rotator, time: engine.Time) void {
        // rotation API will be applied here
        _ = self;
        _ = time;
    }
};

Rules for component discovery:

  • The struct must be pub and its name must start with a capital letter.
  • pub const is_component = true; marks it for discovery. Set to false to temporarily opt out.
  • Component type names must be unique across the project.
  • Supported field types: f32, i32, bool, engine.Vec3, engine.GameObjectRef, engine.ComponentRef, engine.AssetRef.

After saving the file, click Refresh in the Asset Browser, then select a node in the Hierarchy and choose your component from Add Component ▾.

Lifecycle hooks

All hooks are optional:

Hook Called when
awake(self) Object is first loaded
enable(self) Object becomes active
start(self) Scene starts running
update(self, time: engine.Time) Every frame
disable(self) Object becomes inactive
destroy(self) Object is destroyed

Each hook also accepts a frame: engine.Frame parameter if you need input, services, or the full scene context — use update(self, frame: engine.Frame) instead.

See the Components page for a more complete explanation of the node lifecycle and every available hook.

Using engine.Time

The engine.Time struct provides frame timing information:

Field Type Meaning
.delta f32 seconds since the last frame
.elapsed f32 total seconds since the scene started
.frame u64 frame counter

Example — an FPS counter:

const std = @import("std");
const engine = @import("engine");

pub const FpsDisplay = struct {
    pub const is_component = true;

    _timer: f32 = 0,

    pub fn update(self: *FpsDisplay, time: engine.Time) void {
        self._timer += time.delta;
        if (self._timer >= 1.0) {
            self._timer -= 1.0;
            const fps = if (time.delta > 0) 1.0 / time.delta else 0.0;
            std.debug.print("FPS: {d:.0}\n", .{fps});
        }
    }
};

Customising field values in the Inspector

Any public field with a default value becomes an editable control in the Inspector panel. Select a node, expand its component, and edit directly:

  • f32 → number field with arrow-step buttons.
  • i32 → integer field.
  • bool → checkbox.
  • engine.Vec3 → three-axis vector field with labelled axes.
  • engine.GameObjectRef → drop target — drag a node from the Hierarchy.
  • engine.ComponentRef → drop target — reference another component.
  • engine.AssetRef → drop target — reference any asset.

Fields prefixed with _ (like _timer) are treated as private runtime state and are hidden from the Inspector.

To set default values that differ per instance, change them in the Inspector after adding the component — the editor records the override in the scene file.

Example

const engine = @import("engine");

pub const Patrol = struct {
    pub const is_component = true;

    speed: f32 = 2.5,
    target: engine.GameObjectRef = .{},
    loop: bool = true,
};

After adding Patrol to a node, the Inspector shows speed, target, and loop. Adjust speed to 5.0, drag a node into target, and uncheck loop — all without touching code.


← All docs Edit this page