Examples
Complete, runnable ScriptBehavior classes you can paste into the Code panel, Compile, and attach - a spinner, a first-person controller, a scene-loading trigger, and a savable pickup.
Each is self-contained. Paste it, press Compile, add a Script component to an object, and pick it. Public fields (speed, camera, …) appear as Inspector controls.
Spinner
The "hello world" of Awaken scripting - rotate an object every frame. Tune the speed and axis per instance.
ts
// Spins this object every frame.
export default class implements ScriptBehavior {
speed = 90; // degrees/second 0..720 (slider)
axis: "x" | "y" | "z" = "y"; // rotation axis (dropdown)
clockwise = true; // reverse direction (checkbox)
onUpdate(api: ScriptApi, dt: number) {
const rad = (this.speed * Math.PI / 180) * dt * (this.clockwise ? 1 : -1);
api.rotate(this.axis === "x" ? 1 : 0, this.axis === "y" ? 1 : 0, this.axis === "z" ? 1 : 0, rad);
}
}Attach it to a pickup, a fan, a coin - anything that should turn. To spin about the object's visual centre when its origin is off-centre, read api.getMeshCenter() before and after the rotate and translate by the difference.
First-person controller
A complete WASD + mouse-look + jump controller with real capsule collision. This is the first-person-controller design as importable content - the engine only provides mouse input and moveAndSlide; all the feel lives here.
The rig: this script attaches to a player object that has a capsule Collider. You assign your scene camera to the camera field; the script keeps the camera at eye height and applies pitch to it, while the player body takes the yaw.
ts
// First-person controller. Attach to a player object with a capsule Collider.
// Assign your scene camera to the `camera` field.
function q(axis: "x" | "y" | "z", a: number) {
const s = Math.sin(a / 2), c = Math.cos(a / 2);
return { x: axis === "x" ? s : 0, y: axis === "y" ? s : 0, z: axis === "z" ? s : 0, w: c };
}
function qMul(a: any, b: any) {
return {
x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
};
}
export default class implements ScriptBehavior {
camera: Camera; // the scene camera to drive
moveSpeed = 5; // m/s 0..12
mouseSensitivity = 0.0025; // radians per pixel
jumpHeight = 1.2; // metres 0..3
eyeHeight = 1.6; // camera height above the player origin (m)
gravity = 20; // m/s^2
private yaw = 0;
private pitch = 0;
private vy = 0;
onUpdate(api: ScriptApi, dt: number) {
// 1. Look - only while the pointer is locked (click the game view to lock).
if (api.input.pointerLocked) {
this.yaw -= api.input.mouseDX * this.mouseSensitivity;
this.pitch = Math.max(-1.55, Math.min(1.55, this.pitch - api.input.mouseDY * this.mouseSensitivity));
}
// Player body takes the yaw (turns the mesh); camera takes yaw * pitch.
const yawQ = q("y", this.yaw);
api.setField("Transform", "rotation", yawQ);
// 2. Move - WASD in the yaw frame.
const f = (api.input.isDown("w") ? 1 : 0) - (api.input.isDown("s") ? 1 : 0);
const s = (api.input.isDown("d") ? 1 : 0) - (api.input.isDown("a") ? 1 : 0);
const mx = (Math.cos(this.yaw) * s + Math.sin(this.yaw) * f) * this.moveSpeed;
const mz = (-Math.sin(this.yaw) * s + Math.cos(this.yaw) * f) * this.moveSpeed;
// 3. Gravity + jump.
this.vy -= this.gravity * dt;
// 4. Collide - slide the capsule; get grounded back.
const { grounded } = api.moveAndSlide(mx * dt, this.vy * dt, mz * dt);
if (grounded) {
if (this.vy < 0) this.vy = 0;
if (api.input.isDown(" ")) this.vy = Math.sqrt(2 * this.gravity * this.jumpHeight);
}
// 5. Camera - eye height above the player, composed yaw * pitch.
if (this.camera) {
const T = api.world.registry.get("Transform");
const cam = api.world.get(this.camera, T);
if (cam) {
const p = api.getPosition();
cam.position = { x: p.x, y: p.y + this.eyeHeight, z: p.z };
cam.rotation = qMul(yawQ, q("x", this.pitch));
api.world.markMoved(this.camera);
}
}
}
}Notes on how it works:
api.setField("Transform", "rotation", …)writes this object's rotation (the player body). The camera is a different entity, so it is driven throughapi.world- get its Transform viaworld.registry.get("Transform"), write position/rotation, thenmarkMovedso the renderer syncs it.- The capsule becomes a kinematic character body automatically on the first
moveAndSlidecall - no extra component beyond the capsule Collider. pointerLockedgates look so the camera does not swing before the player clicks in. See Input.
Trigger that loads a scene
A level exit. Put a trigger Collider on the goal; when the player walks in, load the next scene.
ts
// Level exit - loads a scene when the player enters this trigger.
export default class implements ScriptBehavior {
nextScene = "Level2"; // scene to load (edit per instance)
playerTag = "player"; // which object counts as the player
onCollisionEnter(api: ScriptApi, other: Entity) {
const isPlayer = api.findByTag(this.playerTag).includes(other)
|| api.world.getName(other) === "Player";
if (isPlayer) api.loadScene(this.nextScene);
}
}loadScene is deferred to the next frame, so the swap is clean. Tag your player object (a Tag component with value player) for a rename-proof check, or fall back to the name. See Scenes & Persistence.
Pickup with save
A collectible that adds to the score, remembers a lifetime total across reloads, plays a sound, and removes itself.
ts
// Collectible coin - collect on touch, persist the running total.
export default class implements ScriptBehavior {
value = 1; // points this pickup is worth 1..100
sound = "beep"; // clip to play on pickup
onCollisionEnter(api: ScriptApi, other: Entity) {
if (api.world.getName(other) !== "Player") return;
// Session score (survives scene loads).
api.state.score = (api.state.score as number ?? 0) + this.value;
api.setHud(`Score: ${api.state.score}`);
// Lifetime total (survives page reloads).
const total = (api.load("coinsCollected") as number ?? 0) + this.value;
api.save("coinsCollected", total);
api.playSound(this.sound, { volume: 0.6 });
api.destroy(); // remove the coin; onDestroy + timers auto-clean
}
}Two lifetimes at work: api.state.score carries the run's score across level loads, while api.save("coinsCollected", …) banks a total that outlives the browser session. See Scenes & Persistence.
See also
- ScriptApi Reference - every method these examples use
- Inspector Parameters - the public fields shown here
- Input - the controller's keyboard and mouse-look
- Physics: Character Controller -
moveAndSlidein depth - Tutorial: FPS Controller - building the controller step by step