World
The World is the central orchestrator of the engine. It wires together the SceneManager, Renderer, Input, and Audio systems and drives the main connect → update → render loop.
Every project creates exactly one World. It is passed to every Actor at construction time and is available throughout the component tree via actor.world.
Creating a game instance
import {
SceneEngine,
ThreeRenderer,
World
} from "@jolly-pixel/engine";
const canvas = document.querySelector("canvas")!;
const sceneManager = new SceneManager();
const renderer = new ThreeRenderer(canvas, { sceneManager });
const game = new World(renderer, { sceneManager });The constructor accepts a Renderer and a WorldOptions object:
interface WorldOptions {
/** The scene that manages actors and components. */
sceneManager: SceneContract;
/** Input system for keyboard, mouse, gamepad, etc. @default auto-created from canvas */
input?: Input;
/** Global audio manager. @default new GlobalAudio() */
audio?: GlobalAudio;
/** Enable the exit mechanism on the input system. @default false */
enableOnExit?: boolean;
/** Abstraction over `window` (useful for testing). @default BrowserWindowAdapter */
windowAdapter?: WindowAdapter;
/** Abstraction over global references (useful for testing). @default BrowserGlobalsAdapter */
globalsAdapter?: GlobalsAdapter;
}Loading manager
Three.js assets (models, textures, audio) can share a single THREE.LoadingManager via the game instance:
const manager = new THREE.LoadingManager();
manager.onProgress = (_url, loaded, total) => {
console.log(`${loaded}/${total}`);
};
game.setLoadingManager(manager);The loading manager is available from anywhere as actor.world.loadingManager.
Connect and disconnect
connect() starts the game by wiring up input listeners, the window resize handler, and awakening the scene:
game.connect();Internally this:
- Connects the Input system.
- Registers the renderer's
resizecallback on the window adapter. - Calls
scene.awake(), which awakens all existing actors and emits the"awake"event.
disconnect() tears down the listeners:
game.disconnect();Game loop
The game loop is driven by a FixedTimeStep which separates deterministic logic from rendering:
const fixedTimeStep = new FixedTimeStep();
fixedTimeStep.start();
function loop() {
game.beginFrame();
fixedTimeStep.tick({
fixedUpdate: (fixedDelta) => {
game.fixedUpdate(fixedDelta / 1000);
},
update: (_interpolation, delta) => {
game.update(delta / 1000);
game.render();
}
});
const exited = game.endFrame();
if (exited) { /* stop loop */ }
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);beginFrame()
Called once at the start of each animation frame:
- Updates the Input system.
- Calls
sceneManager.beginFrame()— snapshots the actor tree and starts pending components. The snapshot is reused by allfixedUpdateandupdatecalls within the same frame.
fixedUpdate(deltaTime)
Runs deterministic logic at a fixed rate (0 to N times per frame):
- Calls
sceneManager.fixedUpdate(deltaTime)— runsactor.fixedUpdate(deltaTime)on each cached actor.
update(deltaTime)
Runs variable-rate logic once per rendered frame:
- Calls
sceneManager.update(deltaTime)— runsactor.update(deltaTime)on each cached actor.
endFrame(): boolean
Called once at the end of each animation frame:
- Calls
sceneManager.endFrame()— destroys pending components and actors. - If the input system signals an exit, clears the renderer and returns
true. Otherwise returnsfalse.
render()
Delegates to renderer.draw(), which resizes if needed, clears the frame buffer, and renders the scene through all active cameras.
Accessing subsystems
All subsystems are available as public properties, making them accessible from any actor or component:
// From inside a Behavior
const { input, sceneManager, audio, renderer } = this.actor.world;
if (input.isKeyDown("Space")) {
audio.play("jump");
}See also
- SceneManager — actor tree, lifecycle, and destruction
- Renderer — rendering pipeline
- Input — input handling
- Actor — the engine's core entity