
Phaser Best Practices
- 696 installs
- 696 repo stars
- Updated July 27, 2026
- onmax/nuxt-skills
phaser-best-practices is a Claude Code skill that generates, refactors, and debugs Phaser 3 HTML5 game code with correct scene management, physics, tilemaps, and performance patterns.
About
phaser-best-practices is a framework skill from onmax/nuxt-skills for Phaser 3 JavaScript or TypeScript browser games. It scaffolds new projects with Node.js/npm or existing bundlers and covers scenes, entities, physics, UI, tilemaps, animations, input, audio, cameras, and Phaser-specific bug fixes. Metadata pins phaser-major to 3 and skill-type to framework. Developers reach for phaser-best-practices when adding game features or fixing performance and architecture problems instead of generic JavaScript advice
- Creates new Phaser 3 projects with correct folder layout, config, and first scenes
- Adds or refactors scenes, entities, physics, UI, tilemaps, animations, input, audio, and cameras
- Debugs and fixes Phaser-specific issues including scene lifecycle, collider bugs, asset loading, and blurry pixel art
- Optimizes runtime performance through pooling, culling, throttling, and asset strategies
- Triage workflow that classifies every request as new-project, feature-work, bug-fix, optimization, or art-pipeline befor
Phaser Best Practices by the numbers
- 696 all-time installs (skills.sh)
- +37 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #496 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/onmax/nuxt-skills --skill phaser-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 696 |
|---|---|
| repo stars | ★ 696 |
| Last updated | July 27, 2026 |
| Repository | onmax/nuxt-skills ↗ |
How do you structure Phaser 3 scenes and physics correctly?
Generate, refactor, and debug code for Phaser 3 HTML5 games with correct scene management, physics, tilemaps, and performance patterns.
Who is it for?
Developers building or maintaining Phaser 3 JavaScript or TypeScript browser games who need framework-correct patterns.
Skip if: Unity or Godot projects, Phaser 2 legacy codebases, or non-game web apps without Phaser.
When should I use this skill?
Creating a Phaser 3 game, adding scenes or physics, fixing Phaser bugs, or optimizing browser game performance.
What you get
Phaser 3 scenes, entities, tilemaps, physics configs, and performance-tuned game code.
- Phaser 3 scene code
- Physics and tilemap configuration
- Performance-tuned game module
By the numbers
- Targets Phaser major version 3
Files
Building Phaser Games
When to use this skill
Use this skill when the user wants to:
- create a new Phaser 3 game or prototype
- add or refactor scenes, entities, UI, physics, tilemaps, input, audio, or cameras
- debug Phaser-specific behavior such as scene restarts, blurry pixel art, collider bugs, asset loading problems, or animation issues
- improve architecture, maintainability, or runtime performance in a Phaser project
Do not use this skill for non-Phaser engines unless the user explicitly wants Phaser-style patterns adapted elsewhere.
How to operate
1. Triage the request
Classify the task before writing code:
- New project: scaffolding, folder layout, game config, first scenes
- Feature work: add gameplay, UI, audio, transitions, tilemaps, enemies, pickups
- Bug fix: isolate scene lifecycle, asset, physics, input, camera, or rendering failure
- Optimization: profile bottlenecks, pooling, culling, throttling, asset strategy
- Art / asset pipeline: spritesheet measurements, animation setup, nine-slice / three-slice UI, tilemap integration
2. Inspect first, then decide
When a repository already exists, inspect before proposing structure changes:
- package.json, bundler config, tsconfig/jsconfig
- Phaser version and whether the codebase is JS or TS
- game bootstrap, scene list, physics config, scale config
- asset folders and naming conventions
- current state-sharing approach (scene data, registry, services, globals)
- whether the project is pixel art, HD art, desktop-first, mobile-first, or mixed input
Prefer adapting to the existing codebase over replacing it with boilerplate.
3. Default technical choices
Use these defaults unless the task clearly calls for something else:
- Prefer the official Vite + TypeScript style setup for new projects
- Prefer Arcade Physics for platformers, shooters, top-down action, simple pickups, and lightweight collision logic
- Use Matter Physics only when the game needs rotation-driven collisions, compound bodies, constraints, stacking stability, or more realistic simulation
- Organize code around Scenes first, then entities / systems inside scenes
- Keep input scene-owned; entities should consume input state, not attach their own listeners
- Use global animations when multiple sprites share the same animation data
- Preload startup-critical assets up front; load level-specific assets later when it improves startup time
- Use built-in NineSlice / ThreeSlice for scalable UI art when the texture layout supports it; only fall back to custom compositing when transparent padding or discontinuous art breaks built-in slicing
- Use FIT scaling for most games, RESIZE for editor-like or UI-heavy layouts, and NONE only when manually controlling canvas sizing
- For pixel art, enable pixelArt mode, favor integer scaling where possible, and avoid sub-pixel camera movement
4. Output expectations
For new games, provide:
- the recommended folder structure
- a game config
- scene list and responsibilities
- starter code that runs
- notes on why each architectural choice fits the requested genre
For feature work or bug fixes, provide:
- minimal targeted edits
- root cause explanation
- the patch
- validation steps the user can run immediately
For architecture advice, provide:
- the smallest structure that solves the current problem
- one recommended path, not a menu of equally-weighted options
- explicit tradeoffs when the choice is important (for example Arcade vs Matter)
Non-negotiable implementation rules
- Respect the project's existing JS vs TS choice unless the user asks to migrate
- Centralize scene keys, asset keys, collision categories, and balance constants
- Keep
update()orchestration-focused; push detailed logic into entities or systems - Register cleanup for scene shutdown / destroy when you attach listeners, timers, tweens, or long-lived references
- Avoid creating new objects inside hot
update()loops unless profiling proves it is harmless - Do not make every object interactive or physics-enabled by default
- Do not assume spritesheet frame dimensions; inspect and verify them
- Do not tell the user to use Matter when Arcade already solves the problem cleanly
- Do not preload the entire game into one Boot scene just because it is convenient
Recommended delivery workflow
New Phaser project
1. Pick the architecture size:
- Small / jam game: 2-4 scenes, lightweight service modules
- Mid-size game: scenes + entities + systems + constants
- Large content-heavy game: data-driven content, scene services, dedicated state layer
2. Define the base config: renderer, scale mode, physics, pixel-art settings 3. Create startup scenes first: Boot, Menu, Game, UI; add Pause / GameOver only if required 4. Add one vertical slice that proves the core loop works 5. Add reference-driven systems next: audio, saveable state, enemy spawning, tilemaps, UI polish
Adding or refactoring a feature
1. Locate the owning scene and affected systems 2. Identify the smallest correct insertion point 3. Reuse existing helpers, constants, managers, and pools 4. Add cleanup and validation steps with the change 5. Preserve scene restart safety
Debugging
1. Reproduce the issue from the code and config 2. Identify whether the fault is:
- lifecycle / restart
- asset dimensions or loader config
- physics body setup or collider order
- scale / camera / pixel rounding
- stale listeners, timers, or pooled object state
3. Patch the root cause, not just the symptom 4. Provide a quick repro or verification checklist
Reference map
Read only the files relevant to the task:
- Setup / bootstrap / config: references/setup-and-build.md
- Scenes / shared state / architecture: references/scenes-state-architecture.md
- Physics / entities / pooling: references/physics-and-entities.md
- Assets / animations / UI panels: references/assets-animation-ui.md
- Tilemaps / camera / input / audio: references/tilemaps-camera-input-audio.md
- Performance / debugging / cleanup: references/performance-debugging.md
- Code review / architecture checklist: references/review-checklist.md
Concrete examples
Example: "Create a Phaser top-down shooter"
Use this skill. Default to:
- Vite + TypeScript structure
- Arcade Physics
- Boot, Menu, Game, UI scenes
- scene-owned input mapping
- pooled bullets
- global animations
- camera follow and world bounds
- asset keys / scene keys in constants
Then deliver runnable starter code plus the first playable loop.
Example: "My pixel art looks blurry on mobile"
Use this skill. Inspect:
pixelArtandroundPixelssettings- camera follow rounding
- scale mode and zoom strategy
- CSS around the canvas container
- whether art is being scaled non-integer
Then patch the smallest set of config and camera settings required.
Example: "Paper UI panels show weird side bars"
Use this skill. Inspect the source texture first. Then:
- try built-in ThreeSlice / NineSlice if the art is a true 3-slice or 9-slice layout
- if frames contain large transparent padding or discontinuous art, use trimmed or composited fallback slices
- document the measured frame sizes, spacing, margins, and any overlap used
Common traps
Avoid these unless the user explicitly wants them:
- one giant
GameScenethat owns menus, HUD, gameplay, pause, and transitions - state stored on
window, random module globals, or ad hoc singleton soup - entity-owned keyboard listeners
- scene restart bugs caused by forgotten shutdown cleanup
- loading every future asset in the first scene
- manual nine-slice composition when built-in NineSlice already fits the asset
- over-engineering with ECS for tiny games that only need a few entity classes
Final check before responding
Make sure the answer:
- matches the user's genre, platform, and art style
- uses Phaser 3 APIs, not Phaser 4 RC APIs
- chooses a physics system deliberately
- keeps SKILL.md-level advice concise and moves detail into references
- includes validation steps when code is produced
Assets, Animations, and UI Panels
Contents
- Asset pipeline defaults
- Images vs spritesheets vs atlases
- Loader measurement protocol
- Animation patterns
- Aseprite support
- UI slicing strategy
- Built-in NineSlice / ThreeSlice
- Custom fallback for difficult art
- Asset-key conventions
- Common mistakes
Asset pipeline defaults
Use stable keys, predictable folders, and measured loader configs.
Recommended categories:
public/assets/
├── images/
├── sprites/
├── atlases/
├── maps/
├── audio/
├── ui/
└── fonts/Keep file naming boring and consistent. Prefer kebab-case or snake_case, but use one style consistently.
Images vs spritesheets vs atlases
Choose the asset container deliberately.
Use an image when
- the art is static
- there is only one frame
- no animation or slicing is required
this.load.image('background-forest', 'assets/images/background-forest.png');Use a spritesheet when
- frames are a uniform grid
- frame width and height are constant
- the asset is animation-first or grid-sliced UI art
this.load.spritesheet('player-run', 'assets/sprites/player-run.png', {
frameWidth: 32,
frameHeight: 32
});Use an atlas when
- frames are irregular sizes
- many sprites should share one texture for batching
- export tooling already produces a texture atlas
this.load.atlas('game-atlas', 'assets/atlases/game.png', 'assets/atlases/game.json');Rule of thumb:
- spritesheet for measured uniform grids
- atlas for production sprite packs and batched mixed assets
Loader measurement protocol
Never guess spritesheet metrics.
Before writing loader code:
1. inspect the source image 2. record total width and height 3. count rows and columns 4. measure actual frame width and height 5. check for spacing between cells 6. check for margin around the whole sheet 7. verify the math closes exactly
Example with spacing
this.load.spritesheet('wood-panel', 'assets/ui/wood-panel.png', {
frameWidth: 144,
frameHeight: 144,
spacing: 8
});Example with margin and spacing
this.load.spritesheet('icons', 'assets/ui/icons.png', {
frameWidth: 32,
frameHeight: 32,
margin: 4,
spacing: 2
});If the sheet dimensions do not divide cleanly, stop and re-measure.
Animation patterns
Prefer global animations for shared motion
if (!this.anims.exists('player-run')) {
this.anims.create({
key: 'player-run',
frames: this.anims.generateFrameNumbers('player-run', { start: 0, end: 5 }),
frameRate: 12,
repeat: -1
});
}Use global animations when multiple instances share the same data.
Use local animations only for one-off behavior
Local animations are appropriate when:
- a single sprite needs unique frame timing
- a cutscene prop has custom animation data
- you explicitly do not want other sprites to reuse the animation
Animation switching rule
Only switch animations when the state changed. Do not spam .play() with different keys every frame unless required.
if (this.player.body!.velocity.x !== 0) {
this.player.anims.play('player-run', true);
} else {
this.player.anims.play('player-idle', true);
}Completion handling
this.player.on(Phaser.Animations.Events.ANIMATION_COMPLETE, (anim) => {
if (anim.key === 'enemy-die') {
this.player.destroy();
}
});Aseprite support
If the asset pipeline uses Aseprite JSON exports, create animations from the exported data instead of hand-writing every frame list.
this.load.aseprite(
'hero',
'assets/sprites/hero.png',
'assets/sprites/hero.json'
);
this.anims.createFromAseprite('hero');Use Aseprite-driven animations when the animation tags in the art tool already describe the desired behavior.
UI slicing strategy
Use the simplest UI scaling technique that preserves the art.
Use plain images when
- the panel size is fixed
- the art does not need to scale
Use ThreeSlice when
- the UI element stretches horizontally only
- the source art is really left-cap / center / right-cap
Use NineSlice when
- the source art is a regular 3x3 layout
- corners must stay fixed
- edges stretch on one axis
- center stretches freely
Use custom composition when
- frames contain large transparent padding
- the art has discontinuous gaps or gutters
- built-in slicing produces visible side bars or broken seams
Built-in NineSlice / ThreeSlice
Prefer the built-in game object first.
NineSlice
const panel = this.add.nineslice(
320,
180,
'ui-panels',
'paper-panel',
420,
260,
24,
24,
24,
24
);
panel.setOrigin(0.5);ThreeSlice
Create a 3-slice by setting top and bottom slice heights to zero:
const ribbon = this.add.nineslice(
320,
80,
'ui-panels',
'banner',
300,
48,
18,
18,
0,
0
);Use built-in slicing when the art is genuinely slice-friendly. Do not jump to custom canvas composition first.
Custom fallback for difficult art
Some asset packs look like nine-slice art but are not actually safe to stretch directly.
Use a custom fallback when you observe:
- opaque side bars just inside edges
- stretched transparent gutters
- seams between stitched slices
- later slices drifting because spacing or margins were mis-measured
Fallback strategy:
1. measure the real painted bounds in each source frame 2. trim away large transparent padding 3. render the slices into a composed texture at the target size 4. add tiny overlap to hide seams 5. disable smoothing for pixel-art UI
This is a fallback, not the default.
Asset-key conventions
Centralize keys in one place:
export const SCENES = {
Boot: 'BootScene',
Menu: 'MenuScene',
Game: 'GameScene',
UI: 'UIScene'
} as const;
export const TEX = {
Player: 'player',
Atlas: 'game-atlas',
PaperPanel: 'paper-panel'
} as const;
export const ANIMS = {
PlayerIdle: 'player-idle',
PlayerRun: 'player-run'
} as const;This reduces typo bugs and improves refactor safety.
Common mistakes
- guessing frame width or spacing
- putting every animated frame set into a separate PNG when an atlas would batch better
- re-creating global animations every scene start without checking
this.anims.exists() - forcing custom nine-slice composition when the built-in NineSlice already works
- using built-in NineSlice on art with large transparent padding and assuming the result is trustworthy
- scattering asset keys as raw strings across dozens of files
Performance and Debugging
Contents
- Performance mindset
- Biggest wins first
- Pooling patterns
- Update-loop hygiene
- Rendering and asset strategy
- Physics workload control
- Scene-safe cleanup
- Debug tooling
- TestScene pattern
- Shipping checklist
Performance mindset
Profile before optimizing. Phaser performance problems usually come from a small number of recurring causes:
- too many active objects
- too many physics bodies or unnecessary collision pairs
- allocation churn inside
update() - oversized particle systems
- expensive AI or pathfinding every frame
- unnecessary scene overlap
- leaking listeners, timers, or tweens across restarts
Start with the simplest explanation and verify it.
Biggest wins first
Tackle these before micro-optimizations:
1. pool frequently spawned objects 2. reduce active physics bodies and collision pairs 3. stop off-screen or inactive systems from updating 4. throttle expensive AI / pathfinding / scanning work 5. trim particles, post-processing, and unnecessary overdraw 6. simplify scene responsibilities if multiple scenes are running at once
Pooling patterns
Pool when spawn / despawn is frequent enough to create churn.
Bullet pool
this.bullets = this.physics.add.group({
classType: Phaser.Physics.Arcade.Image,
maxSize: 100
});fireBullet(x: number, y: number, vx: number, vy: number) {
const bullet = this.bullets.get(x, y, 'bullet') as Phaser.Physics.Arcade.Image | null;
if (!bullet) return;
bullet.setActive(true).setVisible(true);
bullet.body!.enable = true;
bullet.body!.reset(x, y);
bullet.setVelocity(vx, vy);
}killBullet(bullet: Phaser.Physics.Arcade.Image) {
this.tweens.killTweensOf(bullet);
bullet.body!.stop();
bullet.body!.enable = false;
bullet.setActive(false).setVisible(false);
}Pool only what matters. Premature pooling of rare objects adds complexity for little gain.
Update-loop hygiene
Good habits
- reuse vectors or temp objects
- use primitive values when possible
- throttle logic that does not need 60 Hz updates
- keep
update()short and orchestration-focused
Bad pattern
update() {
const target = { x: this.player.x + 10, y: this.player.y }; // allocates every frame
this.enemy.body!.velocity.x = target.x - this.enemy.x;
}Better pattern
update(time: number) {
if (time - this.lastAiTick > 100) {
this.updateEnemyAi();
this.lastAiTick = time;
}
}Use delta or elapsed time for framerate-independent movement and throttling decisions.
Rendering and asset strategy
Use atlases when it helps batching
When many related sprites can share a texture, prefer atlases over dozens of separate files.
Keep backgrounds simple
Good options:
- large static image
- tile sprite
- one or two parallax layers
Be careful with:
- many translucent full-screen layers
- large particle fields over the entire screen
- stacked blend modes everywhere
Pixel art
For crisp pixel art:
- use
pixelArt: true - favor integer-friendly zoom
- avoid sub-pixel camera drift
- test actual device/browser output, not just desktop Chrome
Physics workload control
Reduce collision pairs
Only add the collisions the game needs.
Good:
this.physics.add.collider(this.player, this.groundLayer);
this.physics.add.collider(this.enemies, this.groundLayer);
this.physics.add.overlap(this.player, this.pickups, this.onPickup, undefined, this);Bad:
this.physics.add.collider(this.enemies, this.enemies);
this.physics.add.collider(this.pickups, this.pickups);Only enable broad collision matrices when the mechanic genuinely needs them.
Disable or recycle inactive bodies
enemy.body!.enable = false;
enemy.setActive(false).setVisible(false);For projectile-heavy games, disable bodies immediately when the object leaves play.
Scene-safe cleanup
A surprising amount of "performance" trouble is actually leaked state after scene restarts.
On shutdown, clean:
- timers
- tweens
- global or foreign-scene listeners
- debug overlays
- DOM nodes added during debugging
- stale references in services or caches
Example:
create() {
this.events.once(Phaser.Scenes.Events.SHUTDOWN, this.onShutdown, this);
}
private onShutdown() {
this.time.removeAllEvents();
this.tweens.killAll();
// destroy any colliders or debug helpers you created and tracked explicitly
}Only destroy colliders or managers you own; be careful not to tear down shared systems accidentally.
Debug tooling
Physics debug
Enable during diagnosis, not by default in shipping builds.
physics: {
default: 'arcade',
arcade: {
debug: true
}
}Tile collision debug
groundLayer.renderDebug(this.add.graphics(), {
tileColor: null,
collidingTileColor: new Phaser.Display.Color(255, 0, 0, 100),
faceColor: new Phaser.Display.Color(0, 255, 0, 255)
});Runtime inspection
Useful things to inspect:
- active scenes
- world bounds
- camera bounds and follow target
- texture cache keys
- whether a pooled object was truly reset
Debug overlays
If you add DOM-based FPS meters or debug widgets, remember to remove them on shutdown.
TestScene pattern
Use a dedicated sandbox scene for isolated mechanics.
A good TestScene can quickly answer:
- are sprite bounds correct?
- did the body size / offset match the art?
- are animations valid?
- does a projectile pool reset correctly?
- does a new tileset align with the map?
Keep TestScene out of production scene lists when shipping.
Shipping checklist
Before declaring a Phaser task done, verify:
- the game still restarts the affected scene cleanly
- no duplicate listeners were introduced
- pooled objects reset fully
- scene transitions do not leave music or overlays behind
- tile collisions match the visual art
- pixel-art projects are still crisp after the change
- performance-sensitive changes were tested with representative object counts
- debug flags and test helpers are removed or gated
Physics and Entities
Contents
- Choosing Arcade vs Matter
- Arcade patterns
- Matter patterns
- Entity composition
- Finite state machines
- Groups and pooling
- Collision handling
- Pooled object reset checklist
- Common mistakes
Choosing Arcade vs Matter
Use this matrix:
| Need | Use |
|---|---|
| Platformer, top-down action, shooter, pickup collection | Arcade |
| Simple overlap checks and fast iteration | Arcade |
| Compound bodies, constraints, realistic stacking, heavy rotation | Matter |
| Physics puzzle with body shapes beyond simple AABB / circles | Matter |
| No real physics, mostly menus / cards / visual novel | None |
Default to Arcade when the answer is not obvious.
Arcade patterns
Arcade is the workhorse for most Phaser 3 gameplay.
Standard player setup
this.player = this.physics.add.sprite(96, 96, 'player');
this.player.setCollideWorldBounds(true);
this.player.setDrag(1200, 0);
this.player.setMaxVelocity(220, 500);Colliders and overlaps
this.physics.add.collider(this.player, this.groundLayer);
this.physics.add.collider(this.player, this.enemies, this.onPlayerHitEnemy, undefined, this);
this.physics.add.overlap(this.player, this.coins, this.onPlayerCollectCoin, undefined, this);Use:
- collider for physical separation
- overlap for triggers, pickups, hurtboxes, checkpoints, sensors
Platformer movement baseline
update() {
const speed = 180;
const jumpSpeed = 360;
if (this.cursors.left.isDown) {
this.player.setVelocityX(-speed);
this.player.setFlipX(true);
} else if (this.cursors.right.isDown) {
this.player.setVelocityX(speed);
this.player.setFlipX(false);
} else {
this.player.setVelocityX(0);
}
if (Phaser.Input.Keyboard.JustDown(this.cursors.up) && this.player.body!.blocked.down) {
this.player.setVelocityY(-jumpSpeed);
}
}Body sizing
Match bodies to gameplay, not to raw sprite dimensions:
this.player.body!.setSize(18, 28, true);
this.player.body!.setOffset(7, 4);Tight bodies usually feel better than full-sprite hitboxes.
Matter patterns
Use Matter when the simulation itself is part of the design.
Minimal sprite setup
this.player = this.matter.add.sprite(200, 120, 'player');
this.player.setFixedRotation();
this.player.setFriction(0.05);
this.player.setBounce(0);Bodies and constraints
const crate = this.matter.add.rectangle(400, 300, 48, 48);
const anchor = this.matter.add.circle(400, 80, 10, { isStatic: true });
this.matter.add.constraint(anchor, crate, 180, 0.8);Use Matter for:
- swinging bodies
- push / topple puzzles
- unusual body shapes
- rotation-sensitive collisions
Do not use Matter just because it sounds more advanced.
Entity composition
Prefer composition over inheritance for most gameplay objects.
class Health {
constructor(public hp: number, public maxHp: number) {}
damage(amount: number) {
this.hp = Math.max(0, this.hp - amount);
}
get dead() {
return this.hp <= 0;
}
}
class ArcadeMover {
constructor(
private readonly sprite: Phaser.Physics.Arcade.Sprite,
private readonly speed: number
) {}
moveX(dir: -1 | 0 | 1) {
this.sprite.setVelocityX(dir * this.speed);
}
}
class Enemy {
readonly health = new Health(30, 30);
readonly mover: ArcadeMover;
constructor(readonly sprite: Phaser.Physics.Arcade.Sprite) {
this.mover = new ArcadeMover(sprite, 70);
}
}Use composition when:
- multiple entity types share only some behaviors
- you want reusable health, movement, attack, patrol, or loot logic
- deep subclass hierarchies would mostly duplicate state handling
Finite state machines
Use a state machine when entity behavior is becoming branch-heavy.
type EnemyState = 'idle' | 'patrol' | 'chase' | 'stunned' | 'dead';
class EnemyBrain {
private state: EnemyState = 'idle';
constructor(private readonly enemy: Phaser.Physics.Arcade.Sprite) {}
update(player: Phaser.Physics.Arcade.Sprite) {
switch (this.state) {
case 'idle':
if (Phaser.Math.Distance.Between(this.enemy.x, this.enemy.y, player.x, player.y) < 180) {
this.state = 'chase';
}
break;
case 'chase':
this.enemy.setVelocityX(player.x < this.enemy.x ? -80 : 80);
break;
case 'stunned':
case 'dead':
this.enemy.setVelocityX(0);
break;
}
}
stun() {
this.state = 'stunned';
}
die() {
this.state = 'dead';
}
}Use an FSM for:
- player locomotion states
- enemy AI states
- boss phases
- run / pause / dialogue state transitions
Groups and pooling
Pool objects that spawn and despawn often:
- bullets
- slash effects
- enemy projectiles
- damage numbers
- temporary pickups
- frequently recycled enemies
Arcade pool example
this.bullets = this.physics.add.group({
classType: Phaser.Physics.Arcade.Image,
maxSize: 64,
runChildUpdate: false
});
fireBullet(x: number, y: number, vx: number, vy: number) {
const bullet = this.bullets.get(x, y, 'bullet') as Phaser.Physics.Arcade.Image | null;
if (!bullet) return;
bullet.setActive(true).setVisible(true);
bullet.body!.enable = true;
bullet.body!.reset(x, y);
bullet.setVelocity(vx, vy);
}Return to pool
despawnBullet(bullet: Phaser.Physics.Arcade.Image) {
bullet.setActive(false).setVisible(false);
bullet.body!.stop();
bullet.body!.enable = false;
}Pool only what is frequent enough to matter.
Collision handling
Keep collision callbacks small and intentional.
Good overlap callback
private onPlayerCollectCoin(
_player: Phaser.GameObjects.GameObject,
coin: Phaser.GameObjects.GameObject
) {
const sprite = coin as Phaser.Physics.Arcade.Sprite;
sprite.disableBody(true, true);
this.registry.set('coins', (this.registry.get('coins') ?? 0) + 1);
}Process callback for conditional collision
private shouldCollidePlayerPlatform(
player: Phaser.Types.Physics.Arcade.GameObjectWithBody,
platform: Phaser.Types.Physics.Arcade.GameObjectWithBody
) {
return player.body.velocity.y >= 0 && player.body.bottom <= platform.body.top + 8;
}
this.physics.add.collider(
this.player,
this.oneWayPlatforms,
undefined,
this.shouldCollidePlayerPlatform,
this
);Pooled object reset checklist
Whenever a pooled object is reused, reset all state that can leak from a previous life:
- position and velocity
- visibility and active flag
- body enabled state
- alpha, scale, tint, rotation, flip
- animation state
- timers or delayed calls tied to the object
- tweens targeting the object
- data values or custom fields such as damage, owner, lifetime, faction
If a pooled object behaves randomly, assume reset state is incomplete until proven otherwise.
Common mistakes
- using Matter for a simple platformer or shooter
- never resizing Arcade bodies away from full-sprite bounds
- putting AI, movement, attacks, and animation switching directly in the scene
update() - destroying bullets instead of pooling them in a bullet-heavy game
- forgetting to stop tweens or timers before returning pooled objects
- assuming
touching.downandblocked.downmean the same thing in every situation - creating physics bodies for decorative sprites or UI art
Review Checklist
Contents
- Activation checklist
- Architecture checklist
- Code generation checklist
- Bug-fix checklist
- Performance checklist
- Asset-pipeline checklist
- Final-response checklist
Activation checklist
Use the Phaser skill when the task is about:
- Phaser 3 scenes, game config, scaling, cameras, or scene lifecycle
- sprites, atlases, animations, or asset loading
- Arcade or Matter physics
- tilemaps / Tiled integration
- browser-game input, audio, HUD, pause menus, or scene transitions
- Phaser-specific debugging or performance issues
Do not reach for this skill just because the project is a web app with a canvas.
Architecture checklist
Before proposing structure, verify:
- Is this a new game, a prototype, a mid-size game, or a content-heavy production?
- Does the current codebase already have an architecture worth preserving?
- Is a dedicated UI scene warranted?
- Does the user actually need Matter, or is Arcade enough?
- Should shared state live in scene data, registry, or a dedicated service?
If the answer is "small prototype", do not prescribe a large architecture.
Code generation checklist
When generating code:
- keep file changes minimal
- preserve the existing language style (JS or TS)
- centralize keys / constants instead of scattering raw strings
- ensure lifecycle-safe cleanup for listeners and timers
- keep
update()readable - avoid speculative abstractions the user did not ask for
- include enough code for the feature to run, not just fragments
- include validation steps
Bug-fix checklist
When fixing a Phaser bug, ask:
- is this a lifecycle problem?
- is the scene restarting safely?
- are loader keys or asset dimensions wrong?
- are bodies the wrong size or offset?
- is the camera or scale config causing the symptom?
- is stale pooled-object state leaking across spawns?
- are duplicate listeners accumulating?
Patch the root cause and explain why it happened.
Performance checklist
Before claiming a performance fix:
- confirm the likely bottleneck category
- reduce active bodies / collisions before micro-tuning
- pool only what is frequently recycled
- remove per-frame allocations where practical
- verify that cleanup happens on shutdown
- test representative counts, not tiny toy counts
- remove or gate debug visuals when done
Asset-pipeline checklist
Before writing loader config:
- inspect the real source image
- verify frame size, spacing, and margin
- decide whether the asset is better as an image, spritesheet, or atlas
- prefer built-in NineSlice / ThreeSlice when the art supports it
- use custom composition only when the art truly demands it
Final-response checklist
Before responding to the user, ensure the answer:
- reflects the requested genre, platform, and art style
- uses Phaser 3 APIs and terminology
- recommends a physics system deliberately
- keeps the architecture proportional to the job
- includes concrete next steps or validation steps
- avoids unnecessary boilerplate
Scenes, State, and Architecture
Contents
- Scene lifecycle essentials
- Scene responsibilities
- Pause vs sleep vs stop vs remove
- Cross-scene state patterns
- Architecture sizes
- UI overlay pattern
- Lifecycle-safe cleanup
- Scene restart pitfalls
- Example scene skeleton
- Architecture anti-patterns
Scene lifecycle essentials
A Phaser scene is booted once, started zero or more times, and can be shut down and started again until it is removed.
Use the lifecycle like this:
init(data): receive start parameters and reset scene-local statepreload(): queue assets required at scene startcreate(): create objects, input bindings, colliders, timers, UIupdate(time, delta): orchestrate per-frame behavior
Important nuance:
- shutdown means the scene stopped running and may start again later
- destroy means the scene was removed and cannot be used again
Write code that survives scene restarts cleanly.
Scene responsibilities
Use scenes as top-level game modes, not as random code buckets.
Typical split:
- BootScene: startup-critical loading, splash, data priming
- MenuScene: title, difficulty, save-slot choice, options entry
- GameScene: core simulation and world logic
- UIScene: HUD, counters, health bars, crosshairs, inventory overlays
- PauseScene: pause menu or modal overlay
- GameOverScene: run summary, restart flow, progression summary
Keep HUD and gameplay separate unless the game is tiny.
Pause vs sleep vs stop vs remove
Use the right scene transition operation:
scene.pause(key): scene still renders, but does not updatescene.sleep(key): scene neither updates nor rendersscene.stop(key): scene shuts down and can be started again laterscene.remove(key): scene is destroyed and removed from the manager
Practical guidance:
- use pause for gameplay under a visible pause overlay
- use sleep for background scenes you want to wake later
- use stop for scenes that should fully reset on next start
- use remove only when the scene should never be reused or is dynamically created
Cross-scene state patterns
Choose the smallest state-sharing mechanism that fits.
1. Scene start data
Use for one-time transitions:
this.scene.start('GameOverScene', { score: this.score, timeMs: this.elapsedMs });Best for:
- score / result handoff
- selected level or seed
- checkpoint data
- modal scene parameters
2. Global registry
Use for small game-wide state:
this.registry.set('musicEnabled', true);
this.registry.set('coins', 42);
const coins = this.registry.get('coins');Best for:
- settings
- meta progression
- currently selected profile or seed
- small shared counters
Do not turn the registry into an unstructured dumping ground.
3. Services / managers
Use for medium or large shared systems:
AudioServiceSaveServiceRunStateInventoryServiceLevelDirector
These can be plain modules, classes owned by bootstrap code, or scene-attached services.
Use services when:
- multiple scenes read and write the same domain state
- invariants matter
- the state has behavior, not just values
4. Events between scenes
Use events for decoupled notifications:
// UIScene
this.game.events.emit('hud:toggle-minimap', true);
// GameScene
this.game.events.on('hud:toggle-minimap', this.onToggleMinimap, this);If you attach cross-scene listeners, add cleanup on shutdown / destroy.
Architecture sizes
Small / jam game
Use:
- 2-4 scenes
- constants file
- a few entity classes
- minimal services
Avoid building a full ECS or data layer unless the user asked for it.
Mid-size action game
Use:
- scenes
- entity classes
- systems for combat, spawning, AI, pickups
- constants / content config
- one or two shared services
This is the default architecture for many production Phaser games.
Large content-heavy game
Use:
- scenes as shells for game modes
- data-driven content tables / JSON
- reusable services for save, progression, encounter generation
- UI layer separate from world simulation
- typed content schemas if the project uses TypeScript
UI overlay pattern
A dedicated UI scene usually scales better than baking HUD into the gameplay scene.
// In GameScene.create()
this.scene.launch('UIScene', { ownerScene: 'GameScene' });
// In UIScene.create(data)
this.ownerSceneKey = data.ownerScene;
this.game.events.on('score:changed', this.onScoreChanged, this);Use a UI scene when you need:
- HUD independent of camera scroll
- pause overlays
- inventory or modal panels
- cross-scene UI that survives game scene resets
Keep the UI scene thin; it should render and react, not own the game simulation.
Lifecycle-safe cleanup
Do not invent a shutdown() scene method and assume Phaser will call it. Register cleanup with scene events.
export class GameScene extends Phaser.Scene {
private onResizeBound?: () => void;
create() {
this.events.once(Phaser.Scenes.Events.SHUTDOWN, this.onShutdown, this);
this.events.once(Phaser.Scenes.Events.DESTROY, this.onDestroy, this);
this.onResizeBound = () => {
// layout logic
};
this.scale.on(Phaser.Scale.Events.RESIZE, this.onResizeBound);
}
private onShutdown() {
if (this.onResizeBound) {
this.scale.off(Phaser.Scale.Events.RESIZE, this.onResizeBound);
}
this.time.removeAllEvents();
this.tweens.killAll();
}
private onDestroy() {
// final teardown for permanently removed scenes
}
}At shutdown, clean up:
- event listeners attached to global emitters,
game.events,scale,registry, or foreign scenes - long-lived timers
- tweens that should not survive a restart
- cached references to scene objects that will be recreated
Scene restart pitfalls
These are common sources of bugs:
- registering keyboard or resize listeners every
create()without removing them - keeping pooled objects in module scope after the scene stops
- storing references to destroyed sprites in services
- relying on constructor initialization instead of resetting state in
init()/create() - launching UI or pause scenes multiple times without checking whether they already exist
Example scene skeleton
export class GameScene extends Phaser.Scene {
private cursors!: Phaser.Types.Input.Keyboard.CursorKeys;
private player!: Phaser.Physics.Arcade.Sprite;
private level = 1;
constructor() {
super('GameScene');
}
init(data: { level?: number }) {
this.level = data.level ?? 1;
}
preload() {
this.load.image('player', 'assets/player.png');
}
create() {
this.events.once(Phaser.Scenes.Events.SHUTDOWN, this.onShutdown, this);
this.player = this.physics.add.sprite(64, 64, 'player');
this.cursors = this.input.keyboard!.createCursorKeys();
}
update(_: number, delta: number) {
const speed = 180;
this.player.setVelocity(0);
if (this.cursors.left.isDown) this.player.setVelocityX(-speed);
else if (this.cursors.right.isDown) this.player.setVelocityX(speed);
// Delegate deeper logic elsewhere once the scene grows
this.updateWorld(delta);
}
private updateWorld(delta: number) {
// systems / entity orchestration
}
private onShutdown() {
// remove cross-scene listeners, timers, etc
}
}Architecture anti-patterns
Avoid these unless the game is truly trivial:
- one mega-scene that owns gameplay, HUD, menus, save logic, and audio policy
- using the registry for every piece of state
- keeping all entities as anonymous sprites with logic in
update() - attaching input listeners inside every entity constructor
- carrying game objects across scenes with
ignoreDestroyunless you have a very deliberate ownership model - deep inheritance trees for enemy variants when configuration + composition would work better
Rule of thumb
If a scene is hard to restart safely, its responsibilities are probably too broad.
Setup and Build
Contents
- New-project defaults
- Recommended folder layouts
- Game configuration patterns
- Scale and renderer decisions
- Pixel-art settings
- Loader timing and asset strategy
- Vite / static asset handling
- Example bootstrap
- Validation checklist
New-project defaults
For a new Phaser 3 project, prefer a modern browser build setup with TypeScript unless the repository already uses plain JavaScript.
Use this as the default stack:
- Phaser 3
- Vite for local dev and production builds
- TypeScript for scene keys, asset keys, and physics object typing
public/assetsfor static runtime files, or module imports for bundled art filessrc/game(orsrc/) as the game code root
If the user already has a Webpack, Parcel, Bun, or plain HTML setup, stay consistent unless they asked for a migration.
Recommended folder layouts
Small game / prototype
src/
├── main.ts
├── game/
│ ├── config.ts
│ ├── constants.ts
│ └── scenes/
│ ├── BootScene.ts
│ ├── GameScene.ts
│ └── UIScene.ts
public/
└── assets/Mid-size game
src/
├── main.ts
├── game/
│ ├── config/
│ │ ├── game-config.ts
│ │ └── keys.ts
│ ├── scenes/
│ ├── entities/
│ ├── systems/
│ ├── services/
│ └── ui/
public/
└── assets/Content-heavy game
src/
├── main.ts
├── game/
│ ├── config/
│ ├── scenes/
│ ├── entities/
│ ├── systems/
│ ├── services/
│ ├── ui/
│ ├── data/
│ └── utils/
public/
└── assets/Use the smallest structure that cleanly supports the current scope.
Game configuration patterns
Baseline Arcade config
import Phaser from 'phaser';
export const gameConfig: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 960,
height: 540,
backgroundColor: '#101418',
parent: 'game-root',
scene: [],
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
}
};Matter config
export const gameConfig: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 1280,
height: 720,
scene: [],
physics: {
default: 'matter',
matter: {
gravity: { y: 1 },
debug: false
}
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
}
};Pixel-art config
export const gameConfig: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 320,
height: 180,
pixelArt: true,
roundPixels: true,
autoRound: true,
scene: [],
physics: {
default: 'arcade',
arcade: { debug: false }
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
}
};Scale and renderer decisions
Use this decision table:
| Need | Recommendation |
|---|---|
| Standard responsive game | Phaser.Scale.FIT |
| UI-heavy app or layout that should resize to the browser | Phaser.Scale.RESIZE |
| Manually controlled canvas size | Phaser.Scale.NONE |
| Unsure about renderer | Phaser.AUTO |
| Pixel art | pixelArt: true, integer-friendly base resolution |
| Heavy post-processing or filters | prefer WebGL / AUTO |
Important host-page rules:
- Put the game inside a parent container you control
- Do not add padding directly to the Phaser parent element
- Put borders, padding, or layout chrome on a wrapper outside the Phaser parent
- Let Phaser manage the canvas display size; avoid overriding canvas width / height with ad hoc CSS
Pixel-art settings
Use all of these together when the art style is pixel art:
pixelArt: trueroundPixels: true- low native resolution with
FIT - camera
roundPixels = trueif camera follow produces shimmer - integer zoom if possible
Avoid:
- high native resolution plus CSS downscaling
- sub-pixel camera scroll on crisp pixel art
- mixing linear-scaled UI art and pixel-art gameplay without deliberate separation
Loader timing and asset strategy
Use preload() for assets needed when the scene starts
preload() {
this.load.image('logo', 'assets/ui/logo.png');
this.load.audio('confirm', [
'assets/audio/confirm.ogg',
'assets/audio/confirm.mp3'
]);
}Lazy-load later when it improves startup time
If assets are loaded outside preload(), remember to start the loader manually:
queueLevelTwoAssets() {
this.load.image('boss', 'assets/enemies/boss.png');
this.load.tilemapTiledJSON('level2', 'assets/maps/level2.json');
this.load.start();
}Use lazy loading for:
- later levels
- cosmetic-only content
- optional menu art
- large boss or cutscene assets
Do not lazy-load assets that are required immediately on scene start.
Boot scene guidance
Use a Boot scene for:
- a loading bar or splash
- startup-critical assets
- decoding or preparing data needed before the first menu / game scene
Do not turn Boot into a mandatory "load the whole game forever" scene unless the game is tiny.
Vite / static asset handling
Two safe patterns:
1. Static assets in public/assets
Use this for larger asset libraries or files referenced by string paths:
this.load.image('player', 'assets/player.png');
this.load.audio('bgm', ['assets/music.ogg', 'assets/music.mp3']);2. Imported bundled assets
Use this for a few assets that belong tightly to one module:
import logoImg from '../assets/logo.png';
preload() {
this.load.image('logo', logoImg);
}Choose one approach consistently per asset family.
Example bootstrap
// src/main.ts
import Phaser from 'phaser';
import { gameConfig } from './game/config/game-config';
import { BootScene } from './game/scenes/BootScene';
import { MenuScene } from './game/scenes/MenuScene';
import { GameScene } from './game/scenes/GameScene';
import { UIScene } from './game/scenes/UIScene';
new Phaser.Game({
...gameConfig,
scene: [BootScene, MenuScene, GameScene, UIScene]
});Validation checklist
Before finishing a setup task, verify:
- the scene array contains the expected startup order
- the chosen scale mode fits the target platform
- the physics default matches the actual gameplay needs
- pixel-art projects use pixel-art-friendly settings
- asset paths align with the repository's bundler strategy
- startup scenes only preload what they truly need
Tilemaps, Camera, Input, and Audio
Contents
- Tilemap defaults
- Tiled layer organization
- Loading maps and tilesets
- Collision setup
- Object layers and spawn data
- Camera patterns
- Input mapping
- Gamepad support
- Audio patterns
- Timers and tweens
- Common mistakes
Tilemap defaults
Use Tiled JSON unless the user explicitly needs procedural-only map generation.
Good reasons to use tilemaps:
- authored platformer levels
- collision-heavy overworlds
- spawn data in object layers
- reusable environment art
- parallax or foreground / background separation
Tiled layer organization
A practical layer order:
Foreground
UI markers / debug helpers (dev only)
Objects
Enemies
Collectibles
Ground
BackgroundFor complex maps, split object layers by purpose:
SpawnPointsEnemiesCollectiblesTriggersDoorsNPCs
Do not overload one giant object layer with unrelated semantics.
Loading maps and tilesets
preload() {
this.load.tilemapTiledJSON('level-1', 'assets/maps/level-1.json');
this.load.image('terrain', 'assets/maps/terrain.png');
this.load.image('props', 'assets/maps/props.png');
}create() {
const map = this.make.tilemap({ key: 'level-1' });
const terrain = map.addTilesetImage('terrain', 'terrain');
const props = map.addTilesetImage('props', 'props');
const background = map.createLayer('Background', [terrain, props], 0, 0);
const ground = map.createLayer('Ground', [terrain, props], 0, 0);
const foreground = map.createLayer('Foreground', [terrain, props], 0, 0);
foreground.setDepth(100);
}If the tileset uses spacing or margin, pass those values in addTilesetImage.
const terrain = map.addTilesetImage('terrain', 'terrain', 32, 32, 1, 2);Collision setup
Prefer collision by tile property where possible. It survives art changes better than raw index ranges.
ground.setCollisionByProperty({ collides: true });
this.physics.add.collider(this.player, ground);Use index-based collision when the map export or tileset metadata is too simple for properties:
ground.setCollisionBetween(1, 64);Use tile callbacks for hazards or triggers when that reads better than large collider callbacks.
Object layers and spawn data
Use object layers for spawn points and designer-authored metadata.
const spawn = map.findObject('SpawnPoints', (obj) => obj.name === 'player-start');
this.player = this.physics.add.sprite(spawn!.x!, spawn!.y!, 'player');Read custom properties from Tiled objects with a helper:
function getObjectProp<T>(obj: { properties?: Array<{ name: string; value: unknown }> }, name: string): T | undefined {
return obj.properties?.find((p) => p.name === name)?.value as T | undefined;
}Use object layers for:
- spawn locations
- patrol points
- scene exits
- chest or switch metadata
- checkpoint positions
Camera patterns
Follow camera
this.cameras.main.startFollow(this.player, true, 0.12, 0.12);
this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
this.physics.world.setBounds(0, 0, map.widthInPixels, map.heightInPixels);Deadzone for larger worlds
this.cameras.main.setDeadzone(160, 90);Pixel-art camera rounding
this.cameras.main.roundPixels = true;Use this when pixel art shimmers during follow movement.
HUD separation
Use a UI scene for HUD instead of setScrollFactor(0) on everything. Reserve setScrollFactor(0) for a few world-attached elements that truly should ignore camera scroll.
Input mapping
Keep input scene-owned and expose an input state object.
type InputState = {
left: boolean;
right: boolean;
up: boolean;
down: boolean;
jumpPressed: boolean;
firePressed: boolean;
};
create() {
const cursors = this.input.keyboard!.createCursorKeys();
const fire = this.input.keyboard!.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
this.inputState = {
left: false,
right: false,
up: false,
down: false,
jumpPressed: false,
firePressed: false
};
this.updateInputState = () => {
this.inputState.left = cursors.left.isDown;
this.inputState.right = cursors.right.isDown;
this.inputState.up = cursors.up.isDown;
this.inputState.down = cursors.down.isDown;
this.inputState.jumpPressed = Phaser.Input.Keyboard.JustDown(cursors.up);
this.inputState.firePressed = Phaser.Input.Keyboard.JustDown(fire);
};
}
update() {
this.updateInputState();
this.playerController.update(this.inputState);
}This makes remapping, replay systems, AI takeover, and mobile control injection easier.
Pointer and touch
Use Phaser's unified pointer system for mouse and touch.
this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
this.spawnPing(pointer.worldX, pointer.worldY);
});Touch-friendly patterns:
- enlarge hit areas
- avoid placing important controls against unsafe screen edges
- use drag or tap consistently
- test with one pointer first; only add multi-touch if the game truly needs it
Gamepad support
Add gamepad support when the game fits it, but keep it optional.
create() {
this.input.gamepad?.once('connected', (pad: Phaser.Input.Gamepad.Gamepad) => {
this.pad = pad;
});
}
update() {
if (this.pad) {
this.inputState.left ||= this.pad.left;
this.inputState.right ||= this.pad.right;
this.inputState.jumpPressed ||= this.pad.A;
}
}Do not hard-require a gamepad mapping in a browser game unless the user asked for it.
Audio patterns
Use two channels of intent:
- BGM: longer loops, scene or mode ownership
- SFX: short one-shots, event-driven
Loading with format fallbacks
preload() {
this.load.audio('bgm-forest', [
'assets/audio/bgm-forest.ogg',
'assets/audio/bgm-forest.mp3'
]);
this.load.audio('pickup', [
'assets/audio/pickup.ogg',
'assets/audio/pickup.mp3'
]);
}Basic usage
create() {
this.music = this.sound.add('bgm-forest', { loop: true, volume: 0.5 });
this.music.play();
}
collectCoin() {
this.sound.play('pickup', { volume: 0.8 });
}Centralize user settings such as music mute and SFX mute in the registry or a dedicated audio service.
Timers and tweens
Prefer Phaser timers and tweens over ad hoc setTimeout.
Timer example
this.time.delayedCall(1200, () => {
this.spawnWave();
});Tween example
this.tweens.add({
targets: this.promptText,
alpha: 0.2,
duration: 400,
yoyo: true,
repeat: -1
});Track timers or tweens that must be canceled on scene shutdown.
Common mistakes
- storing essential spawn logic only in hand-written code when Tiled objects would be clearer
- forgetting to match the Tiled tileset name in
addTilesetImage - not setting camera and physics bounds to the map size
- mixing scene-owned input with entity-owned listeners
- assuming
pointer.x/pointer.yare world coordinates when the camera moved - starting multiple music tracks because a scene restart did not stop or reuse the old one
- using browser
setTimeoutinstead of Phaser's clock for gameplay timing
Related skills
FAQ
Which Phaser version does phaser-best-practices target?
phaser-best-practices targets Phaser major version 3 for JavaScript or TypeScript browser games. Metadata marks phaser-major as 3 and skill-type as framework within onmax/nuxt-skills.
What Phaser features does the skill cover?
phaser-best-practices covers scenes, entities, physics, UI, tilemaps, animations, input, audio, cameras, new project scaffolding, and Phaser-specific bug and performance fixes for browser games.