Skip to content
bg2 engine

AppController

The AppController class defines the application lifecycle and input-event interface of the TypeScript runtime. It is the main extension point used to implement application behavior.

The default implementation does not provide any rendering or interaction logic. Its purpose is to declare the lifecycle and input callbacks that the application can override. In practice, applications typically define a subclass of AppController and implement only the methods they need.

An AppController instance is associated with a MainLoop, which injects the runtime context and forwards lifecycle and input events to the controller.

AppController is responsible for handling:

  • Initialization of application resources
  • Resize notifications
  • Per-frame updates
  • Rendering
  • Destruction and cleanup
  • Keyboard, mouse, and touch input events

This design keeps application logic separate from the main loop and rendering surface.

Creates a new AppController instance.

The controller is initialized without an associated main loop. The MainLoop assigns itself to the controller during its own construction.


Gets or sets the MainLoop associated with this controller.

In normal usage, this property is assigned automatically by MainLoop, so application code usually reads it but does not set it manually.

Read-only shortcut to the canvas associated with the current main loop.

Returns this._mainLoop?.canvas.

Read-only shortcut to the renderer associated with the current canvas.

Returns this._mainLoop?.canvas?.renderer.

viewport: { width: number; height: number; aspectRatio: number }

Section titled “viewport: { width: number; height: number; aspectRatio: number }”

Read-only access to the current viewport.

If the controller is not yet attached to a canvas, this property returns:

{ width: 0, height: 0, aspectRatio: 0 }

These methods define the main application lifecycle. They are intended to be overridden in subclasses.

Called during application startup.

Override this method to initialize resources, create scene objects, allocate GPU resources, or perform any other startup logic required by the application.


reshape(width: number, height: number): void

Section titled “reshape(width: number, height: number): void”

Called when the rendering surface changes size.

Override this method to update the viewport, projection matrices, render targets, or any size-dependent resources.

  • width: New viewport width
  • height: New viewport height

Called once per frame update.

Override this method to implement time-dependent application logic such as animations, physics, state updates, or camera movement.

  • delta: Elapsed time since the previous frame

Called when the application must render a frame.

Override this method to issue rendering commands.


Called when the application exits.

Override this method to release resources, detach references, or perform any necessary cleanup.


These methods are invoked by the runtime when input events occur. All of them are optional and may be overridden as needed.

Called when a key is pressed.

Called when a key is released.

Called when a mouse button is released.

Called when a mouse button is pressed.

Called when the mouse moves.

Called when the mouse leaves the canvas area.

Called when the mouse moves while dragging.

This is a common place to request a redraw in manual update mode.

Called when the mouse wheel is used.

Applications can stop event propagation if needed.

Called when a touch interaction begins.

Called when a touch interaction moves.

Called when a touch interaction ends.


A typical application does not implement every method. Instead, it subclasses AppController and overrides only the callbacks required for its behavior.

The most commonly implemented methods are:

  • init()
  • reshape()
  • frame()
  • display()

Input methods are added depending on the interaction model of the application.

import MainLoop, { FrameUpdate } from "bg2e-js/ts/app/MainLoop.ts";
import Canvas from "bg2e-js/ts/app/Canvas.ts";
import AppController from "bg2e-js/ts/app/AppController.ts";
import WebGLRenderer from "bg2e-js/ts/render/webgl/Renderer.ts";
import Bg2KeyboardEvent from "bg2e-js/ts/app/Bg2KeyboardEvent.ts";
import Bg2MouseEvent from "bg2e-js/ts/app/Bg2MouseEvent.ts";
import Bg2TouchEvent from "bg2e-js/ts/app/Bg2TouchEvent.ts";
class MyAppController extends AppController {
async init() {
console.log("init");
}
reshape(width: number, height: number) {
console.log(`reshape - width:${width}, height:${height}`);
const { gl } = this.renderer as WebGLRenderer;
gl.viewport(0, 0, width, height);
}
async frame(delta: number) {
console.log(`frame - elapsed time: ${delta}`);
}
display() {
const { gl } = this.renderer as WebGLRenderer;
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.DEPTH_BUFFER_BIT | gl.COLOR_BUFFER_BIT);
console.log("display");
}
keyDown(evt: Bg2KeyboardEvent) {
console.log(`keyDown - key: ${evt.key}`);
}
keyUp(evt: Bg2KeyboardEvent) {
console.log(`keyUp - key: ${evt.key}`);
}
mouseUp(evt: Bg2MouseEvent) {
console.log(`mouseUp - mouse location: ${evt.x}, ${evt.y}`);
}
mouseDown(evt: Bg2MouseEvent) {
console.log(`mouseDown - mouse location: ${evt.x}, ${evt.y}`);
}
mouseMove(evt: Bg2MouseEvent) {
console.log(`mouseMove - mouse location: ${evt.x}, ${evt.y}`);
}
mouseOut(evt: Bg2MouseEvent) {
console.log(`mouseOut - mouse location: ${evt.x}, ${evt.y}`);
}
mouseDrag(evt: Bg2MouseEvent) {
console.log(`mouseDrag - mouse location: ${evt.x}, ${evt.y}`);
this.mainLoop.postRedisplay();
}
mouseWheel(evt: Bg2MouseEvent) {
console.log(`mouseWheel - mouse location: ${evt.x}, ${evt.y}, delta: ${evt.delta}`);
evt.stopPropagation();
}
touchStart(evt: Bg2TouchEvent) {
console.log(`touchStart`);
}
touchMove(evt: Bg2TouchEvent) {
console.log(`touchMove`);
}
touchEnd(evt: Bg2TouchEvent) {
console.log(`touchEnd`);
}
}
window.onload = async () => {
const canvasElem = document.getElementById('gl-canvas') as HTMLCanvasElement;
if (!canvasElem) {
console.error("Cannot find canvas element with id 'gl-canvas'");
return;
}
const canvas = new Canvas(canvasElem, new WebGLRenderer());
const appController = new MyAppController();
const mainLoop = new MainLoop(canvas, appController);
mainLoop.updateMode = FrameUpdate.MANUAL;
await mainLoop.run();
};