面向初学者的 Phaser 3 完整入门指南。详细讲解本地 HTTP 服务器环境搭建、为什么不能直接双击打开 HTML、场景核心生命周期,以及一步步编写包含物理碰撞与金币收集的完整 H5 游戏。
Phaser.js 新手入门:环境搭建与第一个 H5 游戏制作
时间:2026-11-02
浏览:294
文章目录
一、环境准备:为什么不能直接双击 HTML 文件
刚接触前端游戏开发时,许多人的第一个直觉是新建一个 index.html,编写代码后直接在文件管理器中双击打开。但在打开浏览器开发者工具(F12)后,往往会看到控制台输出红色的报错信息,游戏画面停留在纯黑或空白状态。
这一现象的根本原因在于浏览器的同源安全策略(CORS)。Phaser.js 在运行过程中,需要加载图片、音频、字体和地图配置等各种静态资产。在底层,Phaser 依赖浏览器的 fetch 或 XMLHttpRequest 接口去异步请求这些资源。
当通过双击文件直接打开页面时,浏览器使用的是 file:/// 本地协议。主流现代浏览器出于安全性考虑,严格禁止页面在 file:// 域下发起跨域请求或读取同级的本地磁盘文件。因此,必须启动一个本地 Web 服务器,通过标准的 http://localhost:端口 协议来提供服务,使资源请求处于标准的 HTTP 上下文中。
快速启动本地 HTTP 服务的具体方法
启动本地静态服务器不需要复杂的后端搭建,选择以下任意一种即可:
-
方法 A:Python 内置服务器(推荐安装过 Python 的开发者)
在终端或命令行中进入项目根目录,输入:
执行后在浏览器中打开python3 -m http.server 8080http://localhost:8080即可。 -
方法 B:Node.js 免安装命令
如果电脑上已安装 Node.js,无需全局安装任何包,直接执行:
根据终端输出的地址(通常为npx serve .http://localhost:3000)在浏览器中打开。 -
方法 C:编辑器插件(如 VS Code Live Server)
在 VS Code 插件市场搜索安装Live Server,在index.html文件右键选择“Open with Live Server”,插件会自动拉起浏览器并分配本地端口。
二、核心机制:Phaser 3 游戏架构与场景生命周期
Phaser 3 是一个以“场景(Scene)”为核心组织单位的游戏引擎。一个游戏可以拥有多个独立的场景,例如启动加载场景(BootScene)、主菜单场景(MenuScene)以及真正的关卡游戏场景(GameScene)。
在最基础的单场景项目中,一个场景通常由三个关键生命周期函数驱动:
-
preload():资源预加载
在游戏画面呈现之前调用,用于向加载器注册所有需要使用的图片、雪碧图(Spritesheet)、音频文件等。Phaser 会在此阶段展示加载进度,直到所有资源完全下载到内存后才进入下一步。 -
create():对象创建与初始化
当 preload 完成后立即执行,且只执行一次。在这里完成场景中所有常驻实体的实例化:创建背景图、初始化物理地面、生成玩家角色、绑定键盘监听事件以及绘制计分板 UI。 -
update(time, delta):游戏主循环
以显示器刷新率(通常为每秒 60 次)持续循环执行。接收两个参数:time(自游戏启动以来的毫秒数)和delta(距上一帧流逝的毫秒数)。用于检测玩家输入、改变实体速度、监测边界及执行即时逻辑。
三、动手制作:编写第一个完整的 H5 收集游戏
为了让新手在不需要额外准备素材图片的前提下立刻跑通流程,接下来的示例将采用 Phaser 3 原生提供的 Graphics 纹理生成能力动态创建角色、平台和金币图形。这样无论在任何机器上,只要复制这两份文件并启动本地服务,就能 100% 成功运行。
1. 创建页面容器:index.html
在项目根目录下新建 index.html。通过官方 CDN 引入最新稳定版 Phaser 3 核心库,并在 <body> 中预留一个用于挂载画布的容器 <div id="game-container">。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My First Phaser Game</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #2D3748;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
#game-container {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5);
border-radius: 6px;
overflow: hidden;
}
</style>
<!-- 引入 Phaser 3 官方 CDN -->
<script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
</head>
<body>
<div id="game-container"></div>
<script src="main.js"></script>
</body>
</html>
2. 编写游戏逻辑:main.js
在同一目录下新建 main.js。包含完整的参数配置、图形生成、物理系统组装、键盘输入监听及碰撞回调。
// 游戏全局配置
const config = {
type: Phaser.AUTO, // 优先使用 WebGL,不支持时自动降级为 Canvas
width: 800,
height: 600,
parent: 'game-container', // 挂载到 index.html 对应的 DOM 容器
physics: {
default: 'arcade', // 使用轻量级的 Arcade 物理引擎
arcade: {
gravity: { y: 600 }, // 设置全局重力加速度为 600 px/s²
debug: false // 设为 true 可显示刚体绿色碰撞框与速度向量
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
// 运行时变量
let player;
let platforms;
let cursors;
let coins;
let score = 0;
let scoreText;
// 启动游戏实例
const game = new Phaser.Game(config);
function preload() {
// 利用 Graphics 动态生成几何纹理,免除外部图片加载失败问题
// 1. 生成地面/平台纹理 (400x32 蓝色方块)
const gPlatform = this.make.graphics({ x: 0, y: 0, add: false });
gPlatform.fillStyle(0x6487A5, 1);
gPlatform.fillRect(0, 0, 400, 32);
gPlatform.generateTexture('platformTexture', 400, 32);
// 2. 生成玩家纹理 (32x40 橙色角色)
const gPlayer = this.make.graphics({ x: 0, y: 0, add: false });
gPlayer.fillStyle(0xC28B68, 1);
gPlayer.fillRoundedRect(0, 0, 32, 40, 6);
gPlayer.fillStyle(0xFFFFFF, 1);
gPlayer.fillCircle(10, 12, 3); // 眼睛装饰
gPlayer.fillCircle(22, 12, 3);
gPlayer.generateTexture('playerTexture', 32, 40);
// 3. 生成金币纹理 (半径 10 的金黄圆形)
const gCoin = this.make.graphics({ x: 0, y: 0, add: false });
gCoin.fillStyle(0xF6E05E, 1);
gCoin.fillCircle(10, 10, 10);
gCoin.lineStyle(2, 0xD69E2E, 1);
gCoin.strokeCircle(10, 10, 10);
gCoin.generateTexture('coinTexture', 20, 20);
}
function create() {
// 1. 创建静态物理组(StaticGroup),用于存放不受重力影响的地面与跳板
platforms = this.physics.add.staticGroup();
// 铺设底部主地面(缩放使其横向铺满 800px 宽度)
// refreshBody() 必须调用:缩放静态实体后需同步更新物理碰撞盒尺寸
platforms.create(400, 584, 'platformTexture').setScale(2, 1).refreshBody();
// 放置半空中的跳板平台
platforms.create(600, 420, 'platformTexture').setScale(0.6, 0.8).refreshBody();
platforms.create(150, 320, 'platformTexture').setScale(0.6, 0.8).refreshBody();
platforms.create(520, 220, 'platformTexture').setScale(0.5, 0.8).refreshBody();
// 2. 创建玩家动态实体(Dynamic Body)
player = this.physics.add.sprite(100, 450, 'playerTexture');
player.setBounce(0.15); // 落地微量反弹系数
player.setCollideWorldBounds(true); // 开启世界边缘碰撞,防止掉出屏幕
// 3. 注册玩家与平台的物理碰撞(阻挡)
this.physics.add.collider(player, platforms);
// 4. 创建金币动态组并分散生成
coins = this.physics.add.group({
key: 'coinTexture',
repeat: 8, // 总共生成 9 个金币 (1 + 8)
setXY: { x: 40, y: 0, stepX: 85 } // 水平间隔 85px 均匀排布
});
// 为每个金币添加轻微的随机弹跳
coins.children.iterate(function (child) {
child.setBounceY(Phaser.Math.FloatBetween(0.3, 0.5));
});
// 金币与地面也需要阻挡碰撞,避免掉出底部
this.physics.add.collider(coins, platforms);
// 5. 注册玩家与金币的重叠检测(Overlap)
this.physics.add.overlap(player, coins, collectCoin, null, this);
// 6. 得分文本 UI
scoreText = this.add.text(24, 20, '金币得分: 0', {
fontSize: '22px',
fill: '#FFFFFF',
fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif'
});
// 7. 初始化键盘方向键监听
cursors = this.input.keyboard.createCursorKeys();
}
function update() {
// 每帧重置玩家水平速度
if (cursors.left.isDown) {
player.setVelocityX(-220); // 向左移动
} else if (cursors.right.isDown) {
player.setVelocityX(220); // 向右移动
} else {
player.setVelocityX(0); // 停止水平移动
}
// 仅当玩家正踩在地面或跳板上时,才允许按下向上箭头跳跃
if (cursors.up.isDown && player.body.touching.down) {
player.setVelocityY(-480); // 向上施加瞬时起跳速度
}
}
// 金币拾取回调函数
function collectCoin(player, coin) {
// 隐藏并禁用当前被触碰的金币刚体
coin.disableBody(true, true);
// 累加积分并刷新界面文本
score += 10;
scoreText.setText('金币得分: ' + score);
// 当场上所有金币都被收集完毕后,重新刷新整批金币
if (coins.countActive(true) === 0) {
coins.children.iterate(function (child) {
child.enableBody(true, child.x, 0, true, true);
child.setVelocityY(0);
});
}
}
四、核心原理深入:Arcade 物理系统与碰撞交互机制
在上面的示例中,游戏之所以具有真实物体的阻挡、重力和收集反应,完全依赖于 Phaser 内置的 Arcade 物理引擎。理解它是编写复杂逻辑的关键。
.setScale(x, y),必须紧随其后调用 .refreshBody()。因为静态刚体的物理检测包围盒(Bounding Box)在初始化后默认不会跟随视觉缩放自动变大,不刷新就会导致“看起来有平台,角色却踩空穿透”的诡异现象。
五、新手开发常见问题与排错方案
| 故障现象 | 根本原因 | 解决步骤 |
|---|---|---|
| 页面纯白/纯黑,控制台报 CORS 错误 | 以 file:/// 协议双击打开了 HTML 文件。 |
改用 python -m http.server 或 VS Code Live Server,通过 http://localhost 访问。 |
| 角色在空中无法跳跃,或按键毫无反应 | 按键更新未放入 update(),或 touching.down 条件未达成。 |
确认按键轮询在 update() 中,且角色与地面存在有效的 collider。 |
| 角色直接穿过地面掉落至屏幕外 | 未调用 this.physics.add.collider(player, platforms)。 |
在 create() 中添加物理阻挡监听;检查地面是否为 staticGroup。 |
金币吃掉后游戏报错 disableBody of undefined |
回调参数顺序颠倒或上下文传递错误。 | 确认 overlap(player, coins, collectCoin, null, this),回调第一项为角色,第二项为金币。 |
六、进阶路径:从单脚本 Demo 到商业 H5 游戏
完成本篇教程后,你已经成功跑通了 Phaser 3 的骨架结构。但在实际项目开发中,单文件编写模式会迅速膨胀并难以维护。随着游戏逻辑的丰富,通常会按照如下工程化路径进行演进:
通过本文的操作,你已经构建起属于自己的第一个物理互动 H5 游戏。建议在此基础上尝试添加新的元素:比如增加一个带有定时销毁机制的障碍陷阱、改变金币弹跳的速度参数,或者为角色添加左右朝向的翻转动画。动手调整这些数值,是快速深入理解 Phaser 游戏引擎的最佳方式。
实用工具推荐
绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
选择画质和输出格式,预览压缩前后的大小。