Skip to content
Development
Agent

pixi-react-integration

**Scope**: @pixi/react v8 + React 19, hybrid canvas/DOM, Zustand sharing, lazy loading, Vite splitting **Version range**: @pixi/react ^8.0.0, pixi.js ^8.5.0, React ^19.0.0, Vite ^5+ **Note**: @pixi/react v8 requires React 19 — do not use with React 18.

From plugin
vexjoy-agent
449198 skills198 agents12 commands83 hooks

Claude Code

$ npx -y skills add notque/vexjoy-agent --agent claude-code

Agent definition

pixi-react-integration.md

@pixi/react v8 Integration Reference

> **Scope**: @pixi/react v8 + React 19, hybrid canvas/DOM, Zustand sharing, lazy loading, Vite splitting > **Version range**: @pixi/react ^8.0.0, pixi.js ^8.5.0, React ^19.0.0, Vite ^5+ > **Note**: @pixi/react v8 requires React 19 — do not use with React 18.

---

Installation

npm install pixi.js@^8 @pixi/react@^8
npm install @spd789562/pixi-v8-particle-emitter  # v8 particles
npm install pixi-filters                          # post-processing

---

extend() API

Declare which PixiJS classes the reconciler knows about. Module-level, imported once:

// src/combat/pixi-setup.ts
import { extend } from '@pixi/react';
import { Application, Container, Sprite, AnimatedSprite, ParticleContainer, Graphics, Text } from 'pixi.js';

extend({ Application, Container, Sprite, AnimatedSprite, ParticleContainer, Graphics, Text });

After extending: `Container` → `<container>`, `Sprite` → `<sprite>`, etc.

---

Application Setup

import './pixi-setup';
import { Application } from '@pixi/react';

export function PixiCombatCanvas({ width, height }: { width: number; height: number }) {
  return (
    <Application width={width} height={height} backgroundColor={0x000000}
      antialias resolution={window.devicePixelRatio} autoDensity
      onInit={(app) => { console.debug('[pixi] ready', app.renderer.type); }}>
      <CombatScene />
    </Application>
  );
}

---

Hybrid Canvas + DOM Layout

PixiJS = canvas (combat). React DOM = UI chrome (HP bars, buttons). Stacked via CSS absolute positioning:

const PixiCombatCanvas = React.lazy(() => import('../combat/PixiCombatCanvas').then(m => ({ default: m.PixiCombatCanvas })));

export function CombatScreen() {
  return (
    <div className="relative w-full h-screen overflow-hidden bg-black">
      <div className="absolute inset-0 pointer-events-none">
        <Suspense fallback={<div className="absolute inset-0 bg-black" />}>
          <PixiCombatCanvas width={1280} height={720} />
        </Suspense>
      </div>
      <div className="absolute inset-0 pointer-events-auto">
        <CombatHUD />
      </div>
    </div>
  );
}

Canvas layer: `pointer-events: none`. DOM UI: `pointer-events: auto`. Never put HP bars/buttons in PixiJS canvas.

---

Zustand State Sharing

Both PixiJS and React DOM subscribe to the same store. PixiJS reads in ticker (zero re-renders), not in render:

// PixiJS component — read via subscribe, mutate display objects directly
useEffect(() => {
  return useCombatStore.subscribe(
    (state) => state.lastEffectType,
    (effectType) => {
      if (!spriteRef.current || !effectType) return;
      if (effectType === 'hit') {
        spriteRef.current.tint = 0xff4444;
        setTimeout(() => { if (spriteRef.current) spriteRef.current.tint = 0xffffff; }, 150);
      }
    }
  );
}, []);

// React DOM — use hooks normally
const playerHP = useCombatStore((state) => state.playerHP);

---

Lazy Loading + Vite Bundle Splitting

PixiJS ~800KB raw / ~250KB gzipped. Split from main bundle:

