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.