Scoreboards and game-time timers

Track score, lives and time, and schedule delays that pause with the game.

Rules and listeners

world.scoreboard keeps score, lives and the clock in Rust; read score, best, lives, seconds, shownSeconds and running. Assign handlers for the game's rules and call listen for UI that follows along: it adds handlers without replacing others and returns an unlisten function.

Callbacks run from the world's step only when a value changed, handlers first and then listeners in order. best lasts for the scoreboard's life, so persist personal bests yourself.

A HUD hook

React and React Native · shared engine API

A HUD hook
import { useEffect, useState } from "react";
import type { World } from "@nayan-ui/engine";
export function useHud(world: World, onEnd: (result: "win" | "lose") => void) {
const [score, setScore] = useState(0);
const [seconds, setSeconds] = useState(60);
useEffect(() => {
world.scoreboard.handlers = { onTimeUp: () => onEnd("win"), onLivesOut: () => onEnd("lose") }; // rules
return world.scoreboard.listen({ onScore: setScore, onSecond: setSeconds }); // UI; unlistens on unmount
}, [world, onEnd]);
return { score, seconds };
}

Ready, start and score

reset(lives, countdownSeconds) begins a run: score 0, those lives and a stopped clock that already shows the full countdown, which is your ready screen. resume() starts it when the player presses Start; reset actors and input yourself.

A round's controls

React and React Native · shared engine API

A round's controls
import type { World } from "@nayan-ui/engine";
export function round(world: World) {
const board = world.scoreboard;
return {
ready: () => board.reset(3, 60), // 3 lives, stopped at 60
start: () => board.resume(),
coin: () => board.add(10),
hurt: () => board.addLives(-1), // onLivesOut fires at 0
};
}

Scoreboard operations

Operations change state immediately; their callbacks follow on the next step. stop() and resume() affect only the clock, not the rest of the simulation.

Scoreboard operations reference
OperationBehavior
add(points) / set(score)Add (negative subtracts) or replace the score; returns it.
addLives(n) / setLives(n)Adjust or replace lives; addLives returns the result.
startCountdown(seconds, pointsPerSecond = 0)Count down to 0, then onTimeUp; optionally earn points.
startClock(pointsPerSecond = 0)Count up from 0, for survival or distance scores.
stop() / resume()Pause or continue the clock only.
addTime(seconds)Time bonus, or a penalty with negative seconds.
awardTimeBonus(pointsPerSecond, expectedSeconds = 0)Points per whole second saved; returns the score.
reset(lives = 0, countdownSeconds = 0)New run with a stopped clock showing the countdown.
shownSecondsWhole seconds: rounded up counting down, down counting up.

Finish with a time bonus

React and React Native · shared engine API

Finish with a time bonus
import type { World } from "@nayan-ui/engine";
export function finishLevel(world: World) {
world.scoreboard.stop(); // freeze the clock before the results screen
const score = world.scoreboard.awardTimeBonus(10); // 10 points per second left
return { score, secondsLeft: world.scoreboard.shownSeconds };
}

Game-time timers

world.after(seconds, fn) runs fn after that much update time and returns an id; it pauses whenever the world stops stepping, so never use setTimeout for gameplay delays. cancelTimer(id) drops one (false if it already ran), cancelTimers() drops all of them on reset, and timerCount reports how many wait.

After dispose, after does nothing and returns 0, and pending timers never run.

Spawn a wave later, cancel on reset

React and React Native · shared engine API

Spawn a wave later, cancel on reset
import type { World } from "@nayan-ui/engine";
export function scheduleWave(world: World) {
const id = world.after(1.5, () => world.spawn({ mesh: "sphere", position: [3, 2, 0], color: "red" }));
return {
cancel: () => world.cancelTimer(id),
resetRound: () => world.cancelTimers(), // an old round can't act in the new one
};
}