Philosophy
GWEN is built on simple principles that make game development predictable and enjoyable.
Scene-Driven Architecture
Games are state machines. You're always in a scene (MainMenu, GamePlay, GameOver, Settings).
export const MainMenuScene = defineScene('MainMenu', () => ({
onEnter(api) {
// Setup menu UI, music, etc.
},
onExit(api) {
// Cleanup before next scene
}
}));Scenes orchestrate everything: which systems run, which UI displays, what entities exist.
ECS Without the Boilerplate
Components are pure data:
export const Health = defineComponent({
name: 'health',
schema: { current: Types.i32, max: Types.i32 }
});Systems are pure logic:
import { defineSystem } from '@djodjonx/gwen-engine-core';
export const DamageSystem = defineSystem({
name: 'DamageSystem',
onUpdate(api, dt) {
const entities = api.query(['health', 'damaged']);
// Process damage
}
});Entities are just IDs:
const enemy = api.createEntity();
api.addComponent(enemy, Position, { x: 100, y: 50 });
api.addComponent(enemy, Health, { current: 10, max: 10 });No classes. No inheritance. Just composition.
Plugin-First Extensibility
Want input? Audio? Debug overlay?
export default defineConfig({
plugins: [
new InputPlugin(),
new AudioPlugin(),
new DebugPlugin()
]
});Plugins expose services that your systems can access:
onUpdate(api, dt) {
const keyboard = api.services.get('keyboard');
const audio = api.services.get('audio');
if (keyboard.isPressed('Space')) {
audio.play('shoot');
}
}Type-safe. Auto-completed. Zero configuration.
Performance by Default
GWEN's core is written in Rust and compiled to WebAssembly.
This means:
- 10K+ entities at 60 FPS
- Cache-friendly data layout
- Zero garbage collection pauses
- Predictable frame times
But you write TypeScript and never touch Rust code.
Reusability Through Prefabs
Don't repeat entity creation:
export const EnemyPrefab = definePrefab({
name: 'Enemy',
create: (api, x, y) => {
const id = api.createEntity();
api.addComponent(id, Position, { x, y });
api.addComponent(id, Health, { current: 5, max: 5 });
api.addComponent(id, AIBehavior, { state: 'patrol' });
return id;
}
});
// Later, anywhere:
api.prefabs.instantiate('Enemy', 100, 200);
api.prefabs.instantiate('Enemy', 300, 150);Flexibility Where It Matters
GWEN doesn't force a renderer on you. Choose based on your game's needs:
Canvas2D
Blazing fast for 2D pixel/sprite games:
export const PlayerUI = defineUI({
name: 'PlayerUI',
render(api, id) {
const { ctx } = api.services.get('renderer');
ctx.fillStyle = '#00ff00';
ctx.fillRect(x, y, 32, 32);
}
});HTML/CSS
Perfect for menus, HUD, UI overlays:
new HtmlUIPlugin() // Enable in config
// Use standard HTML
api.services.get('htmlUI').mount('menu', '<button>Start</button>');WebGL (Three.js, Babylon.js)
Integrate any WebGL library via services:
export const Model3DUI = defineUI({
name: 'Model3DUI',
render(api, id) {
const scene = api.services.get('three-scene');
// Update your 3D objects directly
}
});Mix Multiple Renderers
Use Canvas for gameplay sprites, HTML for UI:
export const GameScene = defineScene('Game', () => ({
ui: [
PlayerUI, // Canvas2D
EnemyUI, // Canvas2D
HUDMenuUI // HTML
]
}));This is a core strength: no abstraction layer forcing you into one paradigm. You control the rendering layer entirely.
Why TypeScript?
- Type safety catches bugs before runtime
- IntelliSense guides you as you code
- Refactoring is safe and fast
- Documentation is built-in via types
- Ecosystem - use any npm package
Why Not Just Use...
Unity/Godot?
- GWEN is web-native (no export step, no WebGL quirks)
- Smaller runtime (<50KB core)
- TypeScript ecosystem (npm, bundlers, DevTools)
- Open source and hackable
Phaser/PixiJS?
- GWEN has true ECS (not GameObject-based)
- Type-safe services (not global singletons)
- Scene lifecycle built-in
- High-performance core (Rust/WASM)
Bevy (Rust)?
- GWEN targets TypeScript developers
- No Rust knowledge required
- Faster iteration (no compile step)
- Web-first (no WebAssembly export headaches)
Extending define* via Plugins
GWEN is built to stay lightweight at its core and grow through plugin extensions — not by bloating the engine itself.
Plugins can contribute typed properties to any define* helper via an extensions key. This keeps game code clean, avoids magic globals, and ensures each capability is opt-in.
How it works
A plugin declares the extension shape it provides:
// @djodjonx/gwen-plugin-physics2d — provides extensions.physics
export class Physics2DPlugin implements GwenPlugin {
readonly name = 'Physics2D';
readonly provides = { physics: physicsManager };
}Your game code then uses extensions directly inside definePrefab, defineScene, or defineUI:
export const PlayerPrefab = definePrefab({
name: 'Player',
extensions: {
physics: {
bodyType: 'kinematic',
radius: 20,
},
},
create: (api) => {
const id = api.createEntity();
api.addComponent(id, Position, { x: 240, y: 560 });
return id;
},
});export const EnemyScene = defineScene('Enemy', () => ({
extensions: {
physics: { gravity: 0 },
},
systems: [AiSystem, MovementSystem],
onEnter(api) {},
onExit(api) {},
}));export const PlayerUI = defineUI({
name: 'PlayerUI',
extensions: {
spriteAnim: {
atlas: '/sprites/player.png',
frame: { width: 32, height: 32, columns: 4 },
clips: {
idle: { frames: [0, 1, 2, 3], fps: 8, loop: true },
},
},
},
render(api, id) {
const pos = api.getComponent(id, Position);
if (!pos) return;
const animator = api.services.get('animator');
animator.draw(api.services.get('renderer').ctx, id, pos.x, pos.y);
},
});Why this design?
- Core stays minimal: the engine ships without physics, sprite animation, or audio baked in.
- No global config sprawl: each feature lives next to the entity/scene that uses it.
- Type-safe: after
gwen prepare, extension shapes are fully typed and auto-completed. - Composable: combine multiple plugin extensions on a single
define*without conflicts. - Discoverability: a developer reads one
definePrefaband immediately sees all behaviours attached to it.
This is what makes GWEN lightweight and extensible at the same time.
Design Goals
- Fast to start -
npx @djodjonx/create-gwen-app my-gameand you're coding in under a minute - Hard to break - TypeScript prevents most bugs
- Easy to understand - clear folder structure, no magic
- Scalable - from jam games to production apps
- Hackable - you own the code, customize anything
Core Beliefs
Explicit over implicit - We don't hide config or use global state.
Composition over inheritance - ECS naturally leads to reusable logic.
Convention over configuration - Standard structure means less setup.
Developer experience matters - If it's annoying, we fix it.
Next Steps
- Project Structure - How files are organized
- Components - Define your game data
- Systems - Implement gameplay logic