Zielgruppe: Einsteiger in HTML/JavaScript — schrittweise bis zur Engine-Architektur

GitHub     ← Zurück

Teil 1 — 3D Function Plotter

1. Was ist der 3D Function Plotter?

Der 3D Function Plotter ist eine reine Browser-Anwendung — eine einzelne HTML-Datei, die mathematische Funktionen als interaktive 3D-Oberflächen darstellt. Keine Installation, kein Server, kein Backend benötigt.

Du gibst eine Funktion z = f(x, y) ein — der Plotter berechnet alle z-Werte für ein x/y-Gitter und zeigt das Ergebnis als drehbare, färbige 3D-Fläche an.

VersionFeatures
Version 1 BasisBeispiel-Funktionen, eigener Formel-Editor, Auflösungs-Slider, Live-Update
Version 2 Animation+ Animationsparameter t, Start/Stop-Buttons, zeitabhängige Funktionen
Version 3 Export+ PNG-Screenshot, JSON-Datendump, TXT-Funktionsexport
Version 4 Physik+ Boot-System mit Auftrieb, Hydrodynamik, Segel- und Motorphysik
Version 5 Multiplayer+ WebSocket-Multiplayer mit Client/Server, Prediction & Replication
Version 6 WebGPU Engine+ Vollständige WebGPU-Engine mit Renderer, Editor, Shader-Compiler, Gizmos

2. Was ist Plotly.js?

Plotly.js ist eine Open-Source-JavaScript-Bibliothek für interaktive Diagramme und 3D-Visualisierungen. Sie wird über ein einzelnes <script>-Tag eingebunden.

<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>

<div id="plot"></div>
<script>
    Plotly.newPlot('plot', data, layout);
</script>

3. Starten — direkt im Browser

  1. Projekt-Ordner herunterladen (oder klonen)
  2. Version-1/index.html im Browser öffnen
  3. Fertig — der Plotter läuft sofort
Kein lokaler Server nötig! Eine HTML-Datei mit externem CDN-Script läuft direkt per file://. Erst wenn man eigene Dateien per fetch() oder WebGPU verwendet, wird ein lokaler Server benötigt (z.B. npx serve oder VS Code Live Server).

4. Version 1: Grundfunktionen

ElementFunktion
Beispiel-Funktion (ComboBox)Vorausgewählte Beispiele: Wellen, Sättel, Kegel, Spiralen
Formel-EingabeEigene Funktion eingeben, z.B. Math.sin(x)*Math.cos(y)
Auflösungs-SliderFeinheit des Gitters — höher = schöner, aber langsamer
Live-UpdatePlot aktualisiert sich automatisch beim Ändern der Formel
// Klassische Beispiel-Funktionen:
Math.sin(x) * Math.cos(y)           // Welle
Math.sin(Math.sqrt(x*x + y*y))      // Ringwelle
x*x - y*y                           // Sattelfläche
Math.exp(-(x*x + y*y))              // Gauß-Glocke

3D-Plot-Steuerung

5. Version 2: Animation mit Parameter t

Version 2 fügt einen Zeitparameter t hinzu. Die Funktion kann nun von t abhängen — der Plot animiert sich automatisch.

// t = Zeit (läuft automatisch hoch)
Math.sin(x + t) * Math.cos(y + t)           // Pulsierende Welle
Math.sin(Math.sqrt(x*x + y*y) - t)          // Rotierende Ringwelle
Math.sin(x) * Math.sin(y) * Math.cos(t)     // Stehende Welle
Wie läuft die Animation? setInterval() ruft die Plot-Funktion regelmäßig auf, erhöht t und aktualisiert mit Plotly.react() (effizienter als newPlot() für Updates).

6. Version 3: Export

ExportFormatVerwendung
ScreenshotPNGWord, PowerPoint, Präsentationen
DatendumpJSON (x/y/z)Weiterverarbeitung in Python, MATLAB
FormelTXTFunktion speichern und später laden
// PNG-Export:
Plotly.downloadImage('plot', { format: 'png', filename: '3d-plot' });

// JSON-Export:
const blob = new Blob([JSON.stringify({x, y, z})], {type: 'application/json'});
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'plot-data.json';
a.click();

7. Eigene Funktionen eingeben

Im Formel-Feld können alle JavaScript-Math-Funktionen verwendet werden:

Math.sin(x)    Math.cos(y)    Math.tan(x)
Math.sqrt(x*x+y*y)            Math.pow(x, 3)
Math.exp(-x)   Math.log(Math.abs(x))    Math.PI

// Kreative Beispiele:
Math.floor(x) + Math.floor(y)              // Treppe
Math.sin(Math.sqrt(x*x+y*y)) / (Math.sqrt(x*x+y*y)+0.01)  // Mex. Hut
(Math.floor(x) + Math.floor(y)) % 2        // Schachbrett

8. Wie der Plotter funktioniert

function plotFunction(formel, aufloesung) {
    const n = aufloesung;  // z.B. 50 → 50×50 = 2500 Punkte
    const range = 5;       // x und y von -5 bis +5
    const x = [], y = [], z = [];

    for (let i = 0; i < n; i++) {
        const xRow = [], zRow = [];
        const xVal = -range + (2 * range * i) / (n - 1);
        for (let j = 0; j < n; j++) {
            const yVal = -range + (2 * range * j) / (n - 1);
            const fn = new Function('x', 'y', 't', `return ${formel}`);
            xRow.push(xVal);
            zRow.push(fn(xVal, yVal, t));
        }
        x.push(xRow);
        z.push(zRow);
    }
    Plotly.react('plot', [{ type: 'surface', x, y, z, colorscale: 'Viridis' }]);
}

9. Projektstruktur

