phaser-gamedev by chongdashu/phaserjs-tinyswords
npx skills add https://github.com/chongdashu/phaserjs-tinyswords --skill phaser-gamedev使用 Phaser 3 基于场景的架构和物理系统,构建快速、精美的 2D 浏览器游戏。
游戏不是静态的 UI——它们是动态系统,其中实体交互、状态演变,玩家输入驱动一切。在编写代码之前,请先进行架构思考。
构建前请思考:
核心原则:
每个 Phaser 游戏都从一个配置对象开始。
const config = {
type: Phaser.AUTO, // WebGL 并回退到 Canvas
width: 800,
height: 600,
scene: [BootScene, GameScene]
};
const game = new Phaser.Game(config);
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'game-container', // DOM 元素 ID
backgroundColor: '#2d2d2d',
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false // 开发期间启用
}
},
scene: [BootScene, MenuScene, GameScene, GameOverScene]
};
| 系统 | 适用场景 |
|---|---|
| Arcade | 平台游戏、射击游戏、大多数 2D 游戏。快速、简单的 AABB 碰撞 |
| Matter | 物理谜题、布娃娃系统、真实碰撞。较慢,更精确 |
| None | 菜单场景、视觉小说、卡牌游戏 |
场景是基本的组织单元。每个场景都有生命周期。
class GameScene extends Phaser.Scene {
constructor() {
super('GameScene'); // 用于引用的场景键名
}
init(data) {
// 首先调用。接收来自前一个场景的数据
this.level = data.level || 1;
}
preload() {
// 加载资源。在 create() 之前运行
this.load.image('player', 'assets/player.png');
this.load.spritesheet('enemy', 'assets/enemy.png', {
frameWidth: 32, frameHeight: 32
});
}
create() {
// 设置游戏对象、物理系统、输入
this.player = this.physics.add.sprite(100, 100, 'player');
this.cursors = this.input.keyboard.createCursorKeys();
}
update(time, delta) {
// 游戏循环。每帧调用
// delta = 自上一帧以来的毫秒数
this.player.x += this.speed * (delta / 1000);
}
}
// 启动新场景(停止当前场景)
this.scene.start('GameOverScene', { score: this.score });
// 并行启动场景(两者同时运行)
this.scene.launch('UIScene');
// 暂停/恢复场景
this.scene.pause('GameScene');
this.scene.resume('GameScene');
// 停止场景
this.scene.stop('UIScene');
scenes/
├── BootScene.js # 资源加载,进度条
├── MenuScene.js # 标题屏幕,选项
├── GameScene.js # 主要游戏玩法
├── UIScene.js # HUD 叠加层(并行启动)
├── PauseScene.js # 暂停菜单叠加层
└── GameOverScene.js # 结束屏幕,重启选项
Phaser 中所有可见的东西都是游戏对象。
// 图像(静态)
this.add.image(400, 300, 'background');
// 精灵(可动画化,支持物理)
const player = this.add.sprite(100, 100, 'player');
// 文本
const score = this.add.text(16, 16, 'Score: 0', {
fontSize: '32px',
fill: '#fff'
});
// 图形(绘制形状)
const graphics = this.add.graphics();
graphics.fillStyle(0xff0000);
graphics.fillRect(100, 100, 50, 50);
// 容器(分组对象)
const container = this.add.container(400, 300, [sprite1, sprite2]);
// 瓦片地图
const map = this.make.tilemap({ key: 'level1' });
// 基础精灵
const sprite = this.add.sprite(x, y, 'textureKey');
// 带物理体的精灵
const sprite = this.physics.add.sprite(x, y, 'textureKey');
// 从精灵表帧创建
const sprite = this.add.sprite(x, y, 'sheet', frameIndex);
// 从图集创建
const sprite = this.add.sprite(x, y, 'atlas', 'frameName');
适用于大多数 2D 游戏的快速、简单物理系统。
// 在精灵上启用物理
this.physics.add.sprite(x, y, 'player');
// 或为现有精灵添加物理
this.physics.add.existing(sprite);
// 配置物理体
sprite.body.setVelocity(200, 0);
sprite.body.setBounce(0.5);
sprite.body.setCollideWorldBounds(true);
sprite.body.setGravityY(300);
// 碰撞检测
this.physics.add.collider(player, platforms);
this.physics.add.overlap(player, coins, collectCoin, null, this);
function collectCoin(player, coin) {
coin.disableBody(true, true); // 从物理世界移除并隐藏
this.score += 10;
}
// 静态组(平台、墙壁)
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
// 动态组(敌人、子弹)
const enemies = this.physics.add.group({
key: 'enemy',
repeat: 5,
setXY: { x: 100, y: 0, stepX: 70 }
});
enemies.children.iterate(enemy => {
enemy.setBounce(Phaser.Math.FloatBetween(0.4, 0.8));
});
用于真实的物理模拟。
// 配置
physics: {
default: 'matter',
matter: {
gravity: { y: 1 },
debug: true
}
}
// 创建物理体
const ball = this.matter.add.circle(400, 100, 25);
const box = this.matter.add.rectangle(400, 400, 100, 50, { isStatic: true });
// 带 Matter 物理体的精灵
const player = this.matter.add.sprite(100, 100, 'player');
player.setFriction(0.005);
player.setBounce(0.9);
// 方向键
this.cursors = this.input.keyboard.createCursorKeys();
// 在 update() 中
if (this.cursors.left.isDown) {
player.setVelocityX(-160);
} else if (this.cursors.right.isDown) {
player.setVelocityX(160);
}
if (this.cursors.up.isDown && player.body.touching.down) {
player.setVelocityY(-330); // 跳跃
}
// 自定义按键
this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
// 按键事件
this.input.keyboard.on('keydown-SPACE', () => {
this.fire();
});
// 点击/轻触
this.input.on('pointerdown', (pointer) => {
console.log(pointer.x, pointer.y);
});
// 使对象可交互
sprite.setInteractive();
sprite.on('pointerdown', () => {
sprite.setTint(0xff0000);
});
sprite.on('pointerup', () => {
sprite.clearTint();
});
// 拖拽
this.input.setDraggable(sprite);
this.input.on('drag', (pointer, obj, dragX, dragY) => {
obj.x = dragX;
obj.y = dragY;
});
// 在 create() 中 - 定义一次
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1 // 永久循环
});
this.anims.create({
key: 'jump',
frames: [{ key: 'player', frame: 4 }],
frameRate: 20
});
// 从图集创建
this.anims.create({
key: 'explode',
frames: this.anims.generateFrameNames('atlas', {
prefix: 'explosion_',
start: 1,
end: 8,
zeroPad: 2
}),
frameRate: 16,
hideOnComplete: true
});
// 播放动画
sprite.anims.play('walk', true); // true = 如果已在播放则忽略
// 播放一次
sprite.anims.play('jump');
// 停止
sprite.anims.stop();
// 动画事件
sprite.on('animationcomplete', (anim, frame) => {
if (anim.key === 'die') {
sprite.destroy();
}
});
preload() {
// 图像
this.load.image('sky', 'assets/sky.png');
// 精灵表
this.load.spritesheet('player', 'assets/player.png', {
frameWidth: 32,
frameHeight: 48
});
// 图集(TexturePacker)
this.load.atlas('sprites', 'assets/sprites.png', 'assets/sprites.json');
// 瓦片地图
this.load.tilemapTiledJSON('map', 'assets/level1.json');
this.load.image('tiles', 'assets/tileset.png');
// 音频
this.load.audio('bgm', 'assets/music.mp3');
this.load.audio('sfx', ['assets/sound.ogg', 'assets/sound.mp3']);
// 进度跟踪
this.load.on('progress', (value) => {
console.log(`Loading: ${Math.round(value * 100)}%`);
});
}
class BootScene extends Phaser.Scene {
constructor() {
super('BootScene');
}
preload() {
// 加载条
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const progressBar = this.add.graphics();
const progressBox = this.add.graphics();
progressBox.fillStyle(0x222222, 0.8);
progressBox.fillRect(width/2 - 160, height/2 - 25, 320, 50);
this.load.on('progress', (value) => {
progressBar.clear();
progressBar.fillStyle(0xffffff, 1);
progressBar.fillRect(width/2 - 150, height/2 - 15, 300 * value, 30);
});
// 在此加载所有游戏资源
this.load.image('player', 'assets/player.png');
// ... 更多资源
}
create() {
this.scene.start('MenuScene');
}
}
preload() {
this.load.tilemapTiledJSON('map', 'assets/map.json');
this.load.image('tiles', 'assets/tileset.png');
}
create() {
const map = this.make.tilemap({ key: 'map' });
const tileset = map.addTilesetImage('tileset-name-in-tiled', 'tiles');
// 创建图层(匹配 Tiled 中的名称)
const backgroundLayer = map.createLayer('Background', tileset, 0, 0);
const groundLayer = map.createLayer('Ground', tileset, 0, 0);
// 在特定瓦片上启用碰撞
groundLayer.setCollisionByProperty({ collides: true });
// 或通过瓦片索引
groundLayer.setCollisionBetween(1, 100);
// 添加与玩家的碰撞
this.physics.add.collider(this.player, groundLayer);
}
// 从 Tiled 对象图层生成生成点
const spawnPoint = map.findObject('Objects', obj => obj.name === 'spawn');
this.player = this.physics.add.sprite(spawnPoint.x, spawnPoint.y, 'player');
// 从图层创建对象
const coins = map.createFromObjects('Objects', {
name: 'coin',
key: 'coin'
});
this.physics.world.enable(coins);
game/
├── src/
│ ├── scenes/
│ │ ├── BootScene.js
│ │ ├── MenuScene.js
│ │ ├── GameScene.js
│ │ └── UIScene.js
│ ├── gameObjects/
│ │ ├── Player.js
│ │ ├── Enemy.js
│ │ └── Collectible.js
│ ├── systems/
│ │ ├── InputManager.js
│ │ └── AudioManager.js
│ ├── config/
│ │ └── gameConfig.js
│ └── main.js
├── assets/
│ ├── images/
│ ├── audio/
│ ├── tilemaps/
│ └── fonts/
├── index.html
└── package.json
// main.js
import Phaser from 'phaser';
import BootScene from './scenes/BootScene';
import GameScene from './scenes/GameScene';
import { gameConfig } from './config/gameConfig';
const config = {
...gameConfig,
scene: [BootScene, GameScene]
};
new Phaser.Game(config);
❌ 全局状态混乱:将游戏状态存储在 window 或模块全局变量中 为何不好:难以追踪的 bug,场景切换会破坏状态 更好的做法:使用场景数据、注册表或专用的状态管理器
❌ 在 Create 中加载:在 create() 而非 preload() 中加载资源 为何不好:引用时资源可能尚未就绪 更好的做法:始终在 preload() 中加载,使用启动场景加载所有资源
❌ 依赖帧的逻辑:使用帧计数而非增量时间 为何不好:游戏速度随帧率变化 更好的做法:使用 this.speed * (delta / 1000) 实现一致的运动
❌ 物理系统过度使用:为简单的平台游戏碰撞使用 Matter 为何不好:性能损耗,不必要的复杂性 更好的做法:Arcade 物理能满足 90% 的 2D 游戏需求
❌ 单体场景:一个包含所有游戏逻辑的巨型场景 为何不好:难以维护,难以添加功能 更好的做法:为菜单、游戏玩法、UI 叠加层使用独立的场景
❌ 魔法数字:代码中散布的硬编码值 为何不好:无法平衡,不一致 更好的做法:配置对象、常量文件
❌ 忽略对象池:每帧创建/销毁对象 为何不好:内存抖动,垃圾回收卡顿 更好的做法:使用带有 setActive(false) / setVisible(false) 的组
❌ 同步资源访问:假设资源立即加载完成 为何不好:竞态条件,未定义的纹理 更好的做法:链式启动场景,使用加载事件
❌ 假设精灵表帧尺寸:使用猜测的帧尺寸而未经验证 为何不好:错误的尺寸导致无声的帧损坏;像素级的偏差累积导致视觉效果损坏 更好的做法:打开资源文件,测量帧,计算间距/边距,验证数学计算正确
❌ 忽略精灵表间距:未为有间隙的精灵表指定 spacing 为何不好:帧逐渐偏移;后续帧读取错误的像素区域 更好的做法:检查源资源中帧之间的间隙;在加载器配置中使用 spacing: N
❌ 硬编码九宫格颜色:为所有 UI 面板变体使用单一背景色 为何不好:透明帧边缘在不同资源配色方案下会显示错误的颜色 更好的做法:每个资源的背景色配置;从中心帧(第 4 帧)采样
❌ 使用带内边距帧的九宫格:当艺术内容在每个图块内居中/有内边距时,将整个帧视为切片区域 为何不好:边缘图块贡献内部填充,在面板内部显示为不透明的“侧边栏” 更好的做法:将图块修剪到其有效内容边界(alpha 边界框)并合成/缓存纹理;添加约 1 像素的重叠并禁用平滑以避免接缝
❌ 缩放不连续的 UI 艺术:拉伸包含内部透明间隙的裁剪丝带/横幅行 为何不好:透明间隙被拉伸,导致 UI 看起来分段或填充消失在框架后面。 更好的做法:将资源切片为端部/中心,仅拉伸中心部分,并在以枢轴尺寸渲染之前拼接各部件(带有约 1 像素的重叠并禁用平滑)。
重要提示:游戏实现应根据以下因素而变化:
避免趋同于:
body.setVelocity(x, y)
body.setVelocityX(x)
body.setBounce(x, y)
body.setGravityY(y)
body.setCollideWorldBounds(true)
body.setImmovable(true) // 用于类似静态的动态体
body.setDrag(x, y)
body.setMaxVelocity(x, y)
this.cameras.main // 主摄像机
this.physics.world // 物理世界
this.input.keyboard // 键盘管理器
this.sound // 音频管理器
this.time // 时间/时钟管理器
this.tweens // 补间管理器
this.anims // 动画管理器
this.registry // 跨场景数据存储
this.data // 场景特定数据存储
// 场景事件
this.events.on('pause', callback)
this.events.on('resume', callback)
this.events.on('shutdown', callback)
// 物理事件
this.physics.world.on('worldbounds', callback)
// 游戏对象事件
sprite.on('destroy', callback)
sprite.on('animationcomplete', callback)
Phaser 为你提供了强大的原语——场景、精灵、物理、输入——但架构是你的责任。
以系统的方式思考:你需要哪些场景?存在哪些实体?它们如何交互?在编写代码之前回答这些问题,你的游戏在成长过程中将易于维护。
Claude 能够构建完整、精美的 Phaser 游戏。这些指南指明了道路——它们并未限制道路。
每周安装量
443
代码仓库
GitHub 星标数
28
首次出现
2026 年 1 月 25 日
安全审计
安装于
opencode387
codex377
gemini-cli371
github-copilot342
kimi-cli316
amp312
Build fast, polished 2D browser games using Phaser 3's scene-based architecture and physics systems.
Games are not static UIs—they are dynamic systems where entities interact, state evolves, and player input drives everything. Before writing code, think architecturally.
Before building, ask :
Core principles :
Every Phaser game starts with a configuration object.
const config = {
type: Phaser.AUTO, // WebGL with Canvas fallback
width: 800,
height: 600,
scene: [BootScene, GameScene]
};
const game = new Phaser.Game(config);
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'game-container', // DOM element ID
backgroundColor: '#2d2d2d',
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false // Enable during development
}
},
scene: [BootScene, MenuScene, GameScene, GameOverScene]
};
| System | Use When |
|---|---|
| Arcade | Platformers, shooters, most 2D games. Fast, simple AABB collisions |
| Matter | Physics puzzles, ragdolls, realistic collisions. Slower, more accurate |
| None | Menu scenes, visual novels, card games |
Scenes are the fundamental organizational unit. Each scene has a lifecycle.
class GameScene extends Phaser.Scene {
constructor() {
super('GameScene'); // Scene key for reference
}
init(data) {
// Called first. Receive data from previous scene
this.level = data.level || 1;
}
preload() {
// Load assets. Runs before create()
this.load.image('player', 'assets/player.png');
this.load.spritesheet('enemy', 'assets/enemy.png', {
frameWidth: 32, frameHeight: 32
});
}
create() {
// Set up game objects, physics, input
this.player = this.physics.add.sprite(100, 100, 'player');
this.cursors = this.input.keyboard.createCursorKeys();
}
update(time, delta) {
// Game loop. Called every frame
// delta = milliseconds since last frame
this.player.x += this.speed * (delta / 1000);
}
}
// Start a new scene (stops current)
this.scene.start('GameOverScene', { score: this.score });
// Launch scene in parallel (both run)
this.scene.launch('UIScene');
// Pause/resume scenes
this.scene.pause('GameScene');
this.scene.resume('GameScene');
// Stop a scene
this.scene.stop('UIScene');
scenes/
├── BootScene.js # Asset loading, progress bar
├── MenuScene.js # Title screen, options
├── GameScene.js # Main gameplay
├── UIScene.js # HUD overlay (launched parallel)
├── PauseScene.js # Pause menu overlay
└── GameOverScene.js # End screen, restart option
Everything visible in Phaser is a Game Object.
// Images (static)
this.add.image(400, 300, 'background');
// Sprites (can animate, physics-enabled)
const player = this.add.sprite(100, 100, 'player');
// Text
const score = this.add.text(16, 16, 'Score: 0', {
fontSize: '32px',
fill: '#fff'
});
// Graphics (draw shapes)
const graphics = this.add.graphics();
graphics.fillStyle(0xff0000);
graphics.fillRect(100, 100, 50, 50);
// Containers (group objects)
const container = this.add.container(400, 300, [sprite1, sprite2]);
// Tilemaps
const map = this.make.tilemap({ key: 'level1' });
// Basic sprite
const sprite = this.add.sprite(x, y, 'textureKey');
// Sprite with physics body
const sprite = this.physics.add.sprite(x, y, 'textureKey');
// From spritesheet frame
const sprite = this.add.sprite(x, y, 'sheet', frameIndex);
// From atlas
const sprite = this.add.sprite(x, y, 'atlas', 'frameName');
Fast, simple physics for most 2D games.
// Enable physics on sprite
this.physics.add.sprite(x, y, 'player');
// Or add physics to existing sprite
this.physics.add.existing(sprite);
// Configure body
sprite.body.setVelocity(200, 0);
sprite.body.setBounce(0.5);
sprite.body.setCollideWorldBounds(true);
sprite.body.setGravityY(300);
// Collision detection
this.physics.add.collider(player, platforms);
this.physics.add.overlap(player, coins, collectCoin, null, this);
function collectCoin(player, coin) {
coin.disableBody(true, true); // Remove from physics and hide
this.score += 10;
}
// Static group (platforms, walls)
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
// Dynamic group (enemies, bullets)
const enemies = this.physics.add.group({
key: 'enemy',
repeat: 5,
setXY: { x: 100, y: 0, stepX: 70 }
});
enemies.children.iterate(enemy => {
enemy.setBounce(Phaser.Math.FloatBetween(0.4, 0.8));
});
For realistic physics simulations.
// Config
physics: {
default: 'matter',
matter: {
gravity: { y: 1 },
debug: true
}
}
// Create bodies
const ball = this.matter.add.circle(400, 100, 25);
const box = this.matter.add.rectangle(400, 400, 100, 50, { isStatic: true });
// Sprite with Matter body
const player = this.matter.add.sprite(100, 100, 'player');
player.setFriction(0.005);
player.setBounce(0.9);
// Cursor keys
this.cursors = this.input.keyboard.createCursorKeys();
// In update()
if (this.cursors.left.isDown) {
player.setVelocityX(-160);
} else if (this.cursors.right.isDown) {
player.setVelocityX(160);
}
if (this.cursors.up.isDown && player.body.touching.down) {
player.setVelocityY(-330); // Jump
}
// Custom keys
this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
// Key events
this.input.keyboard.on('keydown-SPACE', () => {
this.fire();
});
// Click/tap
this.input.on('pointerdown', (pointer) => {
console.log(pointer.x, pointer.y);
});
// Make object interactive
sprite.setInteractive();
sprite.on('pointerdown', () => {
sprite.setTint(0xff0000);
});
sprite.on('pointerup', () => {
sprite.clearTint();
});
// Drag
this.input.setDraggable(sprite);
this.input.on('drag', (pointer, obj, dragX, dragY) => {
obj.x = dragX;
obj.y = dragY;
});
// In create() - define once
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1 // Loop forever
});
this.anims.create({
key: 'jump',
frames: [{ key: 'player', frame: 4 }],
frameRate: 20
});
// From atlas
this.anims.create({
key: 'explode',
frames: this.anims.generateFrameNames('atlas', {
prefix: 'explosion_',
start: 1,
end: 8,
zeroPad: 2
}),
frameRate: 16,
hideOnComplete: true
});
// Play animation
sprite.anims.play('walk', true); // true = ignore if already playing
// Play once
sprite.anims.play('jump');
// Stop
sprite.anims.stop();
// Animation events
sprite.on('animationcomplete', (anim, frame) => {
if (anim.key === 'die') {
sprite.destroy();
}
});
preload() {
// Images
this.load.image('sky', 'assets/sky.png');
// Spritesheets
this.load.spritesheet('player', 'assets/player.png', {
frameWidth: 32,
frameHeight: 48
});
// Atlases (TexturePacker)
this.load.atlas('sprites', 'assets/sprites.png', 'assets/sprites.json');
// Tilemaps
this.load.tilemapTiledJSON('map', 'assets/level1.json');
this.load.image('tiles', 'assets/tileset.png');
// Audio
this.load.audio('bgm', 'assets/music.mp3');
this.load.audio('sfx', ['assets/sound.ogg', 'assets/sound.mp3']);
// Progress tracking
this.load.on('progress', (value) => {
console.log(`Loading: ${Math.round(value * 100)}%`);
});
}
class BootScene extends Phaser.Scene {
constructor() {
super('BootScene');
}
preload() {
// Loading bar
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const progressBar = this.add.graphics();
const progressBox = this.add.graphics();
progressBox.fillStyle(0x222222, 0.8);
progressBox.fillRect(width/2 - 160, height/2 - 25, 320, 50);
this.load.on('progress', (value) => {
progressBar.clear();
progressBar.fillStyle(0xffffff, 1);
progressBar.fillRect(width/2 - 150, height/2 - 15, 300 * value, 30);
});
// Load all game assets here
this.load.image('player', 'assets/player.png');
// ... more assets
}
create() {
this.scene.start('MenuScene');
}
}
preload() {
this.load.tilemapTiledJSON('map', 'assets/map.json');
this.load.image('tiles', 'assets/tileset.png');
}
create() {
const map = this.make.tilemap({ key: 'map' });
const tileset = map.addTilesetImage('tileset-name-in-tiled', 'tiles');
// Create layers (match names from Tiled)
const backgroundLayer = map.createLayer('Background', tileset, 0, 0);
const groundLayer = map.createLayer('Ground', tileset, 0, 0);
// Enable collision on specific tiles
groundLayer.setCollisionByProperty({ collides: true });
// Or by tile index
groundLayer.setCollisionBetween(1, 100);
// Add collision with player
this.physics.add.collider(this.player, groundLayer);
}
// Spawn points from Tiled object layer
const spawnPoint = map.findObject('Objects', obj => obj.name === 'spawn');
this.player = this.physics.add.sprite(spawnPoint.x, spawnPoint.y, 'player');
// Create objects from layer
const coins = map.createFromObjects('Objects', {
name: 'coin',
key: 'coin'
});
this.physics.world.enable(coins);
game/
├── src/
│ ├── scenes/
│ │ ├── BootScene.js
│ │ ├── MenuScene.js
│ │ ├── GameScene.js
│ │ └── UIScene.js
│ ├── gameObjects/
│ │ ├── Player.js
│ │ ├── Enemy.js
│ │ └── Collectible.js
│ ├── systems/
│ │ ├── InputManager.js
│ │ └── AudioManager.js
│ ├── config/
│ │ └── gameConfig.js
│ └── main.js
├── assets/
│ ├── images/
│ ├── audio/
│ ├── tilemaps/
│ └── fonts/
├── index.html
└── package.json
// main.js
import Phaser from 'phaser';
import BootScene from './scenes/BootScene';
import GameScene from './scenes/GameScene';
import { gameConfig } from './config/gameConfig';
const config = {
...gameConfig,
scene: [BootScene, GameScene]
};
new Phaser.Game(config);
❌ Global State Soup : Storing game state on window or module globals Why bad : Untrackable bugs, scene transitions break state Better : Use scene data, registries, or dedicated state managers
❌ Loading in Create : Loading assets in create() instead of preload() Why bad : Assets may not be ready when referenced Better : Always load in preload(), use Boot scene for all assets
❌ Frame-Dependent Logic : Using frame count instead of delta time Why bad : Game speed varies with frame rate Better : this.speed * (delta / 1000) for consistent movement
❌ Physics Overkill : Using Matter for simple platformer collisions Why bad : Performance hit, unnecessary complexity Better : Arcade physics handles 90% of 2D game needs
❌ Monolithic Scenes : One giant scene with all game logic Why bad : Unmaintainable, hard to add features Better : Separate scenes for menus, gameplay, UI overlays
❌ Magic Numbers : Hardcoded values scattered in code Why bad : Impossible to balance, inconsistent Better : Config objects, constants files
❌ Ignoring Object Pooling : Creating/destroying objects every frame Why bad : Memory churn, garbage collection stutters Better : Use groups with setActive(false) / setVisible(false)
❌ Synchronous Asset Access : Assuming assets load instantly Why bad : Race conditions, undefined textures Better : Chain scene starts, use load events
❌ Assuming Spritesheet Frame Dimensions : Using guessed frame sizes without verifying Why bad : Wrong dimensions cause silent frame corruption; off-by-pixels compounds into broken visuals Better : Open asset file, measure frames, calculate with spacing/margin, verify math adds up
❌ Ignoring Spritesheet Spacing : Not specifying spacing for gapped spritesheets Why bad : Frames shift progressively; later frames read wrong pixel regions Better : Check source asset for gaps between frames; use spacing: N in loader config
❌ Hardcoding Nine-Slice Colors : Using single background color for all UI panel variants Why bad : Transparent frame edges reveal wrong color for different asset color schemes Better : Per-asset background color config; sample from center frame (frame 4)
❌ Nine-Slice with Padded Frames : Treating the full frame as the slice region when the art is centered/padded inside each tile Why bad : Edge tiles contribute interior fill, showing up as opaque “side bars” inside the panel Better : Trim tiles to their effective content bounds (alpha bbox) and composite/cache a texture; add ~1px overlap + disable smoothing to avoid seams
❌ Scaling Discontinuous UI Art : Stretching a cropped ribbon/banner row that contains internal transparent gaps Why bad : The transparent gutters get stretched, so the UI looks segmented or the fill disappears behind the frame. Better : Slice the asset into caps/center, stretch only the center, and stitch the pieces (with ~1px overlap + smoothing disabled) before rendering at pivot sizes.
IMPORTANT : Game implementations should vary based on:
Avoid converging on :
body.setVelocity(x, y)
body.setVelocityX(x)
body.setBounce(x, y)
body.setGravityY(y)
body.setCollideWorldBounds(true)
body.setImmovable(true) // For static-like dynamic bodies
body.setDrag(x, y)
body.setMaxVelocity(x, y)
this.cameras.main // Main camera
this.physics.world // Physics world
this.input.keyboard // Keyboard manager
this.sound // Audio manager
this.time // Time/clock manager
this.tweens // Tween manager
this.anims // Animation manager
this.registry // Cross-scene data store
this.data // Scene-specific data store
// Scene events
this.events.on('pause', callback)
this.events.on('resume', callback)
this.events.on('shutdown', callback)
// Physics events
this.physics.world.on('worldbounds', callback)
// Game object events
sprite.on('destroy', callback)
sprite.on('animationcomplete', callback)
Phaser gives you powerful primitives—scenes, sprites, physics, input—but architecture is your responsibility.
Think in systems: What scenes do you need? What entities exist? How do they interact? Answer these questions before writing code, and your game will be maintainable as it grows.
Claude is capable of building complete, polished Phaser games. These guidelines illuminate the path—they don't fence it.
Weekly Installs
443
Repository
GitHub Stars
28
First Seen
Jan 25, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode387
codex377
gemini-cli371
github-copilot342
kimi-cli316
amp312
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装
Seaborn 统计可视化库教程:Python 数据可视化与多变量分析指南
249 周安装
阿里云Model Studio路由指南:一站式调用文生图、语音、视频等AI技能
249 周安装
阿里云DashScope Z-Image Turbo文生图API:快速AI图像生成教程与调用指南
249 周安装
阿里云备份HBR技能:使用OpenAPI和SDK管理云备份资源的完整指南
249 周安装
Spec-Kit技能:基于宪法的规范驱动开发工作流,7阶段GitHub功能开发指南
249 周安装
阿里云AnalyticDB MySQL管理指南:使用OpenAPI与SDK进行云数据库操作
249 周安装