10分钟实战:用Phaser3开发接金币小游戏
告别重型游戏引擎,本教程带你10分钟用Vite+Phaser3搭建开发环境。通过实战“接金币”小游戏,掌握场景生命周期、Arcade物理引擎与碰撞检测,快速上手HTML5 2D游戏开发,独立实现带输入交互的完整项目。

10分钟实战:用Phaser3开发接金币小游戏
做后端或前端开发时,很多同事想自己写个小游戏练手,或者给内部系统加个互动彩蛋。但一听到 Unity、Cocos Creator 就头大:动辄几 GB 的安装包,庞大的工程结构,对只想跑个轻量 2D 网页小游戏的人来说,实在过于笨重。
如果只需要一个免费、文档齐全、开箱即用的 HTML5 游戏方案,Phaser 是目前的优选。它不玩虚的,核心聚焦三件事:怎么把图形渲染到画布上、怎么让游戏每帧稳定运行、怎么处理物理碰撞与用户输入。
本篇不拆解底层源码,也不做特性罗列。直接带你从零搭建环境,写一个带物理碰撞的**“接金币”小游戏**。按照步骤操作,你能彻底跑通游戏开发的核心循环,后续独立开新项目完全能自己搞定。
🛠 前置准备与环境初始化
- 环境:Node.js 16+(用于运行本地开发服务器)
- 基础:掌握 JavaScript 基础语法即可,无需图形学或复杂数学背景
- 心态:别被底层渲染细节吓退。Phaser 已经封装了向量计算与渲染管线,你只需要专注业务逻辑。
💡 为什么必须用本地服务器?
新手常犯的错误是直接双击.html文件打开。Phaser 加载精灵图本质是发起网络请求,浏览器出于安全策略,会拦截file://协议下的跨域读取,导致控制台狂刷 CORS 报错。使用 Vite 启动轻量服务器,能直接规避 80% 的资源加载玄学问题。
打开终端,依次执行以下命令初始化项目:
bash
npm create vite@latest phaser-game -- --template vanilla
cd phaser-game
npm install phaser
npm run dev
环境就绪后,打开 src/main.js。Phaser 的架构非常精简,核心仅依赖两个对象:Phaser.Game 与 Phaser.Scene。
Game实例画板:负责初始化 WebGL/Canvas 渲染器,处理全屏适配与分辨率缩放。Scene场景容器:一个游戏通常拆分为开始界面、核心玩法、结算面板。每个 Scene 遵循严格的生命周期:preload():异步资源加载。此时画面尚未渲染,适合预加载图片、音频、JSON 配置,并展示 Loading 进度条。create():实例初始化。资源全部就绪后,在此生成精灵、配置物理世界、绑定输入事件。属于游戏逻辑的“布景”阶段。update(time, delta):逐帧刷新。delta代表上一帧到当前帧的间隔(毫秒)。利用它计算位移,能保证游戏在不同刷新率的设备上保持速度一致。
编写空壳验证环境是否连通:
javascript
import Phaser from 'phaser';
class MainScene extends Phaser.Scene {
constructor() { super('MainScene'); }
preload() { console.log('资源加载中...'); }
create() {
this.add.text(400, 300, 'Phaser 环境已就绪', { fontSize: '28px' })
.setOrigin(0.5).setColor('#00ffcc');
}
update() {}
}
const config = {
type: Phaser.AUTO,
width: 800, height: 600,
backgroundColor: '#1a1a2e',
scene: MainScene,
physics: { default: 'arcade', arcade: { gravity: { y: 300 } } }
};
new Phaser.Game(config);
运行 npm run dev,浏览器窗口出现居中文字,说明游戏主循环已正常启动。
🎮 实战:构建“接金币”核心玩法
纯文本展示缺乏趣味,接下来接入实体逻辑。玩法设计非常直观:底部篮子左右移动,顶部金币随机掉落。接住加分,漏接重置位置。
在 public/assets/ 目录下准备 basket.png 与 coin.png 两张素材(测试阶段用纯色 SVG 或占位图同样有效)。替换 main.js 中的 MainScene 类,完整代码如下:
javascript
class MainScene extends Phaser.Scene {
constructor() {
super('MainScene');
this.score = 0;
this.basket = null;
this.coins = null;
}
preload() {
this.load.image('basket', '/assets/basket.png');
this.load.image('coin', '/assets/coin.png');
}
create() {
// 1. 创建篮子并启用静态物理体(不受重力影响)
this.basket = this.physics.add.staticSprite(400, 560, 'basket').setScale(0.5);
// 2. 创建金币组(内置对象池机制)
this.coins = this.physics.add.group({
key: 'coin',
repeat: 8, // 预生成9个网格对象
setXY: { x: 50, y: 0, stepX: 90 }
});
// 初始化金币状态:调整尺寸、赋予随机下落速度、添加轻微旋转
this.coins.children.iterate(child => {
child.setScale(0.4);
child.setVelocityY(Phaser.Math.Between(80, 150));
child.setBounce(1, Phaser.Math.Float());
});
// 3. 注册重叠检测回调(篮子与金币组碰撞触发)
this.physics.add.overlap(this.basket, this.coins, this.catchCoin, null, this);
// 4. 绑定键盘输入
this.cursors = this.input.keyboard.createCursorKeys();
// 5. 渲染计分 UI
this.scoreText = this.add.text(16, 16, '分数: 0', { fontSize: '24px', fill: '#fff' });
}
update() {
// 帧率无关的水平位移控制
if (this.cursors.left.isDown) {
this.basket.body.setVelocityX(-300);
} else if (this.cursors.right.isDown) {
this.basket.body.setVelocityX(300);
} else {
this.basket.body.setVelocityX(0);
}
// 检测出界金币并循环利用
this.coins.children.iterate(child => {
if (child.active && child.y > 620) {
this.resetCoin(child);
}
});
}
catchCoin(basket, coin) {
this.score += 10;
this.scoreText.setText(`分数: ${this.score}`);
this.resetCoin(coin);
}
resetCoin(coin) {
coin.y = Phaser.Math.Between(0, -200);
coin.x = Phaser.Math.Between(20, 780);
coin.setVelocityY(Phaser.Math.Between(80, 150));
}
}
代码运行后,按方向键控制篮子,金币会自动下落并触发碰撞判定。UI 实时更新分数,未接住的金币会循环回到顶部重新掉落。
💣 高频踩坑与避坑指南
实际带项目时,以下问题出现频率最高,提前掌握能省下大量调试时间:
-
精灵锚点(Origin)引发碰撞偏移
Phaser 默认setOrigin(0.5),即碰撞框中心与图片中心重合。手动修改 Origin 会导致物理调试框错位,尤其在旋转或缩放时尤为明显。建议强碰撞类游戏保持默认中心点,通过调整setPosition或父容器布局来对齐画面。 -
update循环中频繁创建对象
JavaScript 垃圾回收机制并非实时触发。若在每帧调用this.add.image()生成新精灵,内存会持续上涨,最终导致掉帧卡顿。正确解法是对象池(Object Pool):在create阶段预创建一批精灵并设为不可见,运行时激活,移出屏幕后回收禁用。Phaser 的Group自带池化能力,直接调用即可。 -
物理引擎选型误区
内置引擎包含 Arcade(轻量矩形碰撞)、Matter.js(多边形刚体/斜面)、Impact。新手开发 2D 休闲游戏,统一使用 Arcade。它的性能开销极低,API 简洁。除非涉及复杂地形摩擦或布娃娃系统,否则不要引入 Matter.js 增加调试成本。 -
多端屏幕适配
800x600固定分辨率在手机端会出现严重溢出。在 Game Config 中追加缩放策略:javascriptscale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH }Canvas 会自动计算设备可用区域,保持比例拉伸并居中,彻底解决移动端显示截断问题。
📝 总结与下一步
游戏开发并不神秘。作为业务开发者,掌握场景生命周期管理 + 轻量物理碰撞 + 事件输入处理,足以覆盖市面上 90% 的 2D 小游戏需求。渲染管线与底层优化属于引擎作者的关注范畴,初期无需过度深入。
当前项目已经跑通核心闭环,后续可尝试以下扩展方向:
- 接入
this.input.keyboard.on('keydown-SPACE', ...)实现游戏暂停与二倍速机制 - 使用
this.tweens.add()为金币弹跳或接住瞬间添加缓动动画 - 查阅 Phaser Examples 官方库,将
Tilemap地图加载模块接入现有 Scene
直接上手运行代码,修改参数观察画面反馈,是学习游戏开发最高效的路径。遇到具体 API 调用问题,可随时查阅官方文档或在技术社区交流。