Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
92350f0
docs: clarify product entry and rebuild the onboarding path
Exoridus Sep 24, 2026
39d0b01
chore: add branch-only documentation review inventory
Exoridus Sep 24, 2026
89a12ef
docs: separate asset lifetimes and catalogs, correct scene lifecycle …
Exoridus Sep 24, 2026
2e93ed2
chore: exercise documentation contracts in the isolated review workflow
Exoridus Sep 24, 2026
7e9ad0e
docs: replace stale backend, diagnostics and shipping guidance
Exoridus Sep 24, 2026
bf9ca79
docs: rebuild physics, particles and lighting around current scene ow…
Exoridus Sep 24, 2026
1bb833d
docs: consolidate HUD, split-view and audio-visualization workflows
Exoridus Sep 24, 2026
a5b6a7c
chore: materialize and verify the consolidated learning hierarchy
Exoridus Sep 24, 2026
6126914
chore: run isolated documentation checks without duplicate workstatio…
Exoridus Sep 24, 2026
43de859
docs: implement learning taxonomy and canonical example navigation
github-actions[bot] Sep 24, 2026
dfe53a9
chore: record observed documentation validation results
github-actions[bot] Sep 24, 2026
ffedbee
docs: normalize package entry points and benchmark provenance
Exoridus Sep 24, 2026
46cb236
chore: prepare source-verified site and API documentation materializa…
Exoridus Sep 24, 2026
bc97d32
chore: generate API docs and validate site integration on the review …
Exoridus Sep 24, 2026
e7610ee
docs: restore branded shields and polish package discovery
Exoridus Sep 26, 2026
fed6ae1
docs: polish entry points and materialize guide navigation
github-actions[bot] Sep 26, 2026
231ece9
chore: record documentation integration evidence
github-actions[bot] Sep 26, 2026
5c9c53a
docs: repair verified examples and check built navigation
Exoridus Sep 26, 2026
fd76b21
docs: correct package examples and contextual guide navigation
github-actions[bot] Sep 26, 2026
30a7694
chore: record built documentation checks
github-actions[bot] Sep 26, 2026
2f98021
docs: finalize navigation checks and protect complete README examples
Exoridus Sep 26, 2026
73c20b1
docs: preserve guide entry points and guard README examples
github-actions[bot] Sep 26, 2026
05d0f33
chore: record final documentation verification
github-actions[bot] Sep 26, 2026
d70f8e5
chore: remove temporary documentation migration tooling
github-actions[bot] Sep 26, 2026
26a36d0
docs: preserve the particles minimal-example section anchor
Exoridus Sep 26, 2026
ec3a84a
docs(react): register scenes and start a scene in the README examples
Sep 26, 2026
d74472d
fix(assets): reject createScope on a destroyed loader scope
Sep 26, 2026
6d5935f
ci: run the README example check when a checked README changes
Sep 26, 2026
54ac926
docs: restore API reference contracts and correct the full bundle con…
Sep 26, 2026
846eb9b
docs(site): share one localized guide chapter redirect
Sep 26, 2026
21006d9
test(ci): use a prose-only file for the docs-only plan cases
Sep 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 76 additions & 131 deletions README.md

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions examples/guides/audio-reactive-visualization/spectrum-history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// #region guide:spectrum-history
import { DataTexture, TextureFormat } from '@codexo/exojs';

export class SpectrumHistory {
readonly texture = new DataTexture({ width: 256, height: 64, format: TextureFormat.R8 });
private column = 0;

get nextColumn(): number {
return this.column;
}

write(bands: Uint8Array): void {
if (bands.length !== 64) {
throw new Error('SpectrumHistory expects 64 byte-valued bands.');
}

for (let row = 0; row < 64; row++) {
this.texture.buffer[row * 256 + this.column] = bands[row];
}
this.texture.commitRect(this.column, 0, 1, 64);
this.column = (this.column + 1) % 256;
}

destroy(): void {
this.texture.destroy();
}
}
// #endregion guide:spectrum-history
31 changes: 31 additions & 0 deletions examples/guides/audio-reactive-visualization/spectrum-scene.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// #region guide:spectrum-scene
import { Color, Graphics, type RenderingContext, Scene } from '@codexo/exojs';
import { AudioAnalyser } from '@codexo/exojs-audio-fx';

