
让我们从一个非常基础的游戏开始:一个无限的跑酷游戏。
你应该在谷歌浏览器连接网络失败时玩过恐龙游戏。
这个案例与之相同,只是用矩形和硬币代替原本的素材。
这个初始化文件包含了Phaser游戏的基本配置:在这里我们定义了游戏中的场景(比如开场动画、游戏场景、游戏结束场景等),并配置了屏幕大小、位置、物理类型等。
import Phaser from "phaser";
import Game from "./scenes/game";
import GameOver from "./scenes/gameover";
// 这是游戏的主要配置文件
const config = {
width: 600,
height: 300,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
},
autoRound: false,
parent: "game-container",
physics: {
default: "arcade",
arcade: {
gravity: { y: 350 },
debug: true,
},
},
scene: [Game, GameOver],
};
const game = new Phaser.Game(config);在开发阶段,建议将调试模式设置为 true,这样我们就能看到带有物理属性的任何游戏对象的轮廓。
这是表示玩家的类。它扩展了一个非常基础的 Phaser 游戏对象:一个矩形。在构造函数中设置玩家,为其提供物理属性,包括身体和重力。
class Player extends Phaser.GameObjects.Rectangle {
constructor(scene, x, y, number) {
super(scene, x, y, 32, 32, 0x00ff00);
this.setOrigin(0.5);
this.scene.add.existing(this);
this.scene.physics.add.existing(this);
this.body.collideWorldBounds = true;
this.setScale(1);
this.jumping = false;
this.invincible = false;
this.health = 10;
this.body.mass = 10;
this.body.setDragY = 10;
}
}
export default Player;这个“玩家”现在还很糟糕,但不用担心,随着开发的推进,我们会看到生动有趣的角色。
这个游戏是一个简单的无限跑酷游戏,玩家(绿色矩形)需要避开障碍物并收集硬币。这些元素是随机生成的。
export default class Generator {
constructor(scene) {
this.scene = scene;
this.scene.time.delayedCall(2000, () => this.init(), null, this);
this.pinos = 0;
}
init() {
this.generateCloud();
this.generateObstacle();
this.generateCoin();
}
// 这是生成云朵的函数。它创建一个新的云朵,然后在随机的时间后再次调用自身。
// 这是通过Phaser的 time.delayedCall 函数实现的。
generateCloud() {
new Cloud(this.scene);
this.scene.time.delayedCall(
Phaser.Math.Between(2000, 3000),
() => this.generateCloud(),
null,
this
);
}
generateObstacle() {
this.scene.obstacles.add(
new Obstacle(
this.scene,
800,
this.scene.height - Phaser.Math.Between(32, 128)
)
);
this.scene.time.delayedCall(
Phaser.Math.Between(1500, 2500),
() => this.generateObstacle(),
null,
this
);
}
generateCoin() {
this.scene.coins.add(
new Coin(
this.scene,
800,
this.scene.height - Phaser.Math.Between(32, 128)
)
);
this.scene.time.delayedCall(
Phaser.Math.Between(500, 1500),
() => this.generateCoin(1),
null,
this
);
}
}
// 这是一个表示云朵的游戏对象。它是一个具有随机大小和位置的简单矩形。我们使用补间动画将其从右向左移动,当它移出屏幕时将其销毁。
class Cloud extends Phaser.GameObjects.Rectangle {
constructor(scene, x, y) {
const finalY = y || Phaser.Math.Between(0, 100);
super(scene, x, finalY, 98, 32, 0xffffff);
scene.add.existing(this);
const alpha = 1 / Phaser.Math.Between(1, 3);
this.setScale(alpha);
this.init();
}
init() {
this.scene.tweens.add({
targets: this,
x: { from: 800, to: -100 },
duration: 2000 / this.scale,
onComplete: () => {
this.destroy();
},
});
}
}
// 这是一个表示障碍物的游戏对象。它的工作方式与云朵完全相同,但它是一个红色矩形,是我们在game场景中创建的障碍物组的一部分。如果玩家碰到它,它会导致玩家死亡。
class Obstacle extends Phaser.GameObjects.Rectangle {
constructor(scene, x, y) {
super(scene, x, y, 32, 32, 0xff0000);
scene.add.existing(this);
scene.physics.add.existing(this);
this.body.setAllowGravity(false);
const alpha = 1 / Phaser.Math.Between(1, 3);
this.init();
}
init() {
this.scene.tweens.add({
targets: this,
x: { from: 820, to: -100 },
duration: 2000,
onComplete: () => {
this.destroy();
},
});
}
}
// 这是一个表示硬币的游戏对象。它是一个动画精灵,属于我们在game场景中创建的硬币组。它的移动方式与之前的云朵和障碍物对象相同。
// 如果玩家碰到它,可以增加玩家的分数。
class Coin extends Phaser.GameObjects.Sprite {
constructor(scene, x, y) {
super(scene, x, y, "coin");
scene.add.existing(this);
scene.physics.add.existing(this);
this.body.setAllowGravity(false);
const alpha = 1 / Phaser.Math.Between(1, 3);
this.init();
}
init() {
this.scene.tweens.add({
targets: this,
x: { from: 820, to: -100 },
duration: 2000,
onComplete: () => {
this.destroy();
},
});
const coinAnimation = this.scene.anims.create({
key: "coin",
frames: this.scene.anims.generateFrameNumbers("coin", {
start: 0,
end: 7,
}),
frameRate: 8,
});
this.play({ key: "coin", repeat: -1 });
}
}我们可以调整这个生成器,以便随着游戏的进展增加难度。
这是游戏场景本身!和其他Phaser场景对象一样,它使用三个主要方法:
preload:在这里加载游戏资源:图像、精灵、字体、声音、地图等。create:在这里实例化并启动游戏元素,如玩家、敌人或障碍物生成器。此外,还在这里定义障碍物、硬币和云朵的组,并且最重要的是:定义这些组在触碰到玩家时的行为。update:游戏循环。Phaser 会重复调用这个方法,我们可以在这里处理玩家输入。import Player from "../gameobjects/player";
import Generator from "../gameobjects/generator";
export default class Game extends Phaser.Scene {
constructor() {
super({ key: "game" });
this.player = null;
this.score = 0;
this.scoreText = null;
}
init(data) {
this.name = data.name;
this.number = data.number;
}
// 我们使用preload方法来加载游戏所需的所有资源。
// 同时,我们将分数设置为0并存储在注册表中,以便从其他场景中访问它。
preload() {
this.registry.set("score", "0");
this.load.audio("coin", "assets/sounds/coin.mp3");
this.load.audio("jump", "assets/sounds/jump.mp3");
this.load.audio("dead", "assets/sounds/dead.mp3");
this.load.audio("theme", "assets/sounds/theme.mp3");
this.load.spritesheet("coin", "./assets/images/coin.png", {
frameWidth: 32,
frameHeight: 32,
});
this.load.bitmapFont(
"arcade",
"assets/fonts/arcade.png",
"assets/fonts/arcade.xml"
);
this.score = 0;
}
/**
* 在这里我们做了几个事情:
* - 我们使用 create 方法来初始化游戏。
* - 设置一些变量来存储可能稍后需要的宽度和高度。
* - 设置背景颜色,并创建玩家、障碍物和硬币。
* - 创建键盘输入监听空格键。
* - 添加玩家与障碍物之间的碰撞检测,以及玩家与硬币之间的重叠检测。关键部分是设置当玩家与硬币重叠或碰到障碍物时调用的函数。
*/
create() {
this.width = this.sys.game.config.width;
this.height = this.sys.game.config.height;
this.center_width = this.width / 2;
this.center_height = this.height / 2;
this.cameras.main.setBackgroundColor(0x87ceeb);
this.obstacles = this.add.group();
this.coins = this.add.group();
this.generator = new Generator(this);
this.SPACE = this.input.keyboard.addKey(
Phaser.Input.Keyboard.KeyCodes.SPACE
);
this.player = new Player(this, this.center_width - 100, this.height - 200);
this.scoreText = this.add.bitmapText(
this.center_width,
10,
"arcade",
this.score,
20
);
this.physics.add.collider(
this.player,
this.obstacles,
this.hitObstacle,
() => {
return true;
},
this
);
this.physics.add.overlap(
this.player,
this.coins,
this.hitCoin,
() => {
return true;
},
this
);
this.loadAudios();
this.playMusic();
// 我们使用 pointerdown 事件来监听鼠标点击或触摸事件。
this.input.on("pointerdown", (pointer) => this.jump(), this);
// 我们使用 updateScoreEvent 每隔100毫秒更新一次分数,以便玩家在存活期间能够看到分数的不断增加。
this.updateScoreEvent = this.time.addEvent({
delay: 100,
callback: () => this.updateScore(),
callbackScope: this,
loop: true,
});
}
// 这个方法在玩家碰到障碍物时被调用。我们停止 updateScoreEvent,以使分数不再增加。
// 显然,我们还会结束当前场景。
hitObstacle(player, obstacle) {
this.updateScoreEvent.destroy();
this.finishScene();
}
// 这个方法在玩家碰到硬币时被调用。我们播放一个声音,更新分数,并销毁硬币。
hitCoin(player, coin) {
this.playAudio("coin");
this.updateScore(1000);
coin.destroy();
}
// 我们使用 loadAudios 方法来加载游戏所需的所有音频文件。
// 然后我们会使用 playAudio 方法来播放这些音频。
loadAudios() {
this.audios = {
jump: this.sound.add("jump"),
coin: this.sound.add("coin"),
dead: this.sound.add("dead"),
};
}
playAudio(key) {
this.audios[key].play();
}
// 这个方法专门用于音乐。我们使用它来循环播放主题音乐。
playMusic(theme = "theme") {
this.theme = this.sound.add(theme);
this.theme.stop();
this.theme.play({
mute: false,
volume: 1,
rate: 1,
detune: 0,
seek: 0,
loop: true,
delay: 0,
});
}
// 这是游戏循环。该函数在每一帧调用。
// 在这里我们可以检查是否按下了某个键或玩家的状态,并做出相应的处理。我们使用 update 方法来检查玩家是否按下了空格键。
update() {
if (Phaser.Input.Keyboard.JustDown(this.SPACE)) {
this.jump();
} else if (this.player.body.blocked.down) {
this.jumpTween?.stop();
this.player.rotation = 0;
// ground
}
}
// 这是我们用来让玩家跳跃的方法。跳跃只需在Y轴上施加一个速度,重力会完成其余的工作。
// 我们还播放了跳跃的声音,并添加了一个补间动画,使玩家在跳跃时旋转。
jump() {
if (!this.player.body.blocked.down) return;
this.player.body.setVelocityY(-300);
this.playAudio("jump");
this.jumpTween = this.tweens.add({
targets: this.player,
duration: 1000,
angle: { from: 0, to: 360 },
repeat: -1,
});
}
/**
* 游戏场景结束时时,我们应该:
* - 停止主题音乐
* - 播放死亡音效
* - 将分数设置到注册表中,以便在 gameover 场景中显示
* - 启动 gameover 场景
*/
finishScene() {
this.theme.stop();
this.playAudio("dead");
this.registry.set("score", "" + this.score);
this.scene.start("gameover");
}
// 这个方法每隔100毫秒调用一次,用于更新分数并在屏幕上显示。
updateScore(points = 1) {
this.score += points;
this.scoreText.setText(this.score);
}
}如果玩家死亡,我们将分数存储起来,并打开接下来的场景:GameOver。