3D_Plotter/
├── Version-1/index.html        ← Basis-Plotter (Plotly.js)
├── Version-2/index.html        ← + Animation mit Parameter t
├── Version-3/index.html        ← + PNG / JSON / TXT Export
├── Version-4/engine/           ← Boot-Physik, Hydrodynamik, Segel
├── Version-5/engine/           ← Multiplayer (WebSocket, Prediction, Replication)
└── Version-6/                  ← WebGPU Engine (aktuell)
    ├── index.html              ← Einstiegspunkt
    ├── main.js                 ← Engine-Bootstrap
    ├── core/                   ← Renderer, Kamera, Materialien, Shader-Compiler
    ├── editor/                 ← Editor-UI, Inspector, Transform-Gizmos
    ├── shaders/                ← WGSL-Shader (basic.wgsl)
    └── world/                  ← Welt-Systeme

3DFunctionPlotter.html          ← Standalone-Version (eine Datei, kein Server nötig)

Teil 2 — Open-World WebGPU Engine: Architecture

10. Open-World WebGPU Engine: Einleitung

Der 3D Function Plotter ist der Ausgangspunkt einer größeren Entwicklungsreise: der Open-World WebGPU Engine — eine modulare, vollständig prozedurale 3D-Engine, die direkt im Browser läuft.

SystemBeschreibung
WebGPU RenderingPhysically-Based Rendering, Compute Shader, GPU-Instancing, Post-Processing
Prozedurale WeltTerrain, Biome, Vegetation-Spawner, Wetter-Simulation
FFT-OzeanRealistischer Ozean per Fast Fourier Transform auf der GPU
KI-NPCsBehavior Trees, Pathfinding, NPC-Ökosysteme (Fische, Haie, Vögel, Händler)
GameplayCharakter-Controller, Boot-Physik, Interaktionen
MultiplayerWebSocket-Client, State Replication, Prediction & Reconciliation
EditorTerrain-Painter, Vegetation-Tools, Shader-Editor, Debug-Panels

Architektur-Philosophie

Zielgruppe der Engine

Engine-Entwickler, Gameplay-Programmierer, Shader-Autoren, WebGPU-Enthusiasten, Forscher & Studenten.

11. Engine Architecture: Überblick

Die Engine besteht aus fünf Schichten von oben nach unten:

┌──────────────────────────────────────────────┐
│           APPLICATION LAYER                  │
│  (Game Code, Scripts, Editor Tools)          │
├──────────────────────────────────────────────┤
│           ENGINE LAYER                       │
│  (Core, ECS, Systems, World, Rendering)      │
├──────────────────────────────────────────────┤
│           SUBSYSTEM LAYER                    │
│  (Terrain, Ocean, Weather, AI, Physics)      │
├──────────────────────────────────────────────┤
│           RENDERING LAYER                    │
│  (WebGPU, Pipelines, Shaders, Materials)     │
├──────────────────────────────────────────────┤
│           PLATFORM LAYER                     │
│  (Browser, WebGPU API, Input, Network)       │
└──────────────────────────────────────────────┘
SchichtInhalt
ApplicationSpielcode, Skripte, Editor-Tools — das, was Entwickler schreiben
EngineECS, Systems, World-Manager, Renderer — das Herzstück
SubsystemSpezialisierte Systeme: Terrain, Ozean, Wetter, KI, Physik
RenderingWebGPU-Pipelines, Shader, Materialien
PlatformBrowser-APIs, WebGPU, Input, Netzwerk — die Basis

12. Core Framework: ECS & Engine Loop

12.1 Was ist ECS?

Das Entity-Component-System ist das Fundament der Engine. Statt klassischer Klassen-Hierarchien werden drei Konzepte sauber getrennt:

KonzeptBedeutungBeispiel
EntityEin Objekt in der Welt — nur eine IDBoot, Fisch, Baum, Charakter
ComponentReine Daten eines Objekts — keine LogikPosition, Geschwindigkeit, NPC-State
SystemLogik, die auf viele Entities angewendet wirdPhysicsSystem, AISystem, RenderSystem
// Beispiel: Hai-Entity mit Komponenten
const shark = ecs.createEntity();
ecs.addComponent(shark, Transform,  { x: 10, y: 0, z: 5 });
ecs.addComponent(shark, NPC,        { type: "shark", state: "idle" });
ecs.addComponent(shark, Rigidbody,  { vx: 0, vy: 0, vz: 0 });

// ECS-Datenfluss:
// Entity → Component → System → Frame Update

12.2 System Scheduler

Systeme werden in fester Reihenfolge ausgeführt. Die Reihenfolge ist entscheidend — z.B. muss die Physik vor dem Rendering laufen:

Ausführungsreihenfolge pro Frame:
  1. InputSystem        ← Tastatur, Maus, Touch
  2. PhysicsSystem      ← Kollision, Schwerkraft
  3. OceanSystem        ← FFT-Wellensimulation
  4. WeatherSystem      ← Wind, Regen, Wolken
  5. AISystem           ← Behavior Trees der NPCs
  6. CharacterSystem    ← Spieler-Controller
  7. BoatSystem         ← Boot-Physik & Auftrieb
  8. VegetationSystem   ← Wind-Animation für Bäume/Gras
  9. RenderingSystem    ← Alles zeichnen
 10. EditorSystem       ← Debug-Panels (nur im Editor-Modus)

12.3 Engine Loop

function frame(timestamp) {
    const dt = (timestamp - lastTime) / 1000; // Delta-Zeit in Sekunden
    lastTime = timestamp;

    input.update();          // Eingaben auslesen
    ecs.preUpdate(dt);       // Vorab-Berechnungen
    ecs.update(dt);          // Alle Systeme in Reihenfolge
    renderer.render(scene);  // WebGPU Render-Pass

    requestAnimationFrame(frame); // Nächsten Frame planen
}
requestAnimationFrame(frame); // Loop starten
requestAnimationFrame: Das ist der Browser-native Mechanismus für Animations-Loops. Er wird automatisch mit der Bildwiederholrate des Monitors synchronisiert (meistens 60 Hz) und pausiert, wenn der Tab nicht sichtbar ist — spart Akkustrom und CPU.

13. Rendering Architecture

13.1 Rendering Pipeline

CPU:
  Scene → Culling → Render Graph → Command Buffers → GPU

