Bootstrap browser-based games with PixiJS 8 and a modern retro/vector aesthetic (Geometry Wars, Asteroids, Tempest, Tron). This skill should be used when the user asks to "create a new game", "start a browser game project", "build an arcade game", "prototype a game", "set up PixiJS", or mentions wanting vector graphics, neon aesthetics, or arcade-style gameplay. Provides project scaffolding, ECS-lite architecture, performance patterns (pooling, spatial hashing, fixed timestep), and visual design system.
Bootstraps browser-based arcade games with PixiJS 8, featuring retro vector aesthetics and performance-focused architecture.
npx claudepluginhub rbergman/dark-matter-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/dev-tools-ui.mdreferences/eslint-config.mdreferences/visual-design.mdPurpose: Scaffold browser-based games with PixiJS 8 and modern retro/vector aesthetics. Produces architecture that handles 500+ entities at 60fps with no memory growth over 30+ minute sessions.
Activate this skill when:
Keywords: game, arcade, retro, vector, pixijs, pixi, ecs, prototype, browser game, neon, glow
{
"dependencies": {
"pixi.js": "^8.6.6",
"@pixi/layout": "^2.0.0",
"stats.js": "^0.17.0"
},
"devDependencies": {
"typescript": "^5.7.3",
"typescript-eslint": "^8.21.0",
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
"eslint": "^9.18.0",
"vite": "^6.0.7",
"vitest": "^3.0.4",
"husky": "^9.1.7"
}
}
// Application init is now async
const app = new Application();
await app.init({
resizeTo: window,
backgroundColor: 0x000000,
preference: 'webgpu', // Falls back to WebGL
});
// Graphics API is chainable
const g = new Graphics()
.rect(0, 0, 100, 50)
.fill({ color: 0xff0000 })
.stroke({ width: 2, color: 0xffffff });
// ParticleContainer uses Particle objects, not Sprites
const particles = new ParticleContainer({
dynamicProperties: {
position: true,
scale: true,
rotation: false,
tint: false,
alpha: true,
},
});
const particle = new Particle({ texture, anchorX: 0.5, anchorY: 0.5 });
particles.addParticle(particle);
When querying up-to-date PixiJS docs, use library IDs:
/pixijs/pixijs/v8_12_0/llmstxt/pixijs_llms-full_txtproject/
├── src/
│ ├── index.ts # Entry point, app bootstrap
│ ├── game.ts # Game class - orchestrates everything
│ │
│ ├── core/ # Engine-level systems (game-agnostic)
│ │ ├── clock.ts # Adjustable game timer
│ │ ├── ecs.ts # Entity manager, component arrays
│ │ ├── pool.ts # Generic object pooling
│ │ ├── spatial-hash.ts # Collision broadphase
│ │ └── input.ts # Keyboard/mouse state
│ │
│ ├── components/ # Pure data (no logic)
│ │ ├── transform.ts # Position, rotation, scale
│ │ ├── velocity.ts # Linear + angular velocity
│ │ ├── collider.ts # Radius, collision mask/layer
│ │ ├── health.ts # HP, max HP, invincibility
│ │ ├── lifetime.ts # TTL for projectiles, particles
│ │ └── renderable.ts # Graphics reference, layer
│ │
│ ├── systems/ # Logic that operates on components
│ │ ├── physics.ts # Velocity → position, wrapping
│ │ ├── collision.ts # Spatial hash queries, response
│ │ ├── render.ts # Sync components → PIXI graphics
│ │ └── [game-specific] # Weapon, enemy AI, etc.
│ │
│ ├── data/ # Content definitions (pure data)
│ │ └── config.ts # Tuning constants
│ │
│ ├── rendering/ # PIXI-specific
│ │ ├── layers.ts # Container hierarchy
│ │ ├── viewport.ts # Full viewport scaling
│ │ ├── particles.ts # ParticleContainer system
│ │ ├── design-system.ts # Colors, visual constants
│ │ └── shaders/ # Custom GLSL effects
│ │
│ ├── ui/ # HUD, menus
│ │ └── hud.ts
│ │
│ ├── debug/ # Dev tools
│ │ └── stats.ts # Performance monitor
│ │
│ └── types/ # Type declarations
│ └── stats.js.d.ts
│
├── references/ # Specs, original designs
├── docs/plans/ # Architecture docs
├── history/ # Ephemeral scratch (gitignored)
└── [config files]
Independent of framerate and wall clock. Supports pause, slow-mo, frame stepping.
interface Clock {
elapsed: number; // Total game time (scaled)
delta: number; // Fixed tick duration (1/60)
scale: number; // 1.0 = normal, 0 = paused
wallDelta: number; // Real time (for UI animations)
wallElapsed: number;
}
interface ClockController extends Clock {
pause(): void;
resume(): void;
setScale(scale: number): void;
step(delta: number): void; // Advance one tick (debugging)
}
Physics at fixed 60Hz. Render interpolates for smooth visuals.
const TICK_RATE = 60;
const TICK_DURATION = 1 / TICK_RATE;
let accumulator = 0;
app.ticker.add(() => {
const wallDelta = app.ticker.deltaMS / 1000;
accumulator += wallDelta * clock.scale;
// Cap to prevent spiral of death
accumulator = Math.min(accumulator, TICK_DURATION * 5);
while (accumulator >= TICK_DURATION) {
clock.delta = TICK_DURATION;
clock.elapsed += TICK_DURATION;
// Systems update in deterministic order
inputSystem.update();
physicsSystem.update();
collisionSystem.update();
// ... more systems
world.flush(); // Apply deferred destructions
accumulator -= TICK_DURATION;
}
// Render with interpolation
const alpha = accumulator / TICK_DURATION;
renderSystem.update(alpha);
});
Entities are numbers. Components are typed maps. Zero allocation in hot paths.
type Entity = number;
class ComponentArray<T> {
private readonly data = new Map<Entity, T>();
set(entity: Entity, component: T): void { ... }
get(entity: Entity): T | undefined { ... }
has(entity: Entity): boolean { ... }
remove(entity: Entity): void { ... }
*entries(): IterableIterator<[Entity, T]> { yield* this.data.entries(); }
}
class World {
private nextId = 0;
private readonly alive = new Set<Entity>();
private readonly toDestroy: Entity[] = []; // Deferred
// Component arrays
readonly transform = new ComponentArray<Transform>();
readonly velocity = new ComponentArray<Velocity>();
// ...
// Type markers (for fast iteration)
readonly asteroids = new Set<Entity>();
readonly projectiles = new Set<Entity>();
// ...
spawn(): Entity { ... }
destroy(entity: Entity): void { this.toDestroy.push(entity); }
flush(): void { /* Actually remove */ }
}
Critical for preventing GC during gameplay.
interface Pool<T> {
acquire(): T;
release(item: T): void;
prewarm(count: number): void;
readonly activeCount: number;
readonly pooledCount: number;
}
class ObjectPool<T> implements Pool<T> {
constructor(
private factory: () => T,
private reset: (item: T) => void,
private dispose?: (item: T) => void
) {}
// ...
}
Rules:
reset() hides but doesn't destroy (for Graphics: set visible=false, move offscreen)destroy() only on full shutdownO(n) collision instead of O(n²).
interface SpatialHash {
cellSize: number;
clear(): void;
insert(entity: Entity, x: number, y: number, radius: number): void;
queryRadius(x: number, y: number, radius: number): readonly Entity[];
}
These rules prevent crashes from naive implementations. See game-perf skill for full details.
// ❌ BAD - creates new array every frame
const nearby = entities.filter(e => e.active);
// ✅ GOOD - reuse scratch array
scratchArray.length = 0;
for (const e of entities) {
if (e.active) scratchArray.push(e);
}
Avoid in hot-path systems: filter(), map(), reduce(), spread operator ([...arr]), object literals in loops.
// ❌ BAD - modifies collection during iteration
for (const e of world.asteroids) {
if (dead) world.asteroids.delete(e);
}
// ✅ GOOD - defer destruction
world.destroy(e); // Marks for deletion
// ... after all systems ...
world.flush(); // Actually removes
// ❌ BAD - creates Graphics during gameplay
const explosion = new Graphics();
effectLayer.addChild(explosion);
// ✅ GOOD - acquire from pool, never destroy during gameplay
const explosion = pools.effects.acquire();
explosion.visible = true;
// ... on done ...
pools.effects.release(explosion);
Skip rendering for off-screen entities. Critical when world is larger than viewport.
function isInViewport(x: number, y: number, radius: number, viewport: Rectangle): boolean {
return x + radius > viewport.x &&
x - radius < viewport.x + viewport.width &&
y + radius > viewport.y &&
y - radius < viewport.y + viewport.height;
}
// In render system
for (const [entity, transform] of world.transform.entries()) {
const renderable = world.renderable.get(entity);
if (!renderable) continue;
// Cull off-screen entities
const inView = isInViewport(transform.x, transform.y, renderable.radius, viewport);
renderable.graphics.visible = inView;
if (inView) {
renderable.graphics.position.set(transform.x, transform.y);
renderable.graphics.rotation = transform.rotation;
}
}
Rules:
visible = false for culled objects (GPU skips them entirely)For detailed design guidance, consult references/visual-design.md.
| Principle | Rule |
|---|---|
| Glow | Everything emits light. Lines are energy, not matter. |
| Contrast | Pure black void vs vivid neon. No middle ground. |
| Motion | Trails, particles, pulses. Nothing is static. |
| Hierarchy | Brightness = importance. Player brightest, debris dimmest. |
| Feedback | Every action has visible consequence. |
const Colors = {
background: 0x000000,
playerCore: 0xffffff,
playerGlow: 0x00ffff,
hazardPrimary: 0xffaa00,
enemyPrimary: 0xff0044,
rewardCore: 0x00ff00,
uiPrimary: 0x00ffff,
};
const LineWeight = {
hairline: 1,
thin: 1.5,
normal: 2,
bold: 3,
heavy: 4,
};
Apply BlurFilter per-layer (not per-object):
layer.filters = [new BlurFilter({ strength: 4, quality: 2 })];
Play area expands with viewport (not letterboxed):
const MIN_WIDTH = 1200;
const MIN_HEIGHT = 800;
const MAX_ASPECT_RATIO = 21 / 9;
const MIN_ASPECT_RATIO = 4 / 3;
Canvas UI is inherently "raw"—you're building from primitives. Use the right tool for each UI type:
| UI Type | Approach | Rationale |
|---|---|---|
| In-game HUD (score, lives, health) | @pixi/ui + @pixi/layout | Benefits from canvas effects (glow, shake) |
| Pause screen | @pixi/ui | Keeps visual consistency with game |
| Settings/keybinds menu | HTML/DOM overlay | Forms are easier in DOM |
| Dev tools | Tweakpane | Already in stack, tree-shakes from prod |
Use @pixi/layout (Yoga-powered flexbox) for positioning:
import { Layout } from '@pixi/layout';
import { FancyButton, ProgressBar } from '@pixi/ui';
const hud = new Layout({
id: 'hud',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
padding: 20,
width: viewport.width,
maxWidth: 1400, // Prevents ultra-wide spreading
});
hud.addChild(scoreText, healthBar, pauseButton);
layers.ui.addChild(hud);
Create a centralized theme to avoid repetitive styling:
// src/ui/theme.ts
export const UITheme = {
colors: {
primary: 0x00ffff,
danger: 0xff0044,
success: 0x00ff00,
neutral: 0x888888,
},
fonts: {
hud: { fontFamily: 'monospace', fontSize: 24, fill: 0x00ffff },
title: { fontFamily: 'monospace', fontSize: 48, fill: 0xffffff },
},
button: {
padding: 12,
borderRadius: 4,
borderWidth: 2,
},
} as const;
Build reusable UI factories to reduce repetition:
// src/ui/factory.ts
import { FancyButton, ProgressBar } from '@pixi/ui';
import { Graphics, Text } from 'pixi.js';
import { UITheme } from './theme';
export function createButton(
label: string,
onClick: () => void,
variant: 'primary' | 'danger' = 'primary'
): FancyButton {
const color = UITheme.colors[variant];
const { padding, borderRadius, borderWidth } = UITheme.button;
const makeView = (fill: number, stroke: number) =>
new Graphics()
.roundRect(0, 0, 120, 40, borderRadius)
.fill(fill)
.stroke({ width: borderWidth, color: stroke });
return new FancyButton({
defaultView: makeView(0x000000, color),
hoverView: makeView(color, color),
pressedView: makeView(color, 0xffffff),
text: new Text({ text: label, style: UITheme.fonts.hud }),
anchor: 0.5,
}).on('pointerup', onClick);
}
export function createHealthBar(maxHealth: number): ProgressBar {
return new ProgressBar({
bg: new Graphics().roundRect(0, 0, 200, 20, 4).fill(0x222222),
fill: new Graphics().roundRect(0, 0, 200, 20, 4).fill(UITheme.colors.success),
progress: 100,
});
}
export function createScoreText(): Text {
return new Text({ text: 'SCORE: 0', style: UITheme.fonts.hud });
}
For settings, keybinds, or form-heavy UI, use HTML overlay:
// In your HTML
// <div id="dom-ui" class="hidden">
// <div id="settings-menu">...</div>
// </div>
class Game {
private domUI = document.getElementById('dom-ui')!;
showSettings(): void {
this.pause();
this.domUI.classList.remove('hidden');
}
hideSettings(): void {
this.domUI.classList.add('hidden');
this.resume();
}
}
#dom-ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
#dom-ui > * {
pointer-events: auto;
}
#dom-ui.hidden {
display: none;
}
DOM overlay caveats:
| Component | Use Case |
|---|---|
FancyButton | Buttons with hover/pressed states |
Button | Simple button |
CheckBox | Toggle settings |
Slider / DoubleSlider | Volume, sensitivity |
Input | Text entry (limited—consider DOM for complex forms) |
ScrollBox | Scrollable lists (high scores, inventory) |
Select | Dropdowns |
ProgressBar / CircularProgressBar | Health, loading |
RadioGroup | Mutually exclusive options |
List | Arranged child elements |
Strict rules that cannot be disabled via eslint-disable comments. See references/eslint-config.md for full configuration.
Key rules:
complexity: 10 maxmax-lines-per-function: 60max-lines: 400 per file@typescript-eslint/no-explicit-any: error// Toggle with Cmd+Shift+S (Mac) or Ctrl+Shift+S (Windows)
const stats = createStatsMonitor();
stats.begin();
// ... update ...
stats.end();
clock.pause(); // Freeze game
clock.setScale(0.2); // 20% speed (slow-mo)
clock.step(1/60); // Advance one frame
When creating a new game project:
The architecture is game-agnostic—the same foundation works for shooters, survivors, puzzle games, or anything with real-time gameplay.
For dev tools (Tweakpane, stats), pause system, keybind settings, lifecycle management (Disposable pattern), and DOM overlay patterns, see references/dev-tools-ui.md.
Additional dependencies: @pixi/ui (runtime), tweakpane (devDependency, tree-shakes from prod).
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.