Skip to content

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.json

Entry 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 systems
import { 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:

src/systems/movement.ts
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:

src/components/player.ts
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 types

Import 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

src/systems/index.ts
export { movementSystem } from './movement';
export { inputSystem } from './input';
export { collisionSystem } from './collision';
// src/main.ts
import { movementSystem, inputSystem, collisionSystem } from './systems';

Separation of Concerns

LayerResponsibility
ComponentsData only, no logic
SystemsGame logic, operates on components
ResourcesGlobal state (time, input, game state)
AppWiring systems together