GPU:
  Vertex Shader → Fragment Shader → PostFX → SwapChain → Bildschirm

13.2 Render Graph

Der Render Graph definiert die Reihenfolge aller Render-Passes:

PassInhalt
Depth Pre-PassTiefenwerte vorberechnen für Occlusion Culling
Opaque PassAlle undurchsichtigen Objekte (Terrain, Gebäude)
Ocean PassFFT-Ozean mit Transparenz und Reflektionen
Vegetation PassBäume und Gras (GPU-Instancing)
Sky & Weather PassHimmel, Wolken, Regen, Atmosphäre
Post-ProcessingBloom, Tonemapping, FXAA, Farbkorrektur

13.3 Material System

// Material als JSON-Definition:
{
  "shader": "pbr.wgsl",
  "textures": ["albedo.png", "normal.png"],
  "uniforms": {
    "roughness": 0.4,
    "metallic": 0.1
  }
}

13.4 Shader Beispiel (WGSL)

WGSL (WebGPU Shading Language) ist die Shader-Sprache von WebGPU, vergleichbar mit GLSL für WebGL:

// Vertex Shader: Transformiert 3D-Position in Bildschirmkoordinaten
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
    var out: VertexOutput;
    out.position = camera.proj * camera.view * model * vec4(input.position, 1.0);
    out.normal = normalize((model * vec4(input.normal, 0.0)).xyz);
    return out;
}

// Fragment Shader: Berechnet Farbe pro Pixel
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    let light = max(dot(input.normal, vec3(0.2, 1.0, 0.3)), 0.0);
    return vec4(light, light, light, 1.0); // Graustufen-Beleuchtung
}

14. World Composition

14.1 Terrain System

// Terrain API:
const height = terrain.getHeightAt(x, z);   // Höhe an Position (x,z)
const biome  = terrain.getBiomeAt(x, z);    // Biom an Position (x,z)
const slope  = terrain.getSlopeAt(x, z);    // Hangneigung

14.2 Ocean System (FFT)

Der Ozean wird mit einer Fast Fourier Transform simuliert — physikalisch realistisch, GPU-beschleunigt:

// Wellenhöhe an Position abfragen (für Physik):
const waveHeight = ocean.sampler.sample(x, z);

14.3 Vegetation System

15. Physik & KI-Framework

15.1 Physik-Schicht

KomponenteBeschreibung
RigidbodyGeschwindigkeit, Beschleunigung, Masse
CollisionEinfache AABB- und Sphere-Kollision
BuoyancyAuftrieb basierend auf aktueller Wellenhöhe
Character CapsuleKollision für den Spieler-Controller
Boat PhysicsBoot-Auftrieb, Rückstellung, Wasser-Widerstand
// Buoyancy-Beispiel: Boot treibt auf dem Ozean
const wave     = ocean.sampler.sample(x, z);
const buoyancy = (wave - boat.position.y) * boat.physics.buoyancyFactor;
boat.rigidbody.vy += buoyancy * dt;   // Auftriebskraft anwenden

15.2 KI-Framework

Die KI der NPCs basiert auf Behavior Trees — einem baumartigen Entscheidungssystem:

// Behavior Tree eines Hai-NPCs:
Root
 └─ Selector (wählt erste erfolgreiche Aktion)
     ├─ FleeNode    ← Fliehen, wenn Gefahr nah
     ├─ ChaseNode   ← Verfolgen, wenn Beute sichtbar
     └─ WanderNode  ← Ziellos schwimmen (Standard)

// Tick-Aufruf im AISystem (einmal pro Frame):
bt.tick(npc, dt);
Blackboard: NPCs kommunizieren über ein gemeinsames Datenspeicher-Objekt, das „Blackboard“. Dort steht z.B.: Ist der Spieler in Reichweite? Wo ist die letzte bekannte Position? Jeder Behavior-Tree-Knoten liest und schreibt darauf.

16. Networking & Editor

16.1 Networking Architecture

Multiplayer basiert auf WebSockets — einer dauerhaften bidirektionalen Verbindung zwischen Browser und Server:

// Replication Flow:
Client → sendet Input → Server
Server → berechnet autoritativen State → sendet an alle Clients
Clients → wenden State an + Prediction & Reconciliation

// Deterministische Seeds:
// Alle Clients erzeugen dieselbe prozedurale Welt aus demselben Seed
// → Kein ständiges Übertragen von Terrain-Daten nötig

16.2 Editor Framework

Der integrierte Editor ermöglicht Echtzeit-Bearbeitung der Welt ohne Neustart:

PanelFunktion
Terrain PainterHöhe malen, senken, glätten direkt im 3D-Viewport
Vegetation ToolsBäume und Gras platzieren, löschen, konfigurieren
Shader EditorWGSL-Shader live bearbeiten und sofort sehen
Debug PanelsECS-Entities inspizieren, Systeme deaktivieren, Seed fixieren
// Panel-Registrierung im Editor:
editor.addPanel("Terrain",    new TerrainPanel());
editor.addPanel("Vegetation", new VegetationPanel());
editor.addPanel("Shader",     new ShaderEditor());

Teil 3 — Tutorials & How-To Guides

17. Getting Started

Ziel: Projekt starten, Engine laden, erste Szene sehen.

Voraussetzungen

Schritte

# 1. Lokalen Server starten (Node.js):
npx serve

# Oder mit Python:
python -m http.server 3000

# 2. Browser öffnen:
http://localhost:3000

# 3. Engine lädt automatisch und zeigt:
#    - Terrain mit Biomen
#    - Ozean mit FFT-Wellen
#    - Vegetation (Bäume, Gras)
#    - Charakter und Boot
#    - Editor-Panels

Steuerung

TasteAktion
W / A / S / DCharakter bewegen
MausKamera drehen
SpaceSpringen
ShiftRennen
EInteragieren (Boot betreten)
W / S (im Boot)Vorwärts / Rückwärts
A / D (im Boot)Lenken
F12Browser-Konsole für Debug-Logs

18. Erste Entity erstellen

