Skip to content

Brainstorm: Scene layer — components, lifecycle, and dev tools #163

@devallibus

Description

@devallibus

Motivation

Every gametau game currently reinvents render loops, object management, and scene setup boilerplate. A first-party scene layer would eliminate this duplication and unlock dev tools (inspector, scene tree) that read component metadata.


Feature Tiers

Tier 1: High Value, Natural Fit

1. Component System — The biggest gap. Base class with lifecycle hooks (awake, start, update, afterUpdate, dispose) attached to scene nodes.

2. SceneController — Boilerplate every Three.js game writes (renderer, camera, clock, deltaTime, pause/resume).

3. Property Decorators@prop.num(), .color(), .select(), .vec3() etc. Enables inspector, serialization, and hot-tweaking without a visual editor.

Tier 2: Medium Value, Build After Tier 1

4. Inspector Panel — In-game devtools reading property metadata → editable UI. Framework-specific bindings (SolidJS, React).

5. Scene Serialization — JSON save/load of scene graph + component props. Needs component system first.

6. Input Manager — Action-mapped input (action → key/button). Lower priority.

Tier 3: Skip

Visual scripting, asset marketplace, standalone editor app, prefab system.


Architecture: Renderer-Agnostic Core + Three.js Bindings

The key insight: lifecycle hooks, property decorators, and the component registry don't care about the renderer. Only the "what does a component attach to" binding is renderer-specific.

Package Structure

gametau/
  packages/
    webtau/              (existing) — command bridge, invoke, providers
    webtau-vite/         (existing) — WASM build plugin
    webtau-scene/        (NEW) — renderer-agnostic core
    webtau-three/        (NEW) — Three.js SceneController + bindings
    webtau-devtools/     (NEW, later) — inspector, scene tree

Layer Split

webtau-scene (renderer-agnostic core):
  ├── Component          — base class with lifecycle hooks
  ├── prop decorators    — @prop.num(), .color(), .select(), .vec3(), etc.
  ├── ComponentRegistry  — register/lookup components by name
  ├── SceneNode          — minimal interface a component attaches to
  └── GameLoop           — requestAnimationFrame, deltaTime, pause/resume

webtau-three (Three.js bindings):
  ├── ThreeSceneController — wraps THREE.WebGLRenderer/Scene/Camera/Clock
  ├── ThreeComponent       — Component subclass where node is THREE.Object3D
  ├── createScene()        — factory: container → controller + render loop
  └── ThreeSceneNode       — implements SceneNode for THREE.Object3D

webtau-pixi (future, hypothetical):
  ├── PixiSceneController  — wraps PIXI.Application/Stage
  ├── PixiComponent        — Component where node is PIXI.Container
  └── createPixiScene()    — factory

API Design

Core (webtau-scene) — no Three.js import

import { Component, prop, registerComponent } from "webtau-scene";

class Orbiter extends Component {
  @prop.num({ min: 0, max: 100, label: "Speed" })
  speed = 1.0;

  @prop.color()
  tint = 0xff0000;

  @prop.select(["cw", "ccw"])
  direction: "cw" | "ccw" = "cw";

  // Lifecycle (called by the game loop, never manually)
  awake() {}              // before first frame
  start() {}              // after assets ready
  update(dt: number) {}   // every frame
  afterUpdate() {}        // post-update
  dispose() {}            // on removal

  // this.node — typed by the renderer binding (Object3D, Container, etc.)
}
registerComponent(Orbiter);

Property decorator metadata (renderer-agnostic)

const meta = Component.getMeta(Orbiter);
// → [{ key: "speed", type: "num", min: 0, max: 100, label: "Speed" },
//    { key: "tint", type: "color" },
//    { key: "direction", type: "select", options: ["cw", "ccw"] }]

Three.js binding (webtau-three)

import { createScene, ThreeComponent } from "webtau-three";

class OrbitalBody extends ThreeComponent {
  @prop.num() radius = 1.0;

  update(dt: number) {
    this.object3d.rotation.y += dt * this.radius;
  }
}

const ctrl = createScene({
  container: document.getElementById("canvas")!,
  antialias: true,
  pixelRatio: window.devicePixelRatio,
});

// ctrl.scene, ctrl.camera, ctrl.renderer, ctrl.clock
// ctrl.deltaTime, ctrl.isPaused
// ctrl.pause(), ctrl.resume(), ctrl.setFullscreen()
// ctrl.renderFunc — override for post-processing
// ctrl.start() — begins RAF loop, ticks all components

Component attachment

const mesh = new THREE.Mesh(geometry, material);
ctrl.scene.add(mesh);

const orbiter = ctrl.addComponent(mesh, OrbitalBody);
orbiter.radius = 2.5;

// Query
const body = ThreeComponent.get(mesh, OrbitalBody);
const all = ThreeComponent.getAll(OrbitalBody);

// Remove
ctrl.removeComponent(orbiter); // calls dispose()

Lifecycle Order (per frame)

RAF tick:
  1. ctrl.clock.getDelta() → deltaTime
  2. For each component (awake-pending): component.awake()
  3. For each component (start-pending): component.start()
  4. For each component: component.beforeUpdate?.(dt)
  5. For each component: component.update(dt)
  6. For each component: component.afterUpdate?.()
  7. ctrl.renderFunc(ctrl.renderer, ctrl.scene, ctrl.camera)

Design Principles

Aspect Approach
Ownership Open source, in gametau monorepo
Editor In-game dev overlay (opt-in, later)
Renderer Core is agnostic; Three.js/PixiJS via bindings
Build Vite (webtau-vite compatible)
Backend Full Rust via #[webtau::command]
Desktop Tauri + Electrobun + web
Scene format JSON (portable)
UI framework SolidJS/React bindings (webtau-devtools-*)

Roadmap

Phase 1: webtau-scene + webtau-three

  • Component base class with lifecycle
  • Property decorators with metadata registry
  • ComponentRegistry (register, lookup, getAll)
  • ThreeSceneController (renderer, scene, camera, clock, deltaTime)
  • ThreeComponent (Component with object3d typed binding)
  • createScene() factory
  • Game loop with lifecycle dispatch

Phase 2: webtau-devtools (later)

  • Inspector panel reading prop metadata → editable UI
  • Scene tree viewer
  • SolidJS bindings (webtau-devtools-solid)

Phase 3: Extras (community / as-needed)

  • Input manager with action mapping
  • Scene serialization (JSON save/load)
  • PixiJS bindings (webtau-pixi)

Validation Criteria

  • webtau-scene has zero Three.js imports (renderer-agnostic)
  • webtau-three's peer dependency on three is the only renderer coupling
  • Tree-shaking works: unused webtau-three doesn't bloat a PixiJS project
  • A real game's render loop can be ported to createScene() + ThreeComponent with identical behavior

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions