Skip to content

Getting Started ​

Small World is a lightweight, high-performance, modular 3D game engine for the web, built with TypeScript.

Installation ​

The engine is not yet published to the npm registry. Clone the repository and install dependencies locally, then import the engine package (@small-world/engine) from within the workspace:

bash
git clone https://github.com/rottensteiner-stefan/small-world.git
cd small-world
npm install

Basic setup ​

The engine uses a strategy-based lifecycle. You derive from SmallWorld (or from AbstractShowcase, which adds a few demo/debugging conveniences on top of SmallWorld) and override the setupScene and update lifecycle methods.

1. Basic showcase implementation ​

Create a file called app.ts to bootstrap the engine:

typescript
import {
  AbstractShowcase,
  Color,
  Cube,
  Object3D,
  RendererType,
  StandardMaterial,
} from "@small-world/engine";

class MyFirstWorld extends AbstractShowcase {
  protected override async setupScene(): Promise<void> {
    // 1. Create a green PBR cube
    const cubeObj = new Object3D("RotatingCube");
    cubeObj.geometry = new Cube({ size: 1.5 }).getGeometryData();
    cubeObj.material = new StandardMaterial({
      color: Color.GREEN,
      metallic: 0.5,
      roughness: 0.3,
    });
    cubeObj.position.set(0, 1.0, 0);

    // 2. Add it to the scene
    this.scene.add(cubeObj);

    // 3. Move the camera back to see the scene
    this.camera.position.set(0, 3.0, 6.0);
    this.camera.target.set(0, 1.0, 0);
  }

  protected override update(deltaTime: number): void {
    super.update(deltaTime);

    // Rotate the cube object
    const cube = this.scene.getObjectByName("RotatingCube");
    if (cube) {
      cube.rotation.y += 1.0 * deltaTime;
    }
  }
}

// Instantiate and start
const app = new MyFirstWorld({
  rendererType: RendererType.BEST,
});

app.start().then(() => {
  console.log("Small World initialized!");
});

2. SPA & framework integration (React / Vue / Angular) ​

When Small World is embedded in a single-page application (SPA), the browser doesn't automatically refresh on route changes. To prevent memory leaks or multiple render loops running concurrently in the background, the engine must be cleanly destroyed when its host component unmounts.

Simply call the destroy() method. This immediately stops the requestAnimationFrame loop, removes all global window event listeners, and frees WebGPU/WebGL memory.

tsx
import { useEffect, useRef } from "react";
import { MyFirstWorld } from "./MyFirstWorld";

export function GameComponent() {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    let app: MyFirstWorld | null = null;
    
    if (canvasRef.current) {
      app = new MyFirstWorld({
        canvasId: "SmallWorldCanvas"
      });
      app.start();
    }

    return () => {
      // Fully clean up the engine when the React component unmounts!
      if (app) {
        app.destroy();
      }
    };
  }, []);

  return <canvas id="SmallWorldCanvas" ref={canvasRef} />;
}

Note: The engine already has an automatic safety net built in. If it detects that its canvas element was forcibly removed from the DOM by a framework without destroy() being called explicitly, it catches that and safely destroys itself!

Veröffentlicht unter der MIT-Lizenz.