Ziel: Lernen, wie man ein Entity mit Komponenten erstellt und in der Welt platziert.

Beispiel: Ein „Fisch“-Entity

// Schritt 1: Entity erstellen (bekommt eine eindeutige ID)
const fish = ecs.createEntity();

// Schritt 2: Komponenten hinzufügen (Daten des Objekts)
ecs.addComponent(fish, Transform, { x: 5, y: -2, z: 10 }); // Position
ecs.addComponent(fish, NPC,       { type: "fish", state: "swim" }); // KI-State
ecs.addComponent(fish, Rigidbody, { vx: 0, vy: 0, vz: 0 }); // Physik

// Schritt 3: Renderer registrieren (wie der Fisch gezeichnet wird)
renderer.register("fish", FishRenderer);

// Schritt 4: KI-Verhalten registrieren
AISystem.register("fish", FishBehaviorTree);
Warum getrennt? Durch die Trennung von Daten (Components) und Logik (Systems) können beliebige Kombinationen erstellt werden. Ein Baum hat Transform und Vegetation, aber kein Rigidbody. Ein Boot hat Rigidbody und BoatPhysics, aber keine KI.

19. Eigenes System erstellen

Ziel: Ein neues System erstellen und in den ECS-Scheduler einbinden.

Beispiel: FloatingSystem — Objekte treiben auf dem Ozean

// Schritt 1: System-Klasse definieren
class FloatingSystem {
    update(dt) {
        // Alle Entities abfragen, die Transform UND Floating haben:
        const entities = ecs.query(Transform, Floating);

        for (const e of entities) {
            const t = ecs.getComponent(e, Transform);
            const f = ecs.getComponent(e, Floating);

            // Wellenhöhe an der aktuellen Position abfragen:
            const wave = ocean.sampler.sample(t.x, t.z);

            // Y-Position auf Wellenhöhe + Offset setzen:
            t.y = wave + f.offset;
        }
    }
}

// Schritt 2: System im Scheduler registrieren
ecs.addSystem(new FloatingSystem());

// Schritt 3: Komponente zu gewünschten Entities hinzufügen
ecs.addComponent(driftwood, Floating, { offset: 0.2 });
ecs.addComponent(buoy,      Floating, { offset: 0.5 });

20. NPC erstellen

Ziel: Einen NPC mit Behavior Tree, Physik und Rendering erstellen.

Beispiel: Hai-NPC

// Schritt 1: Entity + Komponenten
const shark = ecs.createEntity();
ecs.addComponent(shark, Transform,  { x: 0,  y: -3, z: 0 });
ecs.addComponent(shark, NPC,        { type: "shark", state: "idle" });
ecs.addComponent(shark, Rigidbody,  { vx: 0,  vy: 0, vz: 0 });

// Schritt 2: Behavior Tree definieren
const SharkBT = {
    root: Selector([
        FleeNode(),    // Zuerst: Fliehen wenn bedroht?
        ChaseNode(),   // Dann: Beute in Sichtweite?
        WanderNode()   // Sonst: Ziellos umherschwimmen
    ])
};

// Schritt 3: KI und Renderer registrieren
AISystem.register("shark", SharkBT);
renderer.register("shark", SharkRenderer);

Behavior Tree Knoten

KnotenTypFunktion
SelectorKompositFührt Kinder aus bis einer erfolgreich ist
SequenceKompositFührt alle Kinder aus — bricht ab wenn einer scheitert
FleeNodeActionBewegt NPC vom Spieler weg
ChaseNodeActionBewegt NPC auf Beute zu
WanderNodeActionZufällige Bewegung

21. Fahrzeug erstellen

Ziel: Ein neues Fahrzeug basierend auf dem Boot-System erstellen.

Beispiel: Floß

// Schritt 1: Entity + Physik-Komponenten
const raft = ecs.createEntity();
ecs.addComponent(raft, Transform,    { x: 0, y: 0, z: 0 });
ecs.addComponent(raft, Rigidbody,    { vx: 0, vy: 0, vz: 0 });
ecs.addComponent(raft, BoatPhysics,  { buoyancy: 1.2, drag: 0.3 });

// Schritt 2: Steuerung hinzufügen
ecs.addComponent(raft, BoatController, { turnSpeed: 0.5, accel: 1.0 });

// Schritt 3: Renderer registrieren
renderer.register("raft", RaftRenderer);

// Das BoatSystem sorgt automatisch für:
// - Auftrieb basierend auf Wellenhöhe (ocean.sampler)
// - Wasser-Widerstand (drag)
// - Rückstellkraft (Kippverhinderung)

22. Neues Biom erstellen

Ziel: Ein neues Biom erstellen, das Terrain-Farben und Vegetation beeinflusst.

Beispiel: Vulkan-Biom

// Schritt 1: Biom im TerrainSystem registrieren
terrain.registerBiome("volcano", {
    heightMin:   0.6,              // Ab Höhe 60%
    heightMax:   1.0,              // Bis zur Spitze
    color:       [0.3, 0.1, 0.1], // Dunkelrote Farbe
    vegetation:  "lavaRocks"       // Welche Vegetation?
});

// Schritt 2: Passende Vegetation registrieren
vegetation.register("lavaRocks", LavaRockSpawner);

// Schritt 3: Noise-Regel in der Terrain-Generierung
// (in terrainGenerator.js):
if (height > 0.8) biome = "volcano";

// Weitere Biome als Referenz:
// height < 0.05  → beach (Sand)
// height < 0.35  → forest (Wald)
// height < 0.65  → mountain (Gebirge)
// height < 0.85  → snow (Schnee)

23. Shader erstellen (WGSL)

Ziel: Einen eigenen Shader in WGSL schreiben und ins Material-System einbinden.

Beispiel: Glühender Lava-Shader

// Schritt 1: Shader-Datei erstellen (lava.wgsl)
@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    // sin() erzeugt pulsierendes Glühen basierend auf Zeit und Höhe
    let glow = sin(time * 5.0 + input.position.y * 10.0) * 0.5 + 0.5;
    return vec4(glow, 0.1, 0.0, 1.0); // Rot-Orange
}

// Schritt 2: Material-Datei erstellen (lava.material.json)
{
  "shader": "lava.wgsl",
  "uniforms": { "intensity": 1.0 }
}

// Schritt 3: Renderer registrieren
renderer.registerMaterial("lava", LavaMaterial);

// Schritt 4: Material einem Entity zuweisen
ecs.addComponent(volcanoMesh, Material, { type: "lava" });
WGSL-Grundprinzip: Jeder Shader hat zwei Hauptfunktionen: @vertex transformiert die 3D-Position eines Punktes in Bildschirmkoordinaten. @fragment berechnet die endgültige Farbe für jeden Pixel. Beide laufen direkt auf der GPU — parallel für alle Vertices/Pixel gleichzeitig.

24. Multiplayer hinzufügen

Ziel: Multiplayer-Funktionalität in die Engine integrieren.

// Schritt 1: WebSocket-Verbindung aufbauen
client.connect("ws://localhost:8080");

// Schritt 2: Eigenen Spieler-State senden (jeder Frame)
client.sendState(playerEntity);

// Schritt 3: Anderen Spielern State empfangen und anwenden
client.onState((remoteState) => {
    multiplayer.apply(remoteState);
});

// Schritt 4: Client-seitige Vorhersage (Prediction)
// Spieler-Bewegung sofort anwenden ohne Server-Bestätigung:
player.position += inputVector * dt * speed;
// Bei Server-Korrektur: Position sanft angleichen (Reconciliation)

Determinismus als Grundlage

Da die gesamte Welt prozedural aus einem Seed erzeugt wird, müssen Terrain-Daten nicht übertragen werden. Alle Clients erzeugen exakt dieselbe Welt — nur Spielerpositionen und Interaktionen werden synchronisiert.

// Alle Clients starten mit demselben Seed:
const WORLD_SEED = 42;
terrain.generate(WORLD_SEED);  // Ergibt überall exakt dasselbe Terrain
ocean.init(WORLD_SEED);
vegetation.spawn(WORLD_SEED);

Teil 4 — Diagramme & Technische Spezifikationen

25. UML-Diagramme

Diese Diagramme zeigen die wichtigsten Klassen und ihre Beziehungen in der Engine — im ASCII-Format, das überall funktioniert.

ECS UML

┌──────────────┐
│   Entity      │
├──────────────┤
│ id: number    │
└───────┬──────┘
        │ hat viele
        ▼
┌──────────────┐
│  Component    │
├──────────────┤
│ data: object  │
└───────┬──────┘
        │ verarbeitet von
        ▼
┌──────────────┐
│    System     │
├──────────────┤
│ update(dt)    │
└──────────────┘
Lesen so: Ein Entity hat keine eigenen Daten. Es ist nur eine ID-Nummer. Alle Daten stecken in den Components. Die Systems lesen und verändern diese Daten jeden Frame.

Renderer UML

┌────────────────┐
│   Renderer      │
├────────────────┤
│ drawMesh()      │  ← zeichnet 3D-Geometrie
│ drawBillboard() │  ← zeichnet 2D-Sprites (Gras)
│ register()      │  ← meldet neuen Typ an
└───────┬────────┘
        │ nutzt
        ▼
┌────────────────┐
│    Material     │
├────────────────┤
│ shader          │  ← WGSL-Programm
│ textures        │  ← Bilder (Albedo, Normal)
│ uniforms        │  ← Parameterwerte
└────────────────┘

Terrain UML

┌────────────────┐
│  TerrainSystem  │
├────────────────┤
│ generate()      │
│ getHeightAt()   │
│ getBiomeAt()    │
└───────┬────────┘
        │ nutzt
        ▼
┌────────────────┐
│     Biome       │
├────────────────┤
│ rules           │  ← ab welcher Höhe aktiv
│ vegetation      │  ← welche Pflanzen spawnen
└────────────────┘

Ocean (FFT) UML

┌────────────────┐
│  OceanSystem    │
├────────────────┤
│ update(dt)      │
│ setWind()       │
└───────┬────────┘
        │ verwendet
        ▼
┌────────────────┐
│  OceanSampler   │
├────────────────┤
│ sample(x, z)    │  ← gibt Wellenhöhe zurück
└────────────────┘

AI UML

┌────────────────┐
│  BehaviorTree   │
├────────────────┤
│ root: Node      │
└───────┬────────┘
        │ enthält
        ▼
┌────────────────┐
│      Node       │
├────────────────┤
│ tick()          │  ← wird jeden Frame aufgerufen
└────────────────┘

26. Sequence- & Dataflow-Diagramme

Sequence-Diagramme zeigen den zeitlichen Ablauf von Ereignissen. Dataflow-Diagramme zeigen, wie Daten von System zu System fließen.

Frame-Update-Sequenz (pro Frame)

User Input
    │
    ▼
InputSystem.update()      ← Tastatur/Maus auslesen
    │
    ▼
ECS.preUpdate()           ← Entities vorbereiten
    │
    ▼
PhysicsSystem.update()    ← Physik berechnen
    │
    ▼
AISystem.update()         ← NPC-Entscheidungen treffen
    │
    ▼
CharacterSystem.update()  ← Spieler bewegen
    │
    ▼
BoatSystem.update()       ← Boot physik berechnen
    │
    ▼
Renderer.render()         ← Alles auf den Bildschirm zeichnen

Behavior Tree Tick (NPC-Entscheidung)

AISystem.update()
    │
    ▼
BehaviorTree.root.tick()
    │
    ├── FleeNode.tick()?       ← Feind in der Nähe? → fliehe!
    │       │
    │       └── success → fertig (return)
    │
    ├── ChaseNode.tick()?      ← Beute in Sichtweite? → verfolge!
    │       │
    │       └── success → fertig (return)
    │
    └── WanderNode.tick()      ← Sonst: zufällig wandern

Multiplayer-Sync-Sequenz