export class SpectrumScene extends Scene {
private analyser!: AudioAnalyser;
private readonly bars = new Graphics();
private readonly barColor = new Color(90, 180, 240);

override init(): void {
this.analyser = this.track(new AudioAnalyser({ source: this.app.audio.music, fftSize: 1024 }));
this.root.addChild(this.bars);
}

override update(): void {
const levels = this.analyser.getSpectrumLog(undefined, { bands: 32 });
const width = this.app.width / levels.length;

this.bars.clear();
this.bars.fillColor = this.barColor;
for (let index = 0; index < levels.length; index++) {
const height = (levels[index] / 255) * this.app.height * 0.6;
this.bars.drawRectangle(index * width, this.app.height - height, Math.max(1, width - 2), height);
}
}

override draw(context: RenderingContext): void {
context.render(this.root);
}
}
// #endregion guide:spectrum-scene
19 changes: 19 additions & 0 deletions examples/guides/coordinates-and-views/split-views.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// #region guide:split-views
import { type RenderingContext, type RenderNode, View } from '@codexo/exojs';

export const createSplitViews = (width: number, height: number): { left: View; right: View } => ({
left: new View(0, 0, width / 2, height).setViewport(0, 0, 0.5, 1),
right: new View(0, 0, width / 2, height).setViewport(0.5, 0, 0.5, 1),
});

export const drawSplitWorld = (context: RenderingContext, world: RenderNode, left: View, right: View): void => {
context.render(world, { view: left });
context.render(world, { view: right });
};
// #endregion guide:split-views

// #region guide:pointer-world
import type { PointLike } from '@codexo/exojs';

export const pointerInWorld = (view: View, pointer: PointLike): PointLike => view.screenToWorld(pointer.x, pointer.y);
// #endregion guide:pointer-world
31 changes: 31 additions & 0 deletions examples/guides/lighting/basic-lightmap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// #region guide:basic-lightmap
import { Application, Color, Graphics, type RenderingContext, Scene } from '@codexo/exojs';
import { LightmapLighting, PointLight } from '@codexo/exojs-lighting';

class LightingScene extends Scene {
override init(): void {
const lighting = new LightmapLighting(this.app, { ambient: new Color(25, 25, 35) });
const floor = new Graphics();
const lamp = new PointLight({ radius: 360, color: new Color(255, 190, 100) });

this.systems.add(lighting);
floor.fillColor = new Color(180, 190, 210);
floor.drawRectangle(0, 0, this.app.width, this.app.height);
lamp.setPosition(this.app.width / 2, this.app.height / 2);
this.root.addChild(floor, lamp);
lighting.add(lamp);
}

override draw(context: RenderingContext): void {
context.render(this.root);
}
}

const app = new Application({
scenes: { LightingScene },
canvas: { width: 800, height: 600, mount: 'body' },
clearColor: Color.black,
});

await app.start(LightingScene);
// #endregion guide:basic-lightmap
44 changes: 44 additions & 0 deletions examples/guides/loading-and-resources/basic-loading.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// #region guide:required-scene
import { type RenderingContext, Scene, Sprite, type Texture } from '@codexo/exojs';

export class HeroScene extends Scene {
private texture!: Texture;

override async load(): Promise<void> {
this.texture = await this.loader.load('image/hero.png');
}

override init(): void {
const hero = new Sprite(this.texture);

hero.setAnchor(0.5).setPosition(this.app.width / 2, this.app.height / 2);
this.root.addChild(hero);
}

override draw(context: RenderingContext): void {
context.render(this.root);
}
}
// #endregion guide:required-scene

// #region guide:scope-ownership
import type { LoaderScope } from '@codexo/exojs';

export const demonstrateClaims = async (parent: LoaderScope): Promise<void> => {
const level = parent.createScope({ name: 'level' });
const hud = parent.createScope({ name: 'hud' });

try {
const texture = level.get('image/hero.png');

hud.get('image/hero.png');
await texture.loaded;
level.destroy();

console.log(texture.ready); // true: the HUD still holds a claim
} finally {
level.destroy(); // repeated destruction is safe
hud.destroy();
}
};
// #endregion guide:scope-ownership
30 changes: 30 additions & 0 deletions examples/guides/loading-and-resources/catalogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// #region guide:catalog-definition
import { Asset, Assets } from '@codexo/exojs';