当用户失败时,我们展示这个场景。它相当简单:
我们展示用户的最终得分,并设置一个输入监听器,当用户点击时,我们将他送回游戏场景。
export default class GameOver extends Phaser.Scene {
constructor() {
super({ key: "gameover" });
}
create() {
this.width = this.sys.game.config.width;
this.height = this.sys.game.config.height;
this.center_width = this.width / 2;
this.center_height = this.height / 2;
this.cameras.main.setBackgroundColor(0x87ceeb);
this.add
.bitmapText(
this.center_width,
50,
"arcade",
this.registry.get("score"),
25
)
.setOrigin(0.5);
this.add
.bitmapText(
this.center_width,
this.center_height,
"arcade",
"GAME OVER",
45
)
.setOrigin(0.5);
this.add
.bitmapText(
this.center_width,
250,
"arcade",
"Press SPACE or Click to restart!",
15
)
.setOrigin(0.5);
this.input.keyboard.on("keydown-SPACE", this.startGame, this);
this.input.on("pointerdown", (pointer) => this.startGame(), this);
}
showLine(text, y) {
let line = this.introLayer.add(
this.add
.bitmapText(this.center_width, y, "pixelFont", text, 25)
.setOrigin(0.5)
.setAlpha(0)
);
this.tweens.add({
targets: line,
duration: 2000,
alpha: 1,
});
}
startGame() {
this.scene.start("game");
}
}即使在像这样的简单游戏中,提供一定的挑战也是重要的,所以你必须在这个屏幕上显示分数来让玩家获得成就感。
源码地址看不了可以去gitee 仓库中翻翻