vue-phaserjs
Esposter vue-phaserjs integration patterns — markRaw for Phaser objects in Pinia stores. Apply when writing Phaser game objects, stores, or vue-phaserjs components.
SKILL.md.claude/skills/vue-phaserjs/SKILL.md
GitHubvue-phaserjs Conventions
Settled — do not re-propose
- A
<Group>component — av-forover the children is the group. - A
<Layer>component — it is incompatible withuseInitializeGameObject. - Input composables —
onUpdatereads input directly. useTimeline, physics, camera or tilemap-layer components — nothing consumes them, and a component is added for the game that needs it.
Components
Every game object component follows the same 4-file pattern, split across four trees — which is the whole inventory rule: what exists is whatever src/components/ holds, and adding one means adding all four files.
| File | Path |
|---|---|
{Name}.vue |
packages/vue-phaserjs/src/components/ |
{Name}Configuration.ts |
packages/vue-phaserjs/src/models/configuration/ |
{Name}EventEmitsOptions.ts |
packages/vue-phaserjs/src/models/emit/ |
{Name}SetterMap.ts |
packages/vue-phaserjs/src/services/setterMap/ |
<Game> mounts the Phaser game and hosts <Scene>; every shape component extends ShapeConfiguration. One naming trap: the component is <Nineslice> with a lowercase s — <NineSlice> resolves to nothing.
Configuration Interfaces — Pick from Game Object Types
A new component's configuration Picks from the Phaser game object type, and a setter returning a fluent value is wrapped in braces, never void (references/adding-a-component.md).
Configuration Keys Must All Be Present at Setup
A configuration object must carry every key it will ever want to update, even when the value is undefined. useInitializeGameObjectSetters enumerates Object.entries(toValue(configuration)) once during setup and registers a setter plus a watcher only for the keys present at that moment. A key absent then gets neither, for the lifetime of the game object — later values are silently dropped with no error.
key: undefined is fine and explicitly handled: the setter still runs, and the composable emits the intrinsic game object value instead of the missing one.
So build configurations as complete inline object literals, and never spread an optional-prop object straight into one:
<!-- correct — displayWidth is always a key, undefined or not -->
<Image :configuration="{ visible: isVisible, ...imagePosition, texture, displayWidth, scaleY, tween }" />
<!-- wrong — an omitted optional prop leaves the key absent, so it never gets a watcher -->
<Image :configuration="props" />
A displayWidth?: number props declaration is still correct; the component just has to name the key when it builds the configuration.
Phaser Objects in Pinia Stores
A Phaser object assigned to reactive store state is markRawed — the rule is the pinia skill's (references/class-instances-in-state.md), and which Phaser classes it bites is references/phaser-objects-in-stores.md.
SSR / "Phaser is not defined"
phaser and phaser4-rex-plugins must stay externalized, subpaths included. A bundled phaser4-rex-plugins/plugins/*.js reads Phaser.Scene/Phaser.Game as globals at module-eval time and throws under Node SSR.
Nothing in this package configures that: they are its peerDependencies, which tsdown externalizes. Keep them peers.
Reference pages
references/adding-a-component.md— when adding a component, or writing its configuration interface or setter map.references/phaser-objects-in-stores.md— when an app store holds a Phaser object, and which classes needmarkRaw.