Client Input
    │
    ▼
Client → Server: sendInput()        ← Tastendruck senden
    │
    ▼
Server: simulate()                  ← Spielwelt berechnen
    │
    ▼
Server → alle Clients: sendState()  ← Ergebnis verteilen
    │
    ▼
Clients: Prediction + Reconciliation ← Eigene Vorhersage korrigieren

Terrain → Vegetation Dataflow

Noise Generator         ← erzeugt zufällige Höhenwerte
      │
      ▼
Heightmap               ← 2D-Raster mit Höhenwerten
      │
      ▼
Biome Rules             ← welches Biom bei welcher Höhe?
      │
      ▼
Vegetation Spawner      ← platziert Bäume, Gras, Felsen
      │
      ▼
Renderer                ← zeichnet alles auf den Bildschirm

Weather → Ocean → Boot Dataflow

WeatherSystem     ← bestimmt Windstärke und Richtung
   │
   ▼
Wind              ← Parameter für Wellen
   │
   ▼
Ocean FFT         ← berechnet Wellenbewegung per GPU
   │
   ▼
Wave Sampler      ← liefert Wellenhöhe an beliebiger Position
   │
   ▼
Boat Physics      ← Boot hebt und senkt sich mit den Wellen

27. Shader-Pipeline-Diagramm

So fließen Daten von der CPU zur GPU und schließlich auf den Bildschirm.

Vollständige Shader-Pipeline

CPU-Seite:
 ├─ Scene Graph       ← alle Objekte in der Welt
 ├─ Culling           ← unsichtbare Objekte ausblenden
 ├─ Render Graph      ← Reihenfolge der Passes festlegen
 └─ Command Buffers   ← GPU-Befehle vorbereiten
        │
        │  (an GPU senden)
        ▼
GPU-Seite:
 ├─ Vertex Shader         ← 3D-Punkte → 2D-Bildschirmposition
 ├─ Fragment Shader        ← Pixel-Farbe berechnen
 ├─ Ocean Compute Shader   ← FFT-Wellensimulation (parallel)
 ├─ Post-Processing        ← Bloom, Tonemapping, Farbkorrektur
 └─ SwapChain Present      ← fertiges Bild anzeigen
Was ist ein Pass? Ein „Pass“ ist ein kompletter Rendering-Durchlauf. Die Engine nutzt mehrere Passes: Depth Pre-Pass (nur Tiefe), Opaque Pass (undurchsichtige Objekte), Ocean Pass (Wasser), Vegetation Pass (Bäume/Gras), Sky Pass, Post-Processing. Jeder Pass schreibt in einen Buffer, der im nächsten Pass gelesen werden kann.

Ocean FFT Pipeline

Initial Spectrum (Phillips)   ← Wellenform basierend auf Wind
        │
        ▼
Time Evolution                ← Wellen bewegen sich mit der Zeit
        │
        ▼
FFT Compute Shader            ← Fast Fourier Transform (GPU)
        │
        ▼
Displacement Map              ← wie weit bewegt sich jeder Punkt?
        │
        ▼
Normal Map                    ← in welche Richtung zeigt die Oberfläche?
        │
        ▼
Ocean Rendering               ← Wasser mit Licht und Reflexionen zeichnen

Teil 5 — Engine Internals

28. ECS Internals & Sparse Set

Wie speichert die Engine Millionen von Komponenten effizient? Die Antwort heißt Sparse Set — eine Datenstruktur, die O(1)-Zugriff mit Cache-Freundlichkeit kombiniert.

Sparse Set Struktur

SparseSet:
  sparse[] → Entity-ID → Index in dense[]
  dense[]  → Index → Entity-ID
  data[]   → Index → Komponenten-Daten

Beispiel (Transform-Komponente für 4 Entities):
  dense:  [E3, E7, E1, E9]          ← welche Entities haben Transform?
  data:   [T3, T7, T1, T9]          ← deren Transform-Daten
  sparse: [ -,  -, 2, 0, -,  -, -, 1,  -, 3]  ← Lookup: E1 → Index 2

Vorteile gegnüber HashMap:

Warum keine Archetypes?

Andere ECS-Systeme (z.B. Unity DOTS) nutzen Archetypes — Gruppen von Entities mit exakt denselben Komponenten. Das ist schnell bei statischen Welten, aber in einer dynamischen Open-World ändern sich Komponenten ständig (NPC bekommt Feuer-Debuff, Boot wird verlassen, ...). Sparse Sets sind für diese dynamischen Welten schneller.

Query-Engine: Wie Systeme Entities finden

// Intern: ecs.query(Transform, Floating) läuft so ab:
// 1. Wähle die Komponente mit den wenigsten Einträgen (z.B. Floating mit 50 Entities)
// 2. Iteriere über deren dense[]-Array
// 3. Prüfe für jede Entity: hat sie auch Transform?
//    → sparse[entity] !== -1 → ja → Entity zur Ergebnisliste hinzufügen

// Ergebnis: nur Entities, die ALLE gewünschten Komponenten haben
const entities = ecs.query(Transform, Floating);
for (const e of entities) {
    // E wurde garantiert gefunden: hat Transform UND Floating
}

29. Memory Layout & Zero-GC Architecture

Die Engine wurde so designed, dass sie keine Garbage Collection ausloest. Warum ist das wichtig? GC-Pausen können in JavaScript mehrere Millisekunden dauern — das fühlt sich als Stotter-Frame an.

Structure of Arrays (SOA)

Jede Komponente speichert ihre Felder als separate Arrays — nicht als Array von Objekten:

// FALSCH (Array of Structures = AoS) — schlecht für Cache
transforms = [
  { x: 1, y: 0, z: 5 },
  { x: 2, y: 1, z: 3 },
  // ... tausende Objekte → JavaScript GC muss alle verwalten
];

// RICHTIG (Structure of Arrays = SoA) — gut für Cache und GPU
Transform = {
  x:   Float32Array([1, 2, ...]),  // alle x-Werte hintereinander
  y:   Float32Array([0, 1, ...]),
  z:   Float32Array([5, 3, ...]),
  rot: Float32Array([...]),         // Quaternion
};

