I organize my scenes using a ScreenManager. (Named “screen” to distinguish from scene.)
Interface Screen brings lifecycle methods activate and deactivate.
export interface Screen {
activate(): void;
deactivate(): void;
render(): void;
dispose(): void;
}
export class ScreenManager {
private readonly screens: Screen[] = [];
constructor() {
}
dispose() {
this.clearScreens();
}
getTopScreen() {
return this.screens.length > 0 ? this.screens[this.screens.length - 1] : null;
}
pushScreen(screen: Screen) {
const topScreen = this.getTopScreen();
if (topScreen !== null) {
topScreen.deactivate();
}
this.screens.push(screen);
screen.activate();
}
popScreen(numberOfScreens = 1) {
if (numberOfScreens < 1) {
return;
}
let topScreen = this.getTopScreen();
if (topScreen === null) {
return;
}
topScreen.deactivate();
while (numberOfScreens > 0) {
topScreen = this.getTopScreen();
if (topScreen === null) {
return;
}
topScreen.dispose();
this.screens.pop();
numberOfScreens--;
}
topScreen = this.getTopScreen();
if (topScreen === null) {
return;
}
topScreen.activate();
}
private clearScreens() {
this.popScreen(this.screens.length);
}
}
The main class App utilizes the ScreenManager:
export class App {
readonly engine: Engine;
readonly screenManager = new ScreenManager();
private readonly resizeHandler = () => this.resize();
private readonly renderHandler = () => this.render();
private disposed = false;
constructor(
readonly canvasElement: HTMLCanvasElement
) {
this.engine = new Engine(canvasElement, true);
this.load();
}
dispose() {
this.disposed = true;
this.stop();
this.screenManager.dispose();
this.engine.dispose();
}
private async load() {
// Load stuff...
this.screenManager.pushScreen(new MyFirstSceen(this));
}
run() {
window.addEventListener('resize', this.resizeHandler);
this.engine.runRenderLoop(this.renderHandler);
}
stop() {
window.removeEventListener('resize', this.resizeHandler);
this.engine.stopRenderLoop(this.renderHandler);
}
private resize() {
this.engine.resize();
}
private render() {
const topScreen = this.screenManager.getTopScreen();
if (topScreen === null) {
return;
}
topScreen.render();
}
}
You then can implement your “screens”:
export class MyFirstScreen implements Screen {
readonly engine: Engine;
readonly scene: Scene;
constructor(
readonly app: App
) {
this.engine = this.app.engine;
this.scene = new Scene(this.engine);
// Setup scene...
}
activate() {
this.scene.attachControl();
this.engine.onResizeObservable.add(this.resize, undefined, undefined, this);
this.resize();
}
deactivate() {
// this.debugLayerManager.hide();
this.engine.onResizeObservable.removeCallback(this.resize, this);
this.scene.detachControl();
}
resize() {
}
render() {
this.scene.render();
}
dispose() {
this.scene.dispose();
}
private pushSecondScreen() {
this.app.screenManager.pushScreen(new MySecondScreen(this.app));
}
}