Excalibur.js — TypeScript-First 2D Game Engine
You are an expert in Excalibur.js, the TypeScript-first 2D game engine built for the web. You help developers build browser games using Excalibur's Actor system, Scene management, Tiled integration, physics, animation, sound, and input handling — with first-class TypeScript support, excellent documentation, and a focus on developer experience over raw performance.
Core Capabilities
Game Setup
// src/main.ts — Excalibur game
import { Engine, DisplayMode, Color } from "excalibur";
import { LevelOne } from "./scenes/LevelOne";
import { loader } from "./resources";
const game = new Engine({
width: 800,
height: 600,
displayMode: DisplayMode.FitScreen,
backgroundColor: Color.fromHex("#1a1a2e"),
pixelArt: true, // Crisp rendering
pixelRatio: 2,
fixedUpdateFps: 60, // Deterministic physics
});
game.addScene("level-one", new LevelOne());
game.start(loader).then(() => { // Preload assets
game.goToScene("level-one");
});
Actors and Components
// src/actors/Player.ts
import { Actor, Color, vec, Keys, CollisionType, Animation, SpriteSheet } from "excalibur";
import { Resources } from "../resources";
export class Player extends Actor {
private speed = 200;
private jumpForce = -400;
private health = 3;
private isGrounded = false;
constructor(x: number, y: number) {
super({
pos: vec(x, y),
width: 16,
height: 24,
collisionType: CollisionType.Active, // Moves and collides
color: Color.Green,
});
}
onInitialize(engine: Engine) {
// Sprite sheet animations
const spriteSheet = SpriteSheet.fromImageSource({
image: Resources.HeroSheet,
grid: { rows: 4, columns: 6, spriteWidth: 16, spriteHeight: 24 },
});
const idle = Animation.fromSpriteSheet(spriteSheet, [0, 1, 2, 3], 200);
const run = Animation.fromSpriteSheet(spriteSheet, [6, 7, 8, 9, 10, 11], 100);
const jump = Animation.fromSpriteSheet(spriteSheet, [12, 13], 150);
this.graphics.add("idle", idle);
this.graphics.add("run", run);
this.graphics.add("jump", jump);
this.graphics.use("idle");
// Ground detection
this.on("postcollision", (evt) => {
if (evt.side === "Bottom") this.isGrounded = true;
});
}
onPreUpdate(engine: Engine, delta: number) {
const kb = engine.input.keyboard;
let moving = false;
if (kb.isHeld(Keys.ArrowLeft)) {
this.vel.x = -this.speed;
this.graphics.flipHorizontal = true;
moving = true;
} else if (kb.isHeld(Keys.ArrowRight)) {
this.vel.x = this.speed;
this.graphics.flipHorizontal = false;
moving = true;
} else {
this.vel.x = 0;
}
if (kb.wasPressed(Keys.Space) && this.isGrounded) {
this.vel.y = this.jumpForce;
this.isGrounded = false;
this.graphics.use("jump");
} else if (moving) {
this.graphics.use("run");
} else {
this.graphics.use("idle");
}
}
takeDamage(amount: number) {
this.health -= amount;
// Flash red
this.actions.blink(100, 100, 5);
if (this.health <= 0) {
this.scene?.engine.goToScene("game-over");
}
}
}
Scenes and Tiled Maps
// src/scenes/LevelOne.ts
import { Scene, Engine, TileMap, vec } from "excalibur";
import { TiledResource } from "@excaliburjs/plugin-tiled";
import { Player } from "../actors/Player";
import { Coin } from "../actors/Coin";
export class LevelOne extends Scene {
private tiledMap!: TiledResource;
onInitialize(engine: Engine) {
this.tiledMap = new TiledResource("/maps/level-1.tmx");
// Add tilemap to scene
this.tiledMap.addToScene(this);
// Get spawn point from Tiled object layer
const spawnPoint = this.tiledMap.getObjectsByName("PlayerSpawn")[0];
const player = new Player(spawnPoint.x, spawnPoint.y);
this.add(player);
// Camera follows player
this.camera.strategy.elasticToActor(player, 0.8, 0.9);
this.camera.zoom = 2;
// Spawn coins from object layer
this.tiledMap.getObjectsByType("coin").forEach((obj) => {
this.add(new Coin(obj.x, obj.y));
});
}
}
Installation
npm install excalibur
npm install @excaliburjs/plugin-tiled # Tiled map support
Best Practices
- TypeScript always — Excalibur is built in TypeScript; use it for full autocompletion and type safety
- Actor lifecycle — Override
onInitialize, onPreUpdate, onPostUpdate instead of constructor for game logic
- Collision types — Use
Active for moving entities, Fixed for static platforms, Passive for triggers/sensors
- Scene transitions —
engine.goToScene("name", { sceneActivationData }) to pass data between scenes
- Tiled plugin — Use the official Tiled plugin for level design; supports tile layers, object layers, and custom properties
- Actions API — Chain animations:
actor.actions.moveTo(100, 100, 200).delay(500).fade(0, 1000) for cutscenes and effects
- Event system — Use typed events (
on("precollision"), on("kill")) for clean game logic
- Resource loading — Define all assets in a loader; Excalibur shows a loading screen automatically
1---2name: excalibur3description: You are an expert in Excalibur.js, the TypeScript-first 2D game engine built for the web. You help developers build browser games using Excalibur's Actor system, Scene management, Tiled integration, physics, animation, sound, and input handling — with first-class TypeScript support, excellent documentation, and a focus on developer experience over raw performance.4license: Apache-2.05---67# Excalibur.js — TypeScript-First 2D Game Engine89You are an expert in Excalibur.js, the TypeScript-first 2D game engine built for the web. You help developers build browser games using Excalibur's Actor system, Scene management, Tiled integration, physics, animation, sound, and input handling — with first-class TypeScript support, excellent documentation, and a focus on developer experience over raw performance.1011## Core Capabilities1213### Game Setup1415```typescript16// src/main.ts — Excalibur game17import { Engine, DisplayMode, Color } from "excalibur";18import { LevelOne } from "./scenes/LevelOne";19import { loader } from "./resources";2021const game = new Engine({22 width: 800,23 height: 600,24 displayMode: DisplayMode.FitScreen,25 backgroundColor: Color.fromHex("#1a1a2e"),26 pixelArt: true, // Crisp rendering27 pixelRatio: 2,28 fixedUpdateFps: 60, // Deterministic physics29});3031game.addScene("level-one", new LevelOne());32game.start(loader).then(() => { // Preload assets33 game.goToScene("level-one");34});35```3637### Actors and Components3839```typescript40// src/actors/Player.ts41import { Actor, Color, vec, Keys, CollisionType, Animation, SpriteSheet } from "excalibur";42import { Resources } from "../resources";4344export class Player extends Actor {45 private speed = 200;46 private jumpForce = -400;47 private health = 3;48 private isGrounded = false;4950 constructor(x: number, y: number) {51 super({52 pos: vec(x, y),53 width: 16,54 height: 24,55 collisionType: CollisionType.Active, // Moves and collides56 color: Color.Green,57 });58 }5960 onInitialize(engine: Engine) {61 // Sprite sheet animations62 const spriteSheet = SpriteSheet.fromImageSource({63 image: Resources.HeroSheet,64 grid: { rows: 4, columns: 6, spriteWidth: 16, spriteHeight: 24 },65 });6667 const idle = Animation.fromSpriteSheet(spriteSheet, [0, 1, 2, 3], 200);68 const run = Animation.fromSpriteSheet(spriteSheet, [6, 7, 8, 9, 10, 11], 100);69 const jump = Animation.fromSpriteSheet(spriteSheet, [12, 13], 150);7071 this.graphics.add("idle", idle);72 this.graphics.add("run", run);73 this.graphics.add("jump", jump);74 this.graphics.use("idle");7576 // Ground detection77 this.on("postcollision", (evt) => {78 if (evt.side === "Bottom") this.isGrounded = true;79 });80 }8182 onPreUpdate(engine: Engine, delta: number) {83 const kb = engine.input.keyboard;84 let moving = false;8586 if (kb.isHeld(Keys.ArrowLeft)) {87 this.vel.x = -this.speed;88 this.graphics.flipHorizontal = true;89 moving = true;90 } else if (kb.isHeld(Keys.ArrowRight)) {91 this.vel.x = this.speed;92 this.graphics.flipHorizontal = false;93 moving = true;94 } else {95 this.vel.x = 0;96 }9798 if (kb.wasPressed(Keys.Space) && this.isGrounded) {99 this.vel.y = this.jumpForce;100 this.isGrounded = false;101 this.graphics.use("jump");102 } else if (moving) {103 this.graphics.use("run");104 } else {105 this.graphics.use("idle");106 }107 }108109 takeDamage(amount: number) {110 this.health -= amount;111 // Flash red112 this.actions.blink(100, 100, 5);113 if (this.health <= 0) {114 this.scene?.engine.goToScene("game-over");115 }116 }117}118```119120### Scenes and Tiled Maps121122```typescript123// src/scenes/LevelOne.ts124import { Scene, Engine, TileMap, vec } from "excalibur";125import { TiledResource } from "@excaliburjs/plugin-tiled";126import { Player } from "../actors/Player";127import { Coin } from "../actors/Coin";128129export class LevelOne extends Scene {130 private tiledMap!: TiledResource;131132 onInitialize(engine: Engine) {133 this.tiledMap = new TiledResource("/maps/level-1.tmx");134135 // Add tilemap to scene136 this.tiledMap.addToScene(this);137138 // Get spawn point from Tiled object layer139 const spawnPoint = this.tiledMap.getObjectsByName("PlayerSpawn")[0];140 const player = new Player(spawnPoint.x, spawnPoint.y);141 this.add(player);142143 // Camera follows player144 this.camera.strategy.elasticToActor(player, 0.8, 0.9);145 this.camera.zoom = 2;146147 // Spawn coins from object layer148 this.tiledMap.getObjectsByType("coin").forEach((obj) => {149 this.add(new Coin(obj.x, obj.y));150 });151 }152}153```154155## Installation156157```bash158npm install excalibur159npm install @excaliburjs/plugin-tiled # Tiled map support160```161162## Best Practices1631641. **TypeScript always** — Excalibur is built in TypeScript; use it for full autocompletion and type safety1652. **Actor lifecycle** — Override `onInitialize`, `onPreUpdate`, `onPostUpdate` instead of constructor for game logic1663. **Collision types** — Use `Active` for moving entities, `Fixed` for static platforms, `Passive` for triggers/sensors1674. **Scene transitions** — `engine.goToScene("name", { sceneActivationData })` to pass data between scenes1685. **Tiled plugin** — Use the official Tiled plugin for level design; supports tile layers, object layers, and custom properties1696. **Actions API** — Chain animations: `actor.actions.moveTo(100, 100, 200).delay(500).fade(0, 1000)` for cutscenes and effects1707. **Event system** — Use typed events (`on("precollision")`, `on("kill")`) for clean game logic1718. **Resource loading** — Define all assets in a loader; Excalibur shows a loading screen automatically