Project Structure
This guide explains how to structure your ESEngine game project and the SDK architecture.
Game Project Structure
A typical ESEngine game project:
my-game/├── src/│ ├── main.ts # Entry point│ ├── systems/ # Game systems│ │ ├── movement.ts│ │ ├── collision.ts│ │ └── input.ts│ └── components/ # Custom components│ ├── player.ts│ └── enemy.ts├── assets/ # Game assets│ ├── sprites/│ └── audio/├── build/ # Build output│ ├── main.js│ ├── esengine.js # WASM loader│ └── esengine.wasm # WASM binary├── index.html # HTML entry├── package.json└── tsconfig.jsonEntry Point
Your src/main.ts exports a main function that receives the WASM module:
import { createWebApp, defineSystem, Schedule, ... } from 'esengine';import type { ESEngineModule } from 'esengine';
// Import your systemsimport { movementSystem } from './systems/movement';import { inputSystem } from './systems/input';
export async function main(Module: ESEngineModule): Promise<void> { const app = createWebApp(Module);
// Add systems app.addSystemToSchedule(Schedule.Startup, setupSystem); app.addSystemToSchedule(Schedule.Update, inputSystem); app.addSystemToSchedule(Schedule.Update, movementSystem);
app.run();}Organizing Systems
Create separate files for each system:
import { defineSystem, Res, Time, Query, LocalTransform, Velocity } from 'esengine';
export const movementSystem = defineSystem( [Res(Time), Query(LocalTransform, Velocity)], (time, query) => { for (const [entity, transform, velocity] of query) { transform.position.x += velocity.linear.x * time.delta; transform.position.y += velocity.linear.y * time.delta; } });Organizing Components
Define custom components in separate files:
import { defineComponent, defineTag } from 'esengine';
export const Player = defineTag('Player');
export const Health = defineComponent('Health', { current: 100, max: 100});
export const MoveSpeed = defineComponent('MoveSpeed', { value: 200});HTML Setup
Your index.html loads the WASM and your game:
<!DOCTYPE html><html><head> <title>My Game</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; } </style></head><body> <canvas id="canvas"></canvas> <script type="module"> import ESEngineModule from './build/esengine.js'; import { main } from './build/main.js';
ESEngineModule().then(Module => { main(Module); }); </script></body></html>SDK Architecture
The ESEngine SDK is organized as a single-entry package:
esengine/├── dist/│ ├── index.js # Main entry│ ├── index.d.ts # TypeScript types│ ├── wasm.js # WASM types export│ └── wasm.d.ts└── src/ ├── index.ts # Public exports ├── types.ts # Entity, Vec2, Vec3, etc. ├── component.ts # Components ├── resource.ts # Resources ├── query.ts # Query system ├── commands.ts # Commands ├── system.ts # System definitions ├── world.ts # World management ├── app.ts # App and createWebApp └── wasm.ts # WASM interface typesImport from Single Entry
// All imports from 'esengine'import { // App App, createWebApp,
// ECS defineSystem, Schedule, Commands, Query, defineComponent, defineTag,
// Resources Res, ResMut, defineResource, Time, Input,
// Builtin Components LocalTransform, Sprite, Camera, Velocity,
// Types type Entity, vec2, vec3, vec4, quat} from 'esengine';Best Practices
File Organization
- Keep systems small and focused (one concern per system)
- Group related components together
- Use barrel exports (
index.ts) for cleaner imports
Code Structure
export { movementSystem } from './movement';export { inputSystem } from './input';export { collisionSystem } from './collision';
// src/main.tsimport { movementSystem, inputSystem, collisionSystem } from './systems';Separation of Concerns
| Layer | Responsibility |
|---|---|
| Components | Data only, no logic |
| Systems | Game logic, operates on components |
| Resources | Global state (time, input, game state) |
| App | Wiring systems together |