Phaser.js 新手入门:环境搭建与第一个 H5 游戏制作

作者:青柠

时间:2026-11-02

浏览:294

面向初学者的 Phaser 3 完整入门指南。详细讲解本地 HTTP 服务器环境搭建、为什么不能直接双击打开 HTML、场景核心生命周期,以及一步步编写包含物理碰撞与金币收集的完整 H5 游戏。

文章目录
本文面向初次接触前端游戏开发的读者,完整介绍如何搭建 Phaser 3 的本地运行环境,解释浏览器同源限制对本地资源加载的影响,并通过编写一个具备玩家操控、动态平台、金币收集与计分机制的完整 H5 小游戏,帮助读者掌握 Phaser 游戏的基本结构与核心工作流。
Phaser.js 新手入门:环境搭建与第一个 H5 游戏制作
图 0:Phaser.js 核心能力与 H5 小游戏技术构架概览

一、环境准备:为什么不能直接双击 HTML 文件

刚接触前端游戏开发时,许多人的第一个直觉是新建一个 index.html,编写代码后直接在文件管理器中双击打开。但在打开浏览器开发者工具(F12)后,往往会看到控制台输出红色的报错信息,游戏画面停留在纯黑或空白状态。

这一现象的根本原因在于浏览器的同源安全策略(CORS)。Phaser.js 在运行过程中,需要加载图片、音频、字体和地图配置等各种静态资产。在底层,Phaser 依赖浏览器的 fetch 或 XMLHttpRequest 接口去异步请求这些资源。

当通过双击文件直接打开页面时,浏览器使用的是 file:/// 本地协议。主流现代浏览器出于安全性考虑,严格禁止页面在 file:// 域下发起跨域请求或读取同级的本地磁盘文件。因此,必须启动一个本地 Web 服务器,通过标准的 http://localhost:端口 协议来提供服务,使资源请求处于标准的 HTTP 上下文中。

Phaser.js 新手入门:环境搭建与第一个 H5 游戏制作
图 1:本地开发环境准备步骤与协议请求差异

快速启动本地 HTTP 服务的具体方法

启动本地静态服务器不需要复杂的后端搭建,选择以下任意一种即可:

  • 方法 A:Python 内置服务器(推荐安装过 Python 的开发者)
    在终端或命令行中进入项目根目录,输入:
    python3 -m http.server 8080
    执行后在浏览器中打开 http://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)。

在最基础的单场景项目中,一个场景通常由三个关键生命周期函数驱动:

  1. preload():资源预加载
    在游戏画面呈现之前调用,用于向加载器注册所有需要使用的图片、雪碧图(Spritesheet)、音频文件等。Phaser 会在此阶段展示加载进度,直到所有资源完全下载到内存后才进入下一步。
  2. create():对象创建与初始化
    当 preload 完成后立即执行,且只执行一次。在这里完成场景中所有常驻实体的实例化:创建背景图、初始化物理地面、生成玩家角色、绑定键盘监听事件以及绘制计分板 UI。
  3. update(time, delta):游戏主循环
    以显示器刷新率(通常为每秒 60 次)持续循环执行。接收两个参数:time(自游戏启动以来的毫秒数)和 delta(距上一帧流逝的毫秒数)。用于检测玩家输入、改变实体速度、监测边界及执行即时逻辑。
Phaser.js 新手入门:环境搭建与第一个 H5 游戏制作
图 2:Phaser 3 场景生命周期三部曲与核心调用关系

三、动手制作:编写第一个完整的 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 物理引擎。理解它是编写复杂逻辑的关键。

Phaser.js 新手入门:环境搭建与第一个 H5 游戏制作
图 3: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 的骨架结构。但在实际项目开发中,单文件编写模式会迅速膨胀并难以维护。随着游戏逻辑的丰富,通常会按照如下工程化路径进行演进:

Phaser.js 新手入门:环境搭建与第一个 H5 游戏制作
图 4:从单文件 Demo 到商业级 H5 游戏的完整工程化进阶路径

通过本文的操作,你已经构建起属于自己的第一个物理互动 H5 游戏。建议在此基础上尝试添加新的元素:比如增加一个带有定时销毁机制的障碍陷阱、改变金币弹跳的速度参数,或者为角色添加左右朝向的翻转动画。动手调整这些数值,是快速深入理解 Phaser 游戏引擎的最佳方式。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章