From aiot-ai-agent-skills
Build high-performance HTML5 games with PixiJS 8.x (WebGL/WebGPU). Use when: developing browser games, 2D game engines, interactive applications, sprite animation, game loops, collision detection, tilemap rendering, scene management, or integrating game libraries (pixi-viewport, @pixi/sound, @pixi/tilemap, @pixi/ui, matter-js). Triggers on: pixi, pixijs, pixi.js, HTML5 game, browser game, web game, 2D game, game loop, game state, scene graph, sprite sheet, texture atlas, collision, tilemap, camera follow, object pooling, particle system, WebGL, WebGPU, fixed timestep.
How this skill is triggered — by the user, by Claude, or both
Slash command
/aiot-ai-agent-skills:pixijsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build high-performance 2D browser games with PixiJS 8.x (WebGL2 / WebGPU).
Build high-performance 2D browser games with PixiJS 8.x (WebGL2 / WebGPU).
Read these on demand -- not upfront:
| File | When to read |
|---|---|
| references/game-architecture.md | Scene managers, state machines, ECS-lite patterns, save/load |
| references/ecosystem.md | Detailed setup for each ecosystem library |
| references/performance.md | Deep optimization: batching, masks, filters, memory, mobile |
| references/api-patterns.md | Full API: shapes, text, assets, filters, interaction, Spine |
| references/migration-v8.md | v7-to-v8 breaking changes table |
npm create pixi.js@latest # scaffold (recommended)
# -- or existing project --
npm install pixi.js
npm install @pixi/sound # audio
Bundler: Use Vite. Wrap bootstrap in an async function (avoid bare top-level await with Vite <= 6.0.6).
import { Application, Assets } from 'pixi.js';
async function startGame() {
const app = new Application();
await app.init({
width: 960,
height: 540,
backgroundColor: 0x1a1a2e,
antialias: true,
});
document.getElementById('game-container').appendChild(app.canvas);
// ---- Asset manifest with bundles ----
await Assets.init({
basePath: '/assets/',
manifest: {
bundles: [
{
name: 'preload',
assets: [
{ alias: 'loading-bar', src: 'ui/loading-bar.png' },
],
},
{
name: 'game',
assets: [
{ alias: 'hero-sheet', src: 'sprites/hero.json' },
{ alias: 'tileset', src: 'maps/tileset.json' },
{ alias: 'bgm', src: 'audio/bgm.mp3' },
],
},
],
},
});
await Assets.loadBundle('preload');
Assets.backgroundLoadBundle('game');
// ---- Responsive canvas ----
function resize() {
const parent = app.canvas.parentElement;
if (!parent) return;
app.renderer.resize(parent.clientWidth, parent.clientHeight);
}
window.addEventListener('resize', resize);
resize();
// ---- Start game loop ----
const game = new Game(app);
app.ticker.add((ticker) => game.update(ticker.deltaTime));
}
startGame();
Scenes are Containers swapped on/off the stage. A minimal SceneManager:
import { Container } from 'pixi.js';
class SceneManager {
constructor(app) {
this.app = app;
this.currentScene = null;
}
async switchTo(SceneClass, data) {
if (this.currentScene) {
this.currentScene.onExit?.();
this.app.stage.removeChild(this.currentScene);
this.currentScene.destroy({ children: true });
}
this.currentScene = new SceneClass(this.app, this);
this.app.stage.addChild(this.currentScene);
await this.currentScene.onEnter?.(data);
}
}
// Base scene pattern
class GameScene extends Container {
constructor(app, scenes) {
super();
this.app = app;
this.scenes = scenes;
}
async onEnter(data) { /* load assets, build UI */ }
update(dt) { /* per-frame logic */ }
onExit() { /* cleanup */ }
}
See references/game-architecture.md for state machines, ECS-lite, and save/load patterns.
Track keyboard state for continuous movement (not just events):
class Keyboard {
constructor() {
this.keys = new Set();
window.addEventListener('keydown', (e) => this.keys.add(e.code));
window.addEventListener('keyup', (e) => this.keys.delete(e.code));
}
isDown(code) { return this.keys.has(code); }
destroy() {
window.removeEventListener('keydown', this._onDown);
window.removeEventListener('keyup', this._onUp);
}
}
// Usage in update loop
if (keyboard.isDown('ArrowRight')) player.x += speed * dt;
For pointer/touch input, use PixiJS events on the stage:
app.stage.eventMode = 'static';
app.stage.hitArea = app.screen;
app.stage.on('pointermove', (e) => {
crosshair.position.copyFrom(e.global);
});
Use fixed timestep for deterministic physics, variable for rendering:
class Game {
constructor(app) {
this.app = app;
this.fixedStep = 1 / 60; // 60 Hz physics
this.accumulator = 0;
}
update(dt) {
const deltaSec = dt / 60; // ticker.deltaTime is frame-based, convert to seconds
this.accumulator += deltaSec;
while (this.accumulator >= this.fixedStep) {
this.fixedUpdate(this.fixedStep);
this.accumulator -= this.fixedStep;
}
this.render(this.accumulator / this.fixedStep); // interpolation alpha
}
fixedUpdate(step) { /* physics, collision */ }
render(alpha) { /* visual interpolation, particles, animations */ }
}
import { Sprite, AnimatedSprite, Assets } from 'pixi.js';
// Static sprite
const texture = Assets.get('hero-sheet');
const hero = new Sprite(texture);
hero.anchor.set(0.5);
// Animated sprite from spritesheet
const sheet = Assets.get('hero-sheet');
const walkFrames = Object.keys(sheet.textures)
.filter((k) => k.startsWith('walk_'))
.map((k) => sheet.textures[k]);
const animHero = new AnimatedSprite(walkFrames);
animHero.anchor.set(0.5);
animHero.animationSpeed = 0.15;
animHero.play();
Use BitmapText for scores, timers, and any text that updates every frame:
import { BitmapText, BitmapFont } from 'pixi.js';
BitmapFont.install({ name: 'GameFont', style: { fontSize: 32, fill: 0xffffff } });
const scoreText = new BitmapText({
text: 'Score: 0',
style: { fontFamily: 'GameFont', fontSize: 32 },
});
scoreText.position.set(16, 16);
// Update cheaply every frame
scoreText.text = `Score: ${score}`;
Reuse objects instead of create/destroy (bullets, particles, enemies):
class Pool {
constructor(factory, reset) {
this.factory = factory;
this.reset = reset;
this.available = [];
}
get() {
const obj = this.available.length > 0
? this.available.pop()
: this.factory();
this.reset(obj);
return obj;
}
release(obj) {
obj.visible = false;
this.available.push(obj);
}
}
// Example: bullet pool
const bulletPool = new Pool(
() => { const s = new Sprite(bulletTex); s.anchor.set(0.5); return s; },
(s) => { s.visible = true; s.alpha = 1; },
);
Manual camera -- move a world container opposite to the target:
const world = new Container({ isRenderGroup: true });
app.stage.addChild(world);
function updateCamera(target, screen) {
world.x = -target.x + screen.width / 2;
world.y = -target.y + screen.height / 2;
}
For zoom, bounds clamping, and inertia, use pixi-viewport:
import { Viewport } from 'pixi-viewport';
const viewport = new Viewport({
screenWidth: app.screen.width,
screenHeight: app.screen.height,
worldWidth: 3000,
worldHeight: 3000,
events: app.renderer.events,
});
app.stage.addChild(viewport);
viewport.drag().pinch().wheel().decelerate();
viewport.follow(player, { speed: 10 });
AABB (axis-aligned bounding box):
function aabb(a, b) {
return (
a.x < b.x + b.width &&
a.x + a.width > b.x &&
a.y < b.y + b.height &&
a.y + a.height > b.y
);
}
Circle collision:
function circleHit(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
const dist = a.radius + b.radius;
return dx * dx + dy * dy < dist * dist;
}
For complex physics (rigid bodies, joints, platformers), integrate Matter.js -- see references/ecosystem.md.
Use @pixi/tilemap for efficient grid-based maps:
import { CompositeTilemap } from '@pixi/tilemap';
const tilemap = new CompositeTilemap();
for (let y = 0; y < mapHeight; y++) {
for (let x = 0; x < mapWidth; x++) {
const tileId = mapData[y][x];
tilemap.tile(tileTextures[tileId], x * tileSize, y * tileSize);
}
}
world.addChild(tilemap);
| Package | Version (v8-compat) | Purpose |
|---|---|---|
@pixi/sound | ^6.x | Audio playback, sprites, filters |
pixi-viewport | ^5.x | Camera: pan, zoom, follow, cull |
@pixi/tilemap | ^4.x | Fast tilemap rendering |
@pixi/ui | ^2.x | Buttons, sliders, scroll, list |
@pixi/particle-emitter | ^5.x | Configurable particle systems |
matter-js | ^0.20 | 2D physics engine |
@esotericsoftware/spine-pixi-v8 | ^4.2 | Spine skeletal animation |
pixi-filters | ^6.x | 40+ post-processing filters |
Install only what you need. See references/ecosystem.md for integration patterns.
Draw call budget: aim for < 100 on mobile, < 300 on desktop.
| Technique | When to use |
|---|---|
| Sprite sheets / atlases | Always -- consolidate into few textures |
| Object pooling | Any object created/destroyed frequently (bullets, particles, pickups) |
isRenderGroup: true | Separate game layers (world, HUD, particles) |
ParticleContainer | Mass identical sprites (rain, stars, bullets) -- only accepts Particle objects in v8 |
BitmapText over Text | Any text that updates per frame |
cacheAsTexture() | Complex static sub-trees that rarely change |
| Reduce filter count | Each filter = extra render pass |
Mobile-specific:
antialias: falsebackgroundAlpha: 1 (avoids compositing cost)@0.5x resolution texturesresolution: Math.min(window.devicePixelRatio, 2)See references/performance.md for full optimization guide.
Common mistakes when writing PixiJS 8.x game code:
new Application() then await app.init({...}) -- constructor alone does nothingapp.view: renamed to app.canvascontainer.name: renamed to container.labelTexture.from(url): must await Assets.load(url) first -- no lazy loading in v8cacheAsBitmap = true: replaced by cacheAsTexture() methodupdateTransform(): replaced by onRender() callbackContainer can have children in v8Ticker instance -- use ticker.deltaTimebeginFill/endFill: Graphics uses chainable .rect().fill() APInew BlurFilter({ strength: 8 })eventMode = 'static': default is 'passive' (no events) -- must opt inParticle objects, not SpritegetBounds() as Rectangle: returns Bounds object -- access .rectangle for the rectimport { ... } from 'pixi.js' -- single entry pointPIXI.settings: removed -- use AbstractRenderer.defaultOptionsnpx claudepluginhub aiotnetwork/aiotaiagentskills --plugin aiot-ai-agent-skillsEntry point for PixiJS v8 skill collection. Routes to specialized skills for application setup, scene graph, rendering, assets, events, migration, and project scaffolding.
Renders high-performance 2D graphics, particle effects, sprite animations, and interactive canvases using PixiJS with WebGL/WebGPU acceleration for games and UI overlays.
Creates and refactors Phaser 3 browser games with scenes, physics, tilemaps, animations, input, audio, camera, and performance fixes.