// Vorteil: Float32Array ist kein GC-Objekt → keine GC-Pausen!
// Vorteil: GPU kann Float32Array direkt nutzen (kein Konvertieren)

GPU-Friendly Data Layout

// Vegetation Instance Buffer — direkt GPU-kompatibel:
struct Instance {
  vec3 position;   // 12 Bytes
  vec3 scale;      // 12 Bytes
  vec4 rotation;   // 16 Bytes (Quaternion)
  uint biomeId;    //  4 Bytes
};
// Ein Buffer für 10.000 Bäume = 440 KB → 1 GPU-Upload, 1 Draw Call

Zero-GC Design-Regeln

Verboten (erzeugt GC)Erlaubt (kein GC)
new Vector3(x, y, z) im LoopWerte direkt in Float32Array schreiben
entities.filter(...)Sparse-Set-Query (keine neuen Arrays)
Object.assign({}, state)Direkte Property-Zuweisung
Temporäre Arrays im FramePre-allokierte, wiederverwendete Puffer

30. Performance-Optimierungen

Frustum Culling

Objekte, die die Kamera nicht sehen kann, werden überhaupt nicht gerendert:

// Für jedes Objekt prüfen: ist es im Sichtfeld der Kamera?
for (const entity of allEntities) {
    const pos = ecs.getComponent(entity, Transform);

    // Ist die Bounding Sphere des Objekts im Frustum?
    if (!camera.frustum.containsSphere(pos, radius)) {
        continue; // überspringen → kein Draw Call → spart GPU-Zeit
    }

    renderer.draw(entity); // nur wenn sichtbar
}

LOD (Level of Detail)

Weit entfernte Objekte werden mit vereinfachten Modellen gerendert:

const dist = distanceTo(camera.position, tree.position);

if      (dist <  50)  renderMesh(tree, lod0);  // Hochdetail
else if (dist < 150)  renderMesh(tree, lod1);  // Mitteldetail
else if (dist < 300)  renderMesh(tree, lod2);  // Niedrigdetail
else                  renderBillboard(tree);    // 2D-Sprite (sehr weit)

GPU Instancing für Vegetation

Statt 10.000 einzelner Draw Calls für Bäume: ein einziger Draw Call mit einem Instance Buffer:

// Einmalig: Instance Buffer erstellen (alle Baum-Positionen)
const instanceBuffer = device.createBuffer({
    size: treeCount * INSTANCE_STRIDE,
    usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
});

// Jeden Frame: ein Draw Call für ALLE Bäume
renderEncoder.drawIndexedIndirect(treeIndexBuffer, 0);
// → GPU rendert alle treeCount Bäume parallel

Ocean FFT Optimierungen

TechnikErklärung
Precomputed SpectrumPhillips-Spektrum wird einmalig berechnet, nicht jeden Frame
Ping-Pong BuffersZwei GPU-Buffer wechseln sich ab — kein Warten auf GPU
Shared Memoryvar<workgroup> in WGSL für schnellen Thread-Austausch
Compute ShaderFFT läuft parallel auf tausenden GPU-Kernen

31. GPU-Optimierungen & Debugging

Bind Group Reuse

Bind Groups (Shader-Ressourcen) werden nicht pro Frame neu erstellt — das wäre extrem teuer:

// FALSCH — neue Bind Group jeden Frame:
function render() {
    const bg = device.createBindGroup({...}); // TEUER!
    renderPass.setBindGroup(0, bg);
}

// RICHTIG — Bind Group einmalig erstellen und wiederverwenden:
const bindGroup = device.createBindGroup({...}); // einmal beim Start
function render() {
    renderPass.setBindGroup(0, bindGroup); // günstig: nur Referenz setzen
}

Pipeline Caching

Alle GPU-Pipelines werden beim Start erstellt — nie zur Laufzeit:

// Beim Laden der Engine — einmalig:
const pbrPipeline       = device.createRenderPipeline({...}); // PBR-Objekte
const oceanPipeline     = device.createRenderPipeline({...}); // Wasseroberfläche
const vegetationPipeline= device.createRenderPipeline({...}); // Bäume/Gras
const skyPipeline       = device.createRenderPipeline({...}); // Himmel
const postPipeline      = device.createRenderPipeline({...}); // Post-Processing

Compute Shader Workgroup-Größen

// Ocean FFT: 2D-Grid (passt zu 2D-Textur)
@compute @workgroup_size(16, 16)
fn fft_pass(/* ... */) { /* ... */ }

// Vegetation Wind: 1D (einfache Listen-Verarbeitung)
@compute @workgroup_size(64)
fn wind_update(/* ... */) { /* ... */ }
Warum diese Zahlen? GPU-Workgroups sollten ein Vielfaches von 32 (Warp-Größe) oder 64 sein. 16×16 = 256 Threads pro Workgroup ist ideal für 2D-Texturen. 64 ist ideal für 1D-Arrays.

Debug-Panels (im integrierten Editor)

PanelZeigt
ECS InspectorAlle Entities und ihre Komponenten
System ProfilerWie viel ms jedes System benötigt
GPU StatsDraw Calls, GPU-Zeit, Buffer-Größen
Ocean DebugWellenkarte, FFT-Buffer-Visualisierung
Terrain DebugHeightmap, Biome-Karte
AI DebugBehavior-Tree-Zustand aller NPCs
// Logging-System:
debug.log("AI",      "Shark switched to chase mode");
debug.log("Physics", "Boat capsized at (123, 0, 456)");
debug.log("GPU",     "Draw calls this frame: 142");

// Profiling-Daten abrufen:
const stats = profiler.getFrame();
// → { physics: 1.2ms, ai: 0.8ms, rendering: 4.1ms, total: 6.1ms }

Teil 6 — Glossar & Schlusswort

32. Glossar

Alle wichtigen Begriffe der Engine kurz erklärt — alphabetisch sortiert.

