# Vue Phaserjs

> Esposter vue-phaserjs integration patterns — component inventory, markRaw for Phaser objects in Pinia stores, configuration Pick pattern. Apply when writing Phaser game objects, stores, or vue-phaserjs components. Use when this capability is needed.

- Skill: `tomevault-io/vue-phaserjs` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/vue-phaserjs`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/vue-phaserjs/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/vue-phaserjs

---


# vue-phaserjs Conventions

## Implemented Components (v1 complete)

All game object components follow the same 4-file pattern: `{Name}Configuration.ts`, `{Name}EventEmitsOptions.ts`, `{Name}SetterMap.ts`, `{Name}.vue`.

**Sprites / Images**: `<Sprite>`, `<Image>`, `<NineSlice>`, `<TileSprite>`, `<PathFollower>`

**Text**: `<Text>`, `<BitmapText>`

**Shapes** (all extend `ShapeConfiguration`): `<Arc>`, `<Circle>`, `<Rectangle>`, `<Ellipse>`, `<Line>`, `<Triangle>`, `<Polygon>`, `<Star>`, `<Curve>`, `<IsoBox>`, `<IsoTriangle>`

**Effects / Rendering**: `<Graphics>`, `<RenderTexture>`, `<Particles>`, `<Video>`

**Structure**: `<Container>`, `<Zone>`, `<Tilemap>`, `<Scene>`

**Not implemented**: `<Group>` (use `v-for`), `<Layer>` (incompatible with `useInitializeGameObject`), input composables (use `onUpdate` directly), `useTimeline`/physics/camera/tilemap-layer components.

## Configuration Interfaces — `Pick` from Game Object Types

When a configuration interface re-declares properties that exist on the Phaser game object, use `Pick<GameObjects.X, "prop1" | "prop2">` in `extends` instead of re-declaring each property individually:

```ts
// ✅ Correct
export interface ArcConfiguration
  extends ShapeConfiguration, Pick<GameObjects.Arc, "closePath" | "endAngle" | "radius" | "startAngle"> {}

// ❌ Wrong — redundant re-declaration
export interface ArcConfiguration extends ShapeConfiguration {
  closePath: GameObjects.Arc["closePath"];
  endAngle: GameObjects.Arc["endAngle"];
  // ...
}
```

Keep explicit declarations only for `Parameters<GameObjects.X["method"]>` tuples and plain primitives (`number`, `string`) that are constructor args without a matching readable property.

## SetterMap void-return

`SetterMap` types the inner setter function as returning `void`. When the setter body is a single method call that returns a value (Phaser fluent API), wrap it in braces — never use the `void` operator:

```ts
// ✅ Correct
x: (gameObject) => (value) => { gameObject.setX(value); },

// ❌ Wrong — void operator banned
x: (gameObject) => (value) => void gameObject.setX(value),
```

Multi-line setters already use braces naturally — no change needed.

## Phaser Objects in Pinia Stores

**Always use `markRaw()` when assigning a Phaser object to any reactive ref in a Pinia store.**

Pinia devtools traverse store state via Vue's `traverse`. Phaser 3.85+ `Frame.get glTexture()` returns `null` before WebGL upload — crash in dev when traversed. `markRaw(obj)` sets `__v_skip = true` to skip traversal.

```ts
// ✅ Correct — traverse-safe
controls.value = markRaw(new KeyboardControls(scene));
controls.value = markRaw(new JoystickControls());
sprite.value = markRaw(newSprite);
pathFollower.value = markRaw(newPathFollower);
tilemap.value = markRaw(newTilemap);
volumeSlider.value = markRaw(useSlider(scene, rectangle, { ... }));

// ❌ Wrong — will crash in dev mode via traverse → Frame.get glTexture → null
controls.value = new KeyboardControls(scene);
sprite.value = newSprite;
```

Any Phaser class that chains to `Scene → TextureManager → Texture → Frame → glTexture` is a risk:

- `GameObjects.*` (Sprite, Image, PathFollower, Container, Text, etc.)
- `Tilemaps.Tilemap`, `Tilemaps.TilemapLayer`
- `Input.Keyboard.Key`, `Input.Keyboard.CursorKeys` (via KeyboardPlugin → Scene)
- Rex plugin instances (Slider, VirtualJoystick, etc.)
- Any class that holds a `scene` reference

## SSR / "Phaser is not defined" Fix

**Problem**: `vue-phaserjs/dist/index.js` bundled `phaser4-rex-plugins` subpath imports (e.g. `phaser4-rex-plugins/plugins/clickoutside.js`). The bundled code accesses `Phaser.Scene`, `Phaser.Game`, etc. as globals at module-evaluation time, which fails in Node.js SSR.

**Root cause**: The `external` array used string literal `"phaser4-rex-plugins"` which only matches the root package name, not subpath imports like `"phaser4-rex-plugins/plugins/clickoutside.js"`.

**Fix** (`packages/vue-phaserjs/vite.config.js`): Use a RegExp to match all subpaths:

```js
rolldownOptions: {
  external: ["phaser", /^phaser4-rex-plugins/, "pinia", "vue"],
},
```

**Rule**: In Rolldown `external`, always use `/^package-name/` (regex) instead of `"package-name"` (string) when the package may be imported via subpaths. String literals only match exact module IDs.

---
> Source: [Esposter/Esposter](https://github.com/Esposter/Esposter) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-05-07 -->

