Scripting with Cog

Hooks and events

A script doesn’t run from top to bottom and stop. It declares hooks, functions with names the engine knows, and the engine calls each one when its moment comes. Every hook is optional: write only the ones you need. The Cog API reference lists them all.

When play starts and every frame

on_ready() runs once when play starts: the place to find other objects and components. The script’s top-level vars were set before it. on_update(delta) runs every frame, and on_destroy() when the object is destroyed or play stops.

var base_y: float = 0.0
var t: float = 0.0

fn on_ready() {
    base_y = self.position.y
}

fn on_update(delta: float) {
    t = t + delta
    var p = self.position
    self.position = Vec3(p.x, base_y + math.sin(t * 3.0) * 0.2, p.z)
}

delta is how long the frame took, in seconds. Multiply anything that changes over time by it, so it changes at the same speed on a fast computer and a slow one.

Physics

When this object starts touching another, on_collision(other, normal) runs, and on_collision_exit(other) when they part. normal points the way this object would have to move to get out. A collider set to Sensor doesn’t block; it reports on_trigger_enter(other) and on_trigger_exit(other) instead.

other is the other object, as a handle: read other.name, check other.has_tag("enemy"), or other.send(...) to it. See Physics.

UI

Scripts on an object with a Canvas get on_ui_click(path) when a button is clicked, on_ui_change(path, value) when a slider moves or a text input changes, and on_ui_submit(path, text) when Enter is pressed in a text input.

Talking to other scripts

Any function that isn’t a hook is a handler: another script can call it with send.

// key.cog: when the player touches the key, tell them and disappear.
fn on_trigger_enter(other) {
    if other.name == "Player" {
        other.send("take_key")
        Scene.destroy()
    }
}
// player.cog
var keys = 0

fn take_key() {
    keys = keys + 1
    Scene.find("HUD").send("show_keys", keys)
}

send passes any extra arguments along: send("show_keys", keys) calls show_keys(keys) on every script on the HUD object that has it. Sending a name nothing there has is a warning in the Console, once.

Let each object decide for itself. The key doesn’t change the player’s count; it tells the player, and the player counts. The exit doesn’t check the player’s keys; it asks the player to leave, and the player decides whether it can. Scripts that only send to each other, and never reach into each other’s variables, stay easy to change.

Later, and over and over

Timers call a function after a delay, or repeatedly:

fn on_ready() {
    self.every(3.0, spawn_crate)          // every 3 seconds
    self.after(10.0, "game_over")         // once, by a handler's name
}

fn spawn_crate() {
    Scene.spawn("assets://prefabs/crate.ofab", self.position)
}

fn game_over() {
    log.info("Time's up")
}

Both give back an id; self.cancel(id) stops it. Timers fire before on_update, in the order they were set. A timer is usually better than counting time in on_update yourself.