// vite.config.ts
build: {
  chunkSizeWarningLimit: 900,
  rollupOptions: {
    output: {
      manualChunks(id: string) {
        if (id.includes('pixi.js') || id.includes('@pixi/') || id.includes('pixi-filters')) return 'pixi-vendor';
        if (id.includes('react') || id.includes('react-dom')) return 'react-vendor';
      },
    },
  },
},

Users on main menu never download PixiJS. Verify: `npx vite build && ls -lh dist/assets/ | grep pixi`

---

useTick — Ticker Animation

Runs on `requestAnimationFrame`. No React re-renders — mutates display objects directly:

useTick(() => {
  if (!spriteRef.current) return;
  spriteRef.current.y = BASE_Y + Math.sin(performance.now() / 1000 * Math.PI) * 8;
});

Must be inside `<Application>` tree.

---

Sprite/Texture Loading

export function useCombatTextures() {
  const [textures, setTextures] = useState<Record<string, Texture> | null>(null);
  useEffect(() => {
    Assets.load([
      { alias: 'player', src: '/sprites/player-sheet.json' },
      { alias: 'enemy', src: '/sprites/enemy-sheet.json' },
    ]).then(setTextures);
  }, []);
  return textures;
}

Never use `Texture.from()` for combat sprites in production — bypasses loading queue.

---

AnimatedSprite

const sheet = await Assets.load('/sprites/player-sheet.json');
const idleFrames = sheet.animations['idle'];

<animatedSprite textures={idleFrames} animationSpeed={0.1} loop autoPlay anchor={{ x: 0.5, y: 1 }} />

---

Patterns to Fix

Store Display Objects in useRef

// WRONG: const [sprite, setSprite] = useState<Sprite | null>(null);
const spriteRef = useRef<Sprite>(null); // CORRECT

Mutate in useTick, Not setState

// WRONG: useTick(() => { setPosition(prev => prev + 1); });
useTick(() => { if (spriteRef.current) spriteRef.current.x += 1; }); // CORRECT

extend() at Module Top Level

// WRONG: function CombatCanvas() { extend({ Sprite }); ... }
extend({ Sprite }); // Module scope, runs once

UI in DOM Layer

// WRONG: <text text={`HP: ${hp}`} />
<div className="absolute top-4 left-4">HP: {hp}</div> // CORRECT
Read more
@notque
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. VexJoy Agent connects plain-English requests to specialist agents, skills, and workflows. /do selects the knowledge and tools needed for your task.

44964 skills198 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
449
Star history of notque/vexjoy-agent on GitHub, a chart by star-history.com

Other agents on vexjoy-agent.

modules

modules

Agent

**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes…

@notque@notqueInstall Agent
testing

testing

Agent

**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**:…

@notque@notqueInstall Agent
communication-patterns

communication-patterns

Agent

**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what…

@notque@notqueInstall Agent

More agents from other plugins.

a11y-architect
Auto-invoked

a11y-architect

Agent

Accessibility Architect specializing in WCAG 2.2 compliance for Web and Native platforms. Use PROACTIVELY when designing UI…

ecc13d293k
@affaan-m@affaan-mInstall Agent
analyze-pass-impact

analyze-pass-impact

Agent

Analyzes how a specific topic affects a group of compiler passes. Used by the /plan-update skill to parallelize research across…

react2d252k
@facebook@facebookInstall Agent
orchestrator

orchestrator

Agent

- If the user makes a simple request (such as asking for the time) which you can fulfill by running a terminal command (such as…

codex2d135k
@openai@openaiInstall Agent
cavecrew-builder

cavecrew-builder

Agent

Surgical 1-2 file edit. Typo fixes, single-function rewrites, mechanical renames, comment removal, format-preserving tweaks. Hard…

caveman3d119k
@juliusbrussee@juliusbrusseeInstall Agent

Questions about pixi-react-integration.

Which coding agents can use pixi-react-integration?
pixi-react-integration installs in Claude Code and Cursor: each loads subagents from .claude/agents, so copy the file there. Flowy's auto-invocation runs in Claude Code only.