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
puband its name must start with a capital letter. pub const is_component = true;marks it for discovery. Set tofalseto 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.