Skip to content
Back to skills

Excalibur

ASecurity

Excalibur.js is a TypeScript-first 2D game engine for the browser with actors, scenes, collision physics, animation, sound, input and a Tiled map plugin. Use when the user wants to build or debug a browser game with Excalibur, load Tiled maps, animate sprite sheets, handle keyboard or pointer input, or manage scenes and cameras.

  • 142 stars
  • 0 votes
  • 0 copies
  • 14 views
  • Added May 27, 2026
developmenttypescriptgobashnodegitapi

Works with

  • terminal
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add TerminalSkills/skills --skill excalibur --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Excalibur?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Excalibur
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/terminalskills-excalibur/badge)](https://www.skillsdirectory.com/skills/terminalskills-excalibur)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: excalibur
description: >-
  Excalibur.js is a TypeScript-first 2D game engine for the browser with actors, scenes, collision physics, animation, sound, input and a Tiled map plugin. Use when the user wants to build or debug a browser game with Excalibur, load Tiled maps, animate sprite sheets, handle keyboard or pointer input, or manage scenes and cameras.
license: Apache-2.0
compatibility: 'Node.js 22+ for tooling, any modern browser (WebGL), TypeScript 5+'
metadata:
  author: terminal-skills
  version: 1.1.0
  category: development
  repository: https://github.com/excaliburjs/Excalibur
  tags:
    - game-engine
    - typescript
    - 2d
    - browser-game
    - webgl
---

# Excalibur.js — TypeScript-First 2D Game Engine

## Overview

Excalibur is a free, BSD-2-Clause 2D game engine written in TypeScript for browser games. It models a game as an `Engine` running `Scene`s that hold `Actor`s (entities with graphics, collision and actions) and offers a built-in loader, camera, collision and physics, animations, sound, input, and a separate Tiled plugin. This skill targets 0.32.x (`excalibur` and `@excaliburjs/plugin-tiled` 0.32.0 were the latest on npm when checked). The engine is pre-1.0 and breaks APIs between minor versions, so pin the version and read the changelog when upgrading; code written for 0.2x will not compile unchanged.

## Instructions

### Step 1: Install and scaffold

```bash
npm create vite@latest sky-runner -- --template vanilla-ts
cd sky-runner
npm install excalibur
npm install --save-exact @excaliburjs/plugin-tiled   # its peer range is excalibur ~0.32.0, keep both on the same minor
npm run dev
```

Put images and maps in `public/` (for example `public/images/hero.png`, `public/maps/level-1.tmx`) so `/images/hero.png` URLs resolve.

### Step 2: Resources and loader

Every asset, including a Tiled map, must be added to the `Loader` and loaded before use:

```typescript
// src/resources.ts
import { ImageSource, Loader } from "excalibur";
import { TiledResource } from "@excaliburjs/plugin-tiled";

export const Resources = {
  HeroSheet: new ImageSource("/images/hero.png"),
  LevelOne: new TiledResource("/maps/level-1.tmx"),
};

export const loader = new Loader(Object.values(Resources));
```

### Step 3: Engine setup

```typescript
// src/main.ts
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 pixels, no smoothing
  pixelRatio: 2,
  fixedUpdateFps: 60,      // fixed-step updates for deterministic physics
});

game.addScene("level-one", new LevelOne());
game.start(loader).then(() => game.goToScene("level-one"));
```

### Step 4: Actors, animation and input

```typescript
// src/actors/Player.ts
import { Actor, Animation, CollisionType, Color, Engine, Keys, Side, SpriteSheet, vec } 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) {
    const sheet = SpriteSheet.fromImageSource({
      image: Resources.HeroSheet,
      grid: { rows: 4, columns: 6, spriteWidth: 16, spriteHeight: 24 },
    });
    this.graphics.add("idle", Animation.fromSpriteSheet(sheet, [0, 1, 2, 3], 200));
    this.graphics.add("run", Animation.fromSpriteSheet(sheet, [6, 7, 8, 9, 10, 11], 100));
    this.graphics.add("jump", Animation.fromSpriteSheet(sheet, [12, 13], 150));
    this.graphics.use("idle");

    this.on("postcollision", (evt) => {
      if (evt.side === Side.Bottom) this.isGrounded = true;
    });
  }

  onPreUpdate(engine: Engine, elapsedMs: 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 (this.isGrounded) {
      this.graphics.use(moving ? "run" : "idle");
    }
  }

  takeDamage(amount: number) {
    this.health -= amount;
    this.actions.blink(100, 100, 5);
    if (this.health <= 0) this.scene?.engine.goToScene("game-over");
  }
}
```

Since 0.31 update hooks and events receive `elapsedMs` (milliseconds), not `delta`, and collision events refer to colliders: reach the other entity with `evt.other.owner`.

### Step 5: Scenes, Tiled maps, camera

```typescript
// src/scenes/LevelOne.ts
import { Scene } from "excalibur";
import { Resources } from "../resources";
import { Player } from "../actors/Player";
import { Coin } from "../actors/Coin";

export class LevelOne extends Scene {
  onActivate() {
    const map = Resources.LevelOne;          // already loaded by the Loader
    map.addToScene(this);                    // tile layers, colliders, camera wiring

    const spawn = map.getObjectsByName("PlayerSpawn")[0];
    const player = new Player(spawn.x, spawn.y);
    this.add(player);

    map.getObjectsByClassName("coin").forEach((obj) => this.add(new Coin(obj.x, obj.y)));

    this.camera.strategy.elasticToActor(player, 0.8, 0.9);
    this.camera.zoom = 2;
  }
}
```

Tiled renamed an object's "type" to "class", so the plugin queries are `getObjectsByClassName`, `getObjectsByName` and `getObjectsByProperty`. Look up spawn points defensively: `getObjectsByName` returns an empty array if the name is wrong.

### Step 6: Collision and triggers

```typescript
// src/actors/Coin.ts
import { Actor, CollisionType, Color, vec } from "excalibur";
import { Player } from "./Player";

export class Coin extends Actor {
  constructor(x: number, y: number) {
    super({ pos: vec(x, y), width: 8, height: 8, collisionType: CollisionType.Passive, color: Color.Yellow });
  }
  onInitialize() {
    this.on("collisionstart", (evt) => {
      if (evt.other.owner instanceof Player) this.kill();
    });
  }
}
```

Collision types: `Active` (moves, collides), `Fixed` (static ground), `Passive` (reports overlaps, no physical response), `PreventCollision`.

## Examples

### Example 1: "Start a new platformer project and get a green square moving"

```bash
npm create vite@latest sky-runner -- --template vanilla-ts
cd sky-runner && npm install excalibur && npm run dev
```

Then add Step 3's `main.ts` and `Player` without the sprite sheet code. Result: Vite serves `http://localhost:5173`, the canvas fills the window, and a green 16x24 actor moves with the arrow keys.

### Example 2: "Load my Tiled level and make coins disappear when the hero touches them"

Add `LevelOne: new TiledResource("/maps/level-1.tmx")` to the loader, mark coin objects in Tiled with class `coin`, and use the `LevelOne` scene and `Coin` actor above. Result: the map renders, the camera follows the player, and each coin removes itself on contact. Type-check with `npx tsc --noEmit`; a missing asset shows up as a failed request in the browser console and the loader screen stays up.

## Guidelines

- Put game logic in `onInitialize`, `onPreUpdate` and `onPostUpdate`, not the constructor; the engine and scene are not available yet in the constructor.
- Load every asset through the `Loader`; calling `addToScene` on a Tiled map that has not been loaded fails.
- Keep `excalibur` and `@excaliburjs/plugin-tiled` on the same minor version; mismatches fail at install or runtime.
- Excalibur is pre-1.0: removed or changed APIs appear in each minor release (for example `Timer` takes only an options object, `Vector.normalize()` of a zero vector returns `(0,0)`). Search the CHANGELOG on GitHub before copying older tutorial code.
- Use `engine.goToScene("name", { sceneActivationData })` to pass data between scenes.
- Chain effects with the Actions API: `actor.actions.moveTo(vec(100, 100), 200).delay(500).fade(0, 1000)`.
- For heavy 3D, huge particle counts or a full editor workflow, a larger engine (Phaser, Godot web export) may fit better.

Files in this skill

  • SKILL.md5.8 KB
  • _scores.json1.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.