BegriffErklärung
AABBAxis-Aligned Bounding Box. Nicht-rotierte Hüllbox für Culling und Kollision.
AISubsystem für NPC-Verhalten: Behavior Trees, Pathfinding, State Machines.
ArchetypeIn manchen ECS-Systemen: Gruppierung von Entities nach Komponenten. Diese Engine nutzt stattdessen Sparse Sets.
Behavior TreeBaumstruktur zur Steuerung von NPC-Verhalten. Nodes: Selector, Sequence, Action.
BiomeRegelset für Terrain-Zonen (Wald, Wüste, Vulkan, Ozean).
Billboard2D-Sprite, das immer zur Kamera zeigt. Wird für Gras und weit entfernte Objekte genutzt.
Bind GroupWebGPU-Objekt, das Shader-Ressourcen (Uniforms, Texturen, Buffer) bündelt.
BuoyancyAuftriebskraft, die Objekte im Wasser hält. Berechnet aus Wellenhöhe minus Objekt-Y.
Compute ShaderShader für allgemeine GPU-Berechnungen (FFT-Ozean, Wind-Animation).
ComponentDatencontainer eines Entities (Transform, Rigidbody, NPC).
CullingNicht-sichtbare Objekte ausschließen, bevor sie zur GPU geschickt werden.
Delta Time (dt)Zeit in Sekunden zwischen zwei Frames. Macht Bewegung frame-rate-unabhängig.
Draw CallGPU-Befehl zum Zeichnen eines Meshes. Jeder Draw Call hat Overhead — daher Instancing.
ECSEntity-Component-System. Architektur, die Daten (Components) von Logik (Systems) trennt.
EntityEindeutige ID-Nummer für ein Objekt in der Welt. Hat selbst keine Daten.
FFTFast Fourier Transform. GPU-Algorithmus zur realistischen Ozeanwellen-Simulation.
FrustumSichtvolumen der Kamera. Objekte außerhalb werden nicht gerendert (Culling).
GPU InstancingViele gleiche Objekte mit einem einzigen Draw Call rendern.
Heightmap2D-Raster mit Höhenwerten für das Terrain.
LODLevel of Detail. Weit entfernte Objekte mit niedrigerem Detailgrad rendern.
MaterialKombination aus Shader + Texturen + Uniform-Werten.
Normal MapTextur, die die Oberflächenrichtung für Beleuchtungsberechnungen simuliert.
Ocean SamplerFunktion, die die Wellenhöhe an einer (x, z)-Position zurückgibt.
PipelineWebGPU-Objekt, das Shader + Render-State definiert. Wird einmalig erstellt.
Post-ProcessingBildeffekte nach dem Rendering: Bloom, Tonemapping, Depth of Field.
PredictionClient berechnet Bewegung sofort lokal, ohne auf Server-Bestätigung zu warten.
ReconciliationServer-Korrektur der Client-Prediction. Positionen werden sanft angepasst.
Render GraphDefiniert Reihenfolge und Abhängigkeiten der Rendering-Passes.
ReplicationSynchronisation von Entity-Zuständen im Multiplayer.
RigidbodyKomponente für physikalische Eigenschaften: Geschwindigkeit, Masse, Kräfte.
SeedStartwert für prozedurale Generatoren. Gleicher Seed = gleiche Welt.
Selector NodeBehavior-Tree-Node, der das erste Kind ausführt, das Erfolg zurückgibt.
Sparse SetECS-Datenstruktur für O(1)-Zugriff auf Komponenten. Cache-freundlich.
SOAStructure of Arrays. Speicherlayout: alle X-Werte zusammen, alle Y-Werte zusammen, ...
SystemLogik-Klasse mit update(dt). Verarbeitet alle Entities mit bestimmten Komponenten.
TransformKomponente für Position (x, y, z), Rotation und Skalierung.
TickEin einzelner Aufruf von update(dt) — passiert jeden Frame.
Uniform BufferGPU-Buffer für Shader-Parameter (Kamera-Matrix, Zeit, Lichtrichtung).
VegetationSubsystem für Bäume, Gras, Büsche — mit GPU-Instancing und Wind-Animation.
Vertex ShaderShader, der 3D-Koordinaten eines Punktes in 2D-Bildschirmkoordinaten umrechnet.
WGSLWebGPU Shading Language. Shader-Sprache für WebGPU (ersetzt GLSL).
WorkgroupGruppe von parallel laufenden Threads in einem Compute Shader.

33. Schlusswort & Credits

Vision der Engine

Die Open-World WebGPU Engine wurde mit einem klaren Ziel entwickelt: Eine moderne, modulare, prozedurale Engine zu schaffen, die vollständig im Browser läuft und dennoch die Architektur-Qualität einer AAA-Engine besitzt.

Ziele

ZeithorizontZiele
KurzfristigStabilisierung der Kernsysteme, Dokumentation vervollständigen, Editor-Tools verbessern
MittelfristigErweiterte Multiplayer-Features, Modding-Support, Asset-Pipeline, Animationssystem, Partikelsystem
LangfristigVollständige Open-World Simulation, persistente Welten, KI-Ökosysteme, Creator-Tools

Community & Contribution

Die Engine ist modular aufgebaut und ideal für Zusammenarbeit. Entwickler können beitragen durch: neue Systeme, neue Shader, neue NPC-Typen, neue Editor-Tools, Bugfixes, Performance-Optimierungen oder Dokumentation.

Geplante Kapitel

Danksagung

Ein großes Dankeschön an die WebGPU-Community, die Open-Source-ECS-Projekte (EnTT, Flecs, Bevy ECS), die Shader-Communities (Shadertoy, WGSL-Pioniere), die Game-Engine-Pioniere (Unreal, Unity, Godot) und alle Entwickler, die moderne Web-Technologien vorantreiben.

Diese Engine ist ein lebendes Projekt. Sie wächst mit jeder Idee, jedem Beitrag und jedem Experiment. Sie ist nicht nur ein Werkzeug — sie ist eine Plattform für Kreativität, Technik und Innovation.