Scenes
Scenes represent a “screen” (or state) in your game: main gameplay, main menu, pause overlay, cutscene, etc.
In Sliver, a Scene is responsible for:
- Holding and updating a list of
GameObjects - Rendering its background + objects + optional overlay
- Dispatching input/events to its objects (with propagation control)
- Running simple physics helpers: gravity + collision resolution
- Supporting camera-like movement via a scene
offset(used by transitions too)
Scenes receive a shared GameContext from the engine (via SceneManager), so game objects can access input, audio, scene switching, and the message bus.
Creating scenes and registering them
Create scenes with:
import { Scene, SceneManager } from "sliver-engine";
const main = new Scene("main", "#0b0a18"); // name + optional background color
const pause = new Scene("pause");
const scenes = new SceneManager(
{ main, pause }, // registry (keys are the ids you switch by)
main // initial current scene (optional)
);
Notes:
- The registry key (
"main","pause") is what you’ll use withsetCurrentScene(...)/transitionToScene(...). - The
Sceneconstructor’snameis currently used mainly for logging (setup()prints it). It’s easiest if you keep it the same as the registry key.
Setup and context injection
When a scene becomes active, SceneManager will:
- call
scene.setContext(gameContext) - call
scene.setup()
This happens when you:
- set a scene as current
- push a scene on the stack
- transition to a scene
If you subclass Scene, setup() is the right place to populate it:
import { Scene } from "sliver-engine";
class MainScene extends Scene {
override setup(): void {
super.setup();
// addGameObject(...), setGravity(...), etc.
}
}
Adding and removing game objects
Add one or many objects:
scene.addGameObject(player);
scene.addGameObject([hud, enemy1, enemy2]);
What addGameObject does for you:
- sets
go.scene = scene - injects the scene’s
GameContextinto the object (and its children) sothis.getContext()starts working
Remove objects either by:
- calling
gameObject.destroy()(recommended; also detaches children and removes from the scene), or scene.removeGameObject(gameObject)if you need manual removal
Tick: updates, gravity, collisions
Each tick, Scene.tick():
- calls
tick()on everyGameObjectin the scene - runs collision detection + resolution across objects that have hitboxes
For a deeper dive (hitboxes, restitution/friction/mass, triggers, and collision hooks), see Physics.
Gravity
A scene has a gravity vector:
import { Vector } from "sliver-engine";
scene.setGravity(new Vector(0, 0.4));
Gravity is applied by GameObject.tick() when:
phisics.affectedByGravity === truephisics.immovable === false- the object isn’t being dragged (
beingGrabbed === false)
Gravity is added directly to speed once per tick, so treat it as “acceleration per tick” rather than per-second.
Collisions
After objects tick, the scene resolves collisions between hitboxes:
- Only active objects with at least one hitbox participate.
- Objects with
phisics.immovable === truewon’t be moved by resolution. beforeColision(other)runs on both objects the first time a pair is checked; if either returnsfalse, that pair is ignored for the rest of the tick.onColision(other, penetration)is called once per pair per tick when overlap is detected.
If you want an object to be physical, you typically:
- give it one or more hitboxes
- set
immovable: false - optionally tweak
restitution,friction,mass, and gravity behavior
See Physics for collision/response details, and Game objects for the hitbox and gameplay patterns.
Render: background, objects, overlay
Each frame, the game renders every active scene (see Game loop).
Scene.render(...) draws, in order:
- background fill (if the scene has a background color)
- all game objects (
obj.render(canvas, scene)) - an optional full-screen overlay color (used by some transitions)
Scenes also have an opacity that multiplies the canvas alpha during render, which is how fade transitions work.
Events and propagation
When a scene receives an input event, Scene.handleEvent(event) forwards it to game objects in reverse order (last added gets first chance to handle it).
If any handler sets event.stopPropagation = true, the scene stops delivering the event to remaining objects.
This works together with the game-level dispatch order (top-most active scene first) to make overlays/menus easy to implement.
Camera offset (scene offset)
Scenes have an offset vector that acts like a simple “camera” shift:
import { Vector } from "sliver-engine";
scene.setOffset(new Vector(-100, 0)); // pan camera right by 100px
Why this works:
GameObject.getPosition()includes the scene offset, so most objects automatically render with the camera shift.- Some input helpers/decorators convert mouse coordinates to “scene space” by subtracting the offset, so hit tests line up while the camera moves.
This same offset is also used by slide transitions.
Scene stack and scene switching
SceneManager maintains:
- a
currentScene - a list of
activeScenes(a stack)
currentScene is the “main” current scene. Pushing an overlay scene does not necessarily replace the current scene; it just adds a scene on top of the active stack.
Active scenes:
- are all ticked each tick
- are all rendered each frame (in order)
- receive input with the top-most scene first (reverse order), so overlays can consume events
You’ll usually switch scenes through the GameContext helpers:
const ctx = game.getContext();
ctx.setCurrentScene("main"); // replace active stack with [main]
ctx.pushScene("pause"); // overlay pause on top
ctx.popScene(); // remove top-most active scene
Transitions (fade / slide / flash)
Transitions are handled by SceneManager.transitionToScene(...) (also exposed on GameContext).
Built-in transitions are exported from sliver-engine:
import {
fadeTransition,
slideReplace,
slidePush,
slidePop,
colorFlash,
} from "sliver-engine";
await game.getContext().transitionToScene("main", fadeTransition(450), "replace");
await game.getContext().transitionToScene("pause", slidePush("down"), "push");
await game.getContext().transitionToScene("main", slidePop("up"), "replace");
await game.getContext().transitionToScene("main", colorFlash("white", 750), "replace");
Transition details worth knowing:
- Only one transition can run at a time; starting a new one while another is active rejects with an error.
- A transition can control
opacity,offset, andoverlayon the involved scenes. incomingOnTopcontrols whether the incoming scene renders above or below the outgoing scene during the animation (used by pop-like transitions).
Custom transitions
If you want something custom, build one with createSceneTransition:
import { createSceneTransition } from "sliver-engine";
const zoomFade = createSceneTransition(
(t, { from, to }) => {
from?.setOpacity(1 - t);
to.setOpacity(t);
// you can also animate offsets/overlays here
},
{ duration: 600 }
);