interface Settings {
startLevel: string;
}

export const SharedAssets = Assets.from({
logo: 'image/logo.png',
settings: Asset.type<Settings>('json', 'data/settings.json'),
});
// #endregion guide:catalog-definition

// #region guide:catalog-result
import type { LoaderScope } from '@codexo/exojs';

export const readStartLevel = async (scope: LoaderScope): Promise<string> => {
const loaded = await scope.load(SharedAssets);

console.log(SharedAssets.settings.value.startLevel);
return loaded.settings.startLevel;
};
// #endregion guide:catalog-result

// #region guide:catalog-composition
const LevelLocal = Assets.from({ ground: 'image/day.png' });

export const DayAssets = Assets.compose(SharedAssets, LevelLocal);
export const NightAssets = Assets.extend(DayAssets, { ground: 'image/night.png' });
// #endregion guide:catalog-composition
43 changes: 43 additions & 0 deletions examples/guides/particles/basic-emitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// #region guide:basic-emitter
import { Application, Color, type RenderingContext, Scene } from '@codexo/exojs';
import { ApplyForce, ConeDirection, Constant, Curve, particlesExtension, ParticleSystem, RateSpawn, ScaleOverLifetime } from '@codexo/exojs-particles';

class FountainScene extends Scene {
private particles!: ParticleSystem;

override init(): void {
this.particles = new ParticleSystem({ capacity: 512 });
this.particles.setPosition(this.app.width / 2, this.app.height - 60);
this.particles.addSpawnModule(
new RateSpawn({
rate: new Constant(80),
lifetime: new Constant(2),
velocity: new ConeDirection(-Math.PI / 2, Math.PI / 6, 120, 220),
}),
);
this.particles.addUpdateModule(new ApplyForce(0, 180));
this.particles.addUpdateModule(
new ScaleOverLifetime(
new Curve([
{ t: 0, v: 6 },
{ t: 1, v: 0 },
]),
),
);
this.systems.add(this.particles);
}

override draw(context: RenderingContext): void {
context.render(this.particles);
}
}

const app = new Application({
scenes: { FountainScene },
extensions: [particlesExtension],
canvas: { width: 800, height: 600, mount: 'body' },
clearColor: Color.black,
});

await app.start(FountainScene);
// #endregion guide:basic-emitter
43 changes: 43 additions & 0 deletions examples/guides/physics-basics/falling-box.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// #region guide:falling-box
import { Application, Color, Graphics, type RenderingContext, Scene, SystemOrder } from '@codexo/exojs';
import { BoxShape, PhysicsWorld } from '@codexo/exojs-physics';

class FallingBoxScene extends Scene {
override init(): void {
const world = new PhysicsWorld({ gravity: { x: 0, y: 980 } });
const floor = new Graphics();
const box = new Graphics();

this.systems.add(world, { order: SystemOrder.Physics });
floor.fillColor = new Color(90, 110, 140);
floor.drawRectangle(-350, -16, 700, 32);
box.fillColor = Color.white;
box.drawRectangle(-20, -20, 40, 40);

world.attach(floor, {
type: 'static',
position: { x: 400, y: 560 },
shape: new BoxShape(700, 32),
});
world.attach(box, {
type: 'dynamic',
position: { x: 400, y: 100 },
shape: new BoxShape(40, 40),
restitution: 0.2,
});
this.root.addChild(floor, box);
}

override draw(context: RenderingContext): void {
context.render(this.root);
}
}

const app = new Application({
scenes: { FallingBoxScene },
canvas: { width: 800, height: 600, mount: 'body' },
clearColor: new Color(20, 24, 32),
});

await app.start(FallingBoxScene);
// #endregion guide:falling-box
27 changes: 27 additions & 0 deletions examples/guides/ui/basic-hud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// #region guide:basic-hud
import { Application, Button, Color, Label, ProgressBar, Scene } from '@codexo/exojs';

