Track score, lives and time, and schedule delays that pause with the game.
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
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") }; // rulesreturn world.scoreboard.listen({ onScore: setScore, onSecond: setSeconds }); // UI; unlistens on unmount}, [world, onEnd]);return { score, seconds };}
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
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 60start: () => board.resume(),coin: () => board.add(10),hurt: () => board.addLives(-1), // onLivesOut fires at 0};}
Operations change state immediately; their callbacks follow on the next step. stop() and resume() affect only the clock, not the rest of the simulation.
| Operation | Behavior |
|---|---|
| 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. |
| shownSeconds | Whole seconds: rounded up counting down, down counting up. |
Finish with a time bonus
React and React Native · shared engine API
import type { World } from "@nayan-ui/engine";export function finishLevel(world: World) {world.scoreboard.stop(); // freeze the clock before the results screenconst score = world.scoreboard.awardTimeBonus(10); // 10 points per second leftreturn { score, secondsLeft: world.scoreboard.shownSeconds };}
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
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};}