ngx-three-engine
v21.5.1
Published
A simple WebGL/WebGPU-based game framework with Actor-Component logic on top of threejs, with cannon-es for physics.
Maintainers
Readme
ngx-three-engine
A WebGL/WebGPU game framework for Angular, built on top of Three.js with Cannon-ES physics. Provides an Unreal Engine-inspired Actor-Component architecture, enhanced input system, ECS, pluggable rendering strategies, and automatic GPU-based graphics scalability — all designed for Angular's zoneless signals architecture.
Features
- Actor-Component system — Extend
Actor(which extends Three.jsObject3D) and attach reusableActorComponentbehaviors - Pawn & Controller pattern — Possess pawns with player or AI controllers, just like Unreal Engine
- Enhanced Input System — Abstract input actions from physical keys with
InputAction,InputMappingContext, andPlayerInput - ECS (Entity-Component-System) — Priority-sorted systems with a built-in
TickSystemandPhysicsSystem - Physics — Cannon-ES integration with auto-synced rigid bodies, debug wireframe (F3), and character capsule colliders
- Pluggable rendering — Swap between
DeferredRenderStrategy(WebGPU pipeline) andPathTracingStrategy(GPU path tracer) at runtime - Auto GPU scalability — Benchmarks the GPU on first run and applies Low/Mid/High presets (shadow quality, anti-aliasing, pixel ratio, effects)
- Object selection — Raycast-based picking with multi-select, hover events, and outline effects
- Editor Mode & State — Game mode + state pattern for organizing initialization and gameplay logic
- Settings panel — Built-in Angular component for checkboxes, dropdowns, and sliders
- Built-in pawns —
OrbitPawn(3D orbit controls),OrbitPawn2D(top-down pan/zoom),DefaultPawn(fly controls),Character(physics-based capsule)
Installation
npm install ngx-three-enginePeer dependencies
ngx-three-engine only keeps the Angular framework packages and rxjs as peer dependencies, because those must match the host Angular app.
| Package | Version |
| ---------------------- | -------- |
| @angular/core | ^21.2.8 |
| @angular/common | ^21.2.8 |
| rxjs | ~7.8.2 |
The engine-specific packages (three, @types/three, cannon-es, @pmndrs/detect-gpu, and three-gpu-pathtracer) are installed automatically with ngx-three-engine.
If your own app imports those packages directly, add them to your app as direct dependencies too. For example, the code samples below import from three, so most projects should also run:
npm install three
npm install -D @types/threeSetup
1. Initialize the InjectorService
ngx-three-engine uses Angular's dependency injection internally. You must provide the injector at app startup:
// app.config.ts
import { ApplicationConfig, inject, Injector, provideAppInitializer } from "@angular/core";
import { InjectorService } from "ngx-three-engine";
export const appConfig: ApplicationConfig = {
providers: [
provideAppInitializer(() => {
const injector = inject(Injector);
InjectorService.setInjector(injector);
return undefined;
}),
// ... other providers
],
};2. Create a World
A World is your scene container — it manages the Three.js scene, camera, lighting, actors, physics, and render pipeline.
// my-world.ts
import { World } from "ngx-three-engine";
import { AmbientLight, DirectionalLight, Mesh, BoxGeometry, MeshStandardMaterial, PlaneGeometry } from "three";
export class MyWorld extends World {
public override init(engineWrapper, rendererCanvas, renderPipeline, renderer, onWindowResize, onPreRender, onPostRender, useWebGlFallback): void {
super.init(engineWrapper, rendererCanvas, renderPipeline, renderer, onWindowResize, onPreRender, onPostRender, useWebGlFallback);
const scene = this.getScene();
// Add lights
scene.add(new AmbientLight(0x404040, 2));
const sun = new DirectionalLight(0xffffff, 3);
sun.position.set(5, 10, 7);
sun.castShadow = true;
scene.add(sun);
// Add a ground plane
const ground = new Mesh(new PlaneGeometry(20, 20), new MeshStandardMaterial({ color: 0x808080 }));
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Add a cube actor
const cube = new Mesh(new BoxGeometry(1, 1, 1), new MeshStandardMaterial({ color: 0xe74c3c }));
cube.position.set(0, 0.5, 0);
cube.castShadow = true;
scene.add(cube);
// Position the camera
const camera = this.getViewportCamera();
camera.position.set(5, 4, 7);
camera.lookAt(0, 1, 0);
}
public override tick(deltaTime: number): void {
super.tick(deltaTime);
// Per-frame game logic goes here
}
}3. Create a Mode, State, and Controller
The Mode ties together a State, Controller, and default Pawn. It handles instantiation and possession automatically.
// my-mode.ts
import { GameModeBase, OrbitPawn } from "ngx-three-engine";
import { MyState } from "./my-state";
import { MyController } from "./my-controller";
export class MyMode extends GameModeBase {
constructor() {
super(MyState, MyController, OrbitPawn);
this.name = "My Game Mode";
}
}// my-state.ts
import { GameStateBase } from "ngx-three-engine";
export class MyState extends GameStateBase {
// Add game state (score, level, etc.)
}// my-controller.ts
import { PlayerController } from "ngx-three-engine";
export class MyController extends PlayerController {
// Add input mapping contexts, handle input actions
}4. Add the engine component
// app.ts
import { Component, signal } from "@angular/core";
import { ThreejsEngineComponent } from "ngx-three-engine";
import { MyMode } from "./my-mode";
import { MyWorld } from "./my-world";
@Component({
selector: "app-root",
imports: [ThreejsEngineComponent],
template: `<threejs-engine [world]="world()" />`,
})
export class App {
protected readonly world = signal(new MyWorld(new MyMode()));
}That's it — the engine will auto-detect your GPU, pick a graphics preset, create a WebGPU renderer (with WebGL fallback), and start rendering.
Changing the Pawn
The default OrbitPawn gives you orbit camera controls. Switch to a different pawn by passing it to your Mode:
import { GameModeBase, DefaultPawn, OrbitPawn, OrbitPawn2D } from "ngx-three-engine";
// 3D orbit camera (default)
super(MyState, MyController, OrbitPawn);
// Free-fly camera
super(MyState, MyController, DefaultPawn);
// 2D top-down orthographic camera
super(MyState, MyController, OrbitPawn2D);Or create a custom pawn:
import { Pawn } from "ngx-three-engine";
export class MyPawn extends Pawn {
public override awake(): void {
this.tickComponent.activate();
// Setup camera, mesh, etc.
}
public override tick(deltaTime: number): void {
// Per-frame pawn logic
}
public override possessed(controller: Controller): void {
super.possessed(controller);
// Controller just took control of this pawn
}
}Enhanced Input System
Inspired by Unreal Engine 5's Enhanced Input, abstracting actions from physical keys:
import { InputAction, InputMappingContext, PlayerController, Pawn } from "ngx-three-engine";
// 1. Define actions
const IA_Jump = new InputAction("IA_Jump"); // boolean (pressed/released)
const IA_Move = new InputAction("IA_Move", "axis2d"); // { x, y } vector
const IA_Fire = new InputAction("IA_Fire");
// 2. Map actions to keys
const gameplayContext = new InputMappingContext("Gameplay");
gameplayContext
.mapAction(IA_Jump, "Space")
.mapAction(IA_Move, "KeyW") // Uses KeyboardEvent.code
.mapAction(IA_Fire, "Mouse:Left");
// 3. Bind in your controller
export class MyController extends PlayerController {
public override possessed(pawn: Pawn): void {
super.possessed(pawn);
this.addMappingContext(gameplayContext);
this.playerInput.onInputAction.subscribe(({ action, event, value }) => {
// event: "started" | "triggered" | "completed"
if (action === IA_Jump && event === "started") {
// Jump!
}
});
}
}Actors & Components
Every object in your world is an Actor (extends Three.js Object3D). Attach ActorComponent instances for reusable behavior:
import { Actor, TickComponent } from "ngx-three-engine";
import { Mesh, SphereGeometry, MeshStandardMaterial } from "three";
export class EnemyActor extends Actor {
public override awake(): void {
this.tickComponent.activate();
const mesh = new Mesh(new SphereGeometry(0.5), new MeshStandardMaterial({ color: 0xff0000 }));
this.add(mesh);
}
public override tick(deltaTime: number): void {
this.rotation.y += deltaTime;
}
}
// Add to your world:
const enemy = new EnemyActor();
this.addActor(enemy);ECS Systems
Register custom systems with priority ordering:
import { System, World } from "ngx-three-engine";
export class ScoreSystem extends System {
constructor() {
super(50); // priority — lower runs first (TickSystem = 0, PhysicsSystem = 100)
}
public override execute(deltaTime: number, world: World): void {
// Runs every frame
}
}
// Register in your world:
this.systemManager.registerSystem(new ScoreSystem());Physics
Cannon-ES is integrated via PhysicsSystem. The Character pawn provides a physics-based capsule collider out of the box. Press F3 at runtime to toggle the physics debug wireframe.
Graphics Scalability
On first launch, the engine benchmarks the GPU and selects a preset:
| Preset | Anti-Aliasing | Shadows | Shadow Map | Pixel Ratio | Effects | | ------ | ------------- | ---------------- | ---------- | ----------- | ----------- | | Low | FXAA | None | 512px | 1x | — | | Mid | SMAA | PCF Soft Shadows | 2048px | 2x | GTAO | | High | TAA | VSM | 4096px | 3x | SSGI |
The selected preset is saved to localStorage and reused on subsequent visits.
API Overview
| Concept | Base Class / Service | Purpose |
| -------------- | ----------------------------------- | ------------------------------------------------- |
| Engine | ThreejsEngineComponent | Angular component — drop into your template |
| World | World | Scene, camera, systems, actors, physics |
| Actor | Actor | Base game object (extends Object3D) |
| Component | ActorComponent | Reusable actor behavior |
| Pawn | Pawn, OrbitPawn, DefaultPawn | Controllable actor with camera |
| Controller | PlayerController, AIController | Possesses and controls a pawn |
| Mode | GameModeBase | Ties together state + controller + pawn |
| State | GameStateBase | Holds game state (score, level, etc.) |
| Input | InputAction, InputMappingContext| Abstract/bind input |
| System | System, SystemManager | ECS frame-processing systems |
| Selection | ObjectSelector, SelectionComponent | Raycast picking + hover/select events |
| Rendering | RendererManager, RenderStrategy | Pluggable render backends |
| Scalability | GraphicsScalabilityConfigsManager | Auto GPU detection + quality presets |
| Settings | SettingsService, SettingsPanelComponent | Runtime settings UI |
| Injector | InjectorService | Angular DI bridge — must be initialized at startup |
License
MIT