class HudScene extends Scene {
override init(): void {
const score = new Label('Score: 0', { fontSize: 24 });
const health = new ProgressBar({ width: 220, height: 16, value: 1 });
const damage = new Button({ label: 'Take damage', width: 180, height: 44 });

score.anchorIn(this.ui, 'top-left', 20, 20);
health.anchorIn(this.ui, 'top-left', 20, 56);
damage.anchorIn(this.ui, 'bottom-right', -20, -20);
damage.onClick.add(() => {
health.value = Math.max(0, health.value - 0.1);
});
this.ui.addChild(score, health, damage);
}
}

const app = new Application({
scenes: { HudScene },
canvas: { width: 800, height: 600, mount: 'body' },
clearColor: new Color(20, 24, 32),
});

await app.start(HudScene);
// #endregion guide:basic-hud
58 changes: 24 additions & 34 deletions packages/create-exo-app/README.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,43 @@
# create-exo-app

Official starter for [ExoJS](https://github.com/Exoridus/ExoJS).
Create an ExoJS application with Vite, TypeScript, and a working scene. Use `minimal` to learn the runtime before adding optional systems.

## Usage

```bash
npm create exo-app@latest my-game
```

Or pick a template:

```bash
```sh
npm create exo-app@latest my-game -- --template minimal
npm create exo-app@latest my-game -- --template game-starter
npm create exo-app@latest my-game -- --template platformer
npm create exo-app@latest my-game -- --template top-down
npm create exo-app@latest my-game -- --template ui-app
npm create exo-app@latest my-game -- --template audio-reactive
```

Then:

```bash
cd my-game
npm install
npm run dev
```

Omit `--template` to choose interactively. The generated project belongs to you: edit its source, commit its lockfile, and keep compatible ExoJS package versions pinned.

## Templates

| Template | Description |
| ---------------- | -------------------------------------------------------------------------------------- |
| `minimal` | Smallest TypeScript ExoJS app — one `Scene`, one rotating box |
| `game-starter` | Keyboard-controlled player, `GameScene` + `GameOverScene`, score HUD |
| `platformer` | Side-scroller on `@codexo/exojs-physics`: coyote time, jump buffer, camera follow |
| `top-down` | Tilemap + physics + click-to-move pathfinding, procedurally built or loaded from Tiled |
| `ui-app` | Settings screen built from the core UI widgets |
| `audio-reactive` | `AudioAnalyser`-driven frequency bar visualiser; click-to-start gesture |
| Template | Starting point |
| ---------------- | ------------------------------------------------------------------------------ |
| `minimal` | One visible animated object in one scene. |
| `game-starter` | Keyboard-controlled gameplay and a game-over scene. |
| `platformer` | Side-scrolling physics, camera follow, and jump handling. |
| `top-down` | Tilemaps, collision, and click-to-move pathfinding; procedural or Tiled input. |
| `ui-app` | A settings interface built from Core UI widgets. |
| `audio-reactive` | Shapes driven by live audio analysis. |

## CLI options
Availability follows the scaffolder version you run. The repository's `next` branch can contain a template that is not yet in an older npm release. A specialized template includes more systems; it is not a prerequisite for using the engine.

```
create-exo-app <project-name> [--template <name>] [--force]
## Locate the application code

--template minimal | game-starter | platformer | top-down | ui-app | audio-reactive (default: minimal)
--force overwrite an existing non-empty directory
`src/main.ts` creates the application, registers scene classes, mounts the canvas, and awaits startup. Scene behavior lives in `src/scenes/`. Files under `public/assets/` are served unchanged; `public/` is not part of their request URL.

```sh
npm run build
npm run preview
```

When run interactively (TTY) without `--template`, the CLI prompts for a template choice. In non-TTY / CI environments it defaults to `minimal` automatically.
The build writes the static application to `dist/`. Preview checks that output locally; verify the actual hosted output as well, especially loader base paths and optional browser capabilities.

## Documentation

[Setup and project layout](https://exoridus.github.io/ExoJS/en/guide/getting-started/setup/) · [Your first scene](https://exoridus.github.io/ExoJS/en/guide/getting-started/your-first-scene/) · [Build and deploy](https://exoridus.github.io/ExoJS/en/guide/shipping/deployment/)

## License

Expand Down
Loading
Loading