项目代码

项目代码

体验一下

空格开始,左右箭头控制移动

体验一下

Phaser简介

Phaser是一个HTML5游戏框架。它使用了许多HTML5 API,例如Canvas,WebGL,Audio,Gamepad等,并添加了一些有用的逻辑,例如管理游戏循环并为我们提供了物理引擎。

使用Phaser,我们可以只用HTML,CSS和JavaScript来构建2D游戏。

项目需求

在使用Phaser构建Breakout克隆之前,让我们首先定义游戏的范围:

这款单人游戏具有30个积木,一个球拍和一个球的一个关卡

目标是让球摧毁所有积木,同时确保其不离开游戏画面的底部。

玩家将控制一个可左右移动的桨

该游戏是为桌面版网络用户打造的,因此将使用键盘进行输入

设置Phaser

Phaser是一个JavaScript库,要开发和玩我们的游戏,我们需要一些基本的HTML来加载JS。在一个工作区中创建一个名为breakout的目录。

在目录中创建以下文件和文件夹:

一个index.html文件

一个breakout.js文件

名为的文件夹 assets

在您的assets文件夹中,创建一个images文件夹

游戏资产是游戏使用的艺术品,声音,视频和其他数据。对于这个简单的Breakout克隆,没有多少资产需要使用文件夹进行组织。但是,优良作法是将资产与代码分开,并按类型将资产分开。

将以下代码添加到您的index.html文件中:

<!doctype html>
<html> <head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>Breakout</title>
<style>
html,
body {
margin: 0 auto;
padding: 0;
width: 100%;
height: 100%;
} #game {
margin: 10px auto;
padding: 0;
width: 800px;
height: 640px;
}
</style>
</head> <body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="game"></div>
<script src="//cdn.jsdelivr.net/npm/phaser@3.17.0/dist/phaser.min.js"></script>
<script src="breakout.js"></script>
</body> </html>

此基本HTML代码执行以下操作:

  • 删除HTML和body标签中的浏览器边距和填充
  • 添加一个gamediv元素,其中将包含我们的Breakout克隆
  • 通过其CDN加载Phaser v3.17
  • 加载breakout.js当前不执行任何操作但包含游戏逻辑的文件

为了使用Phaser有效开发游戏,我们需要将这些文件放到web服务器中运行。如果web服务器,出于安全原因,我们的浏览器将不允许我们的游戏脚本加载资产。

幸运的是,无需设置ApacheNginx即可获得运行中的Web服务器。如果使用VisualStudio Code,则可以安装Live Server扩展。大多数IDE和文本编辑器都具有功能类似的插件。

如果您安装了Python版本3,则可以通过终端进入工作区并输入python3 -m http.server。还有其他CLI工具可提供简单的Web服务器,请选择一种可以为您提供最快时间开发游戏的工具。

最后,下载我们为此游戏创建的图像资产。将PNG文件复制并粘贴到images文件夹中。

创造我们的游戏世界

通过设置HTML和CSS,让我们编辑breakout.js文件以设置游戏世界。

开始Phaser

首先,我们需要配置Phaser并创建我们的Game实例。该游戏的实例是Phaser游戏的中央控制器,它进行所有的设置和开始游戏循环。

让我们配置和创建Game实例:

// This object contains all the Phaser configurations to load our game
const config = {
type: Phaser.AUTO,
parent: 'game',
width: 800,
heigth: 640,
scale: {
mode: Phaser.Scale.RESIZE,
autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: {
preload,
create,
update,
},
physics: {
default: 'arcade',
arcade: {
gravity: false
},
}
}; // Create the game instance
const game = new Phaser.Game(config);

type属性告诉Phaser使用什么渲染器。Phaser可以使用HTML5的WebGLCanvas元素来渲染我们的游戏。通过将类型设置为Phaser.AUTO,我们告诉Phaser首先尝试使用WebGL进行渲染,如果失败,则使用Canvas进行渲染。

parent属性表示将要玩我们的游戏的HTML元素的ID。我们使用width和定义游戏尺寸(以像素为单位)height。该scale对象为我们做两件事:

  • mode 告诉Phaser如何使用父元素的空间,在这种情况下,我们确保游戏适合父元素的大小 div
  • autoCenter告诉Phaserdiv如果需要的话,如何在我们的父级中居中游戏。在这种情况下,我们将游戏在父div内垂直和水平居中。当游戏不占据父对象的整个空间时,此属性会更有用。

在Phaser中,我们的游戏逻辑在中定义Scenes。将场景视为游戏中的各种状态:标题屏幕是一个场景,游戏的每个级别将是它们自己的场景,剪切场景将是它自己的场景,等等。Phaser提供了Scene对象,但它也可以与含有常规的JavaScript对象preload()create()update()定义的函数。

最后一个配置告诉Phaser要使用哪个物理引擎。Phaser可以使用3种不同的物理引擎:ArcadeImpactMatter。Arcade是最简单的入门游戏,足以满足我们的游戏需求。

Breakout 不需要重力即可工作,因此我们在物理引擎中禁用了该属性。如果我们要构建Platform游戏,则可能会启用重力,这样当我们的玩家跳跃时,他们会自然地掉回地面。

为了确保我们的游戏设置工作,我们需要添加preload()create()update()功能。创建游戏实例后,向其中添加以下空白函数:

function preload() { }

function create() { }

function update() { }

在Web服务器运行的情况下,导航到运行游戏的页面。您应该看到一个空白屏幕,如下所示:

加载资产

该游戏中的资产包括5张图片。在您可能创建的其他游戏中,您的资产可能非常庞大。高清晰图像,音频和视频文件可能会占用兆字节的空间。资产越大,负担越长。因此,Phaser具有一项preload()功能,我们可以在开始运行游戏之前加载所有资产。

preload()函数更改为以下内容,以便我们可以在游戏循环开始之前加载图像:

function preload() {
this.load.image('ball', 'assets/images/ball_32_32.png');
this.load.image('paddle', 'assets/images/paddle_128_32.png');
this.load.image('brick1', 'assets/images/brick1_64_32.png');
this.load.image('brick2', 'assets/images/brick2_64_32.png');
this.load.image('brick3', 'assets/images/brick3_64_32.png');
}

第一个参数是稍后将用来引用图像的键,第二个参数是图像的位置。

注: -当我们用this我们的preload()create()update()功能,我们指的是由之前创建的游戏实例game。

加载图像后,我们想在屏幕上放置精灵。在的顶部breakout.js,添加以下将包含我们的精灵数据的变量:

let player, ball, violetBricks, yellowBricks, redBricks, cursors;

一旦全局定义它们,我们所有的函数都可以使用它们。

添加精灵

sprite 是游戏场景中任何2D图像。在Phaser中,sprite 会封装图像以及其位置,速度,物理属性和其他属性。首先,通过create()函数创建player精灵:

player = this.physics.add.sprite(
400, // x position
600, // y position
'paddle', // key of image for the sprite
);

您现在应该可以在屏幕上看到一个桨:

sprite()方法的第一个参数是X放置精灵的坐标。第二个参数是Y坐标,最后一个参数是preload()函数中添加的图像资产的键。

了解phaser和大多数2D游戏框架如何使用坐标很重要。我们在学校学到的图形通常将原点即点(0,0)置于中心。在Phaser中,原点位于屏幕的左上方。随着x增长,我们实际上正在向右移动。随着y增加,我们正在向下移动。

我们的游戏的宽度为800像素,高度为640像素,因此我们的游戏坐标如下所示:

让我们将球添加到Player上方。将以下代码添加到该create()函数:

ball = this.physics.add.sprite(
400, // x position
565, // y position
'ball' // key of image for the sprite
);

由于球上面我们的Player,在坐标y的值是比玩家的Y坐标。

添加精灵组

虽然Phaser可以轻松添加sprite,但如果必须分别定义每个sprite,将很快变得乏味。Breakout中的砖块几乎相同。位置不同,但是它们的属性(例如颜色以及它们与球的交互方式)是相同的。我们可以创建精灵组来更好地管理它们,而不是创建30个砖精灵对象。

让我们通过create()函数添加第一排紫色砖:

// Add violet bricks
violetBricks = this.physics.add.group({
key: 'brick1',
repeat: 9,
setXY: {
x: 80,
y: 140,
stepX: 70
}
});

代替this.physics.add.sprite()我们使用this.physics.add.group()并传递一个JavaScript对象。key属性引用sprite组中所有sprite将使用的图像键。该repeat属性告诉Phaser再创建多少个精灵。每个精灵组都创建一个精灵。随着repeat设置为9,Phaser将创建一个精灵组10个精灵。该setXY对象具有三个有趣的属性:

  • x 是第一个精灵的X坐标
  • y 是第二个精灵的Y坐标
  • stepX 是x轴上重复的精灵之间的像素长度。

也有一个stepY属性,但是我们不需要在游戏中使用它。让我们为砖添加另外两个剩余的精灵组:

// Add yellow bricks
yellowBricks = this.physics.add.group({
key: 'brick2',
repeat: 9,
setXY: {
x: 80,
y: 90,
stepX: 70
}
}); // Add red bricks
redBricks = this.physics.add.group({
key: 'brick3',
repeat: 9,
setXY: {
x: 80,
y: 40,
stepX: 70
}
});

我们的游戏已经整合在一起,您的屏幕应如下所示:

游戏胜利与结束

如果我们的球落到屏幕底部,我们可能会输掉一场比赛。在“phaser”中,要使球位于屏幕下方,则球的Y坐标大于游戏世界的高度。让我们创建一个检查此功能的函数,在底部breakout.js添加以下内容:

function isGameOver(world) {
return ball.body.y > world.bounds.height;
}

我们的功能从场景的物理属性中获取世界对象,该对象将在update()功能中可用。它检查球精灵的Y坐标是否大于游戏世界边界的高度。

为了赢得比赛,我们需要打掉所有砖块。Phaser中的精灵都具有活动属性。我们可以使用该属性来确定我们是否获胜。精灵组可以计算其中包含的活动精灵的数量。如果每个积木精灵组中都没有活动的积木,即活动积木有0个,则玩家赢得了游戏。

让我们breakout.js通过在底部添加一个检查来更新文件:

function isWon() {
return violetBricks.countActive() + yellowBricks.countActive() + redBricks.countActive() === 0;
}

我们接受每个精灵组作为参数,在其中添加活动精灵的数量,并检查其是否等于0。

既然我们已经定义了输赢条件,我们希望Phaser在游戏循环开始时检查它们。一旦玩家获胜或失败,游戏便应停止。

让我们更新update()函数:

function update() {
// Check if the ball left the scene i.e. game over
if (isGameOver(this.physics.world)) {
// TODO: Show "Game over" message to the player
} else if (isWon()) {
// TODO: Show "You won!" message to the player
} else {
// TODO: Logic for regular game time
}
}

使用键盘输入移动播放器

演奏者的动作取决于键盘输入。为了能够跟踪键盘输入。现在该使用cursors变量了。

并且在我们create()功能的底部:

cursors = this.input.keyboard.createCursorKeys();

Phaser中的光标键可跟踪6个键盘键的用法:上,右,下,左,Shift和空格键。

现在我们需要对cursors对象的状态做出反应以更新播放器的位置。在函数的else子句中update()添加以下内容:

// Put this in so that the player stays still if no key is being pressed
player.body.setVelocityX(0); if (cursors.left.isDown) {
player.body.setVelocityX(-350);
} else if (cursors.right.isDown) {
player.body.setVelocityX(350);
}

现在我们可以将播放器从左向右移动!

您会注意到,玩家精灵可以离开游戏屏幕,理想情况下,它不能离开游戏屏幕。我们稍后将在处理冲突时解决该问题。

等待开始

在我们添加逻辑来移动球之前,如果游戏在移动之前等待用户输入,这将有所帮助。加载游戏并立即被强制启动并不是一种好的体验。玩家没有时间做出反应!

玩家按下空格键后,让我们向上移动球。如果用户向左或向右移动球拍,则球也将被移动,因此它始终位于球拍的中心。

首先,我们需要自己的变量来跟踪游戏是否启动。在breakout.js声明游戏变量之后,在的顶部,添加:

let gameStarted = false;

现在,在else我们的更新函数的子句中:

if (!gameStarted) {
ball.setX(player.x); if (cursors.space.isDown) {
gameStarted = true;
ball.setVelocityY(-200);
}
}

如果游戏尚未开始,请将我们球的X坐标设置为玩家的中心。游戏对象的坐标基于其中心,因此sprite的x和y属性将点指向我们sprite的中心。

请注意,虽然可以x通过在设置属性时直接引用属性值来获取属性值,但我们总是尝试使用适当的setter函数。设置器功能可以包括验证我们的输入,更新另一个属性或触发事件的逻辑。它使我们的代码更具可预测性。

就像之前移动player一样,我们检查是否按下了空格键。如果按下该按钮,我们会将gameStarted标志切换到,true以便球不再跟随玩家的水平位置,并将球的Y速度设置为-200。负y速度将物体向上发送。对于正速度,较大的值可以更快地向下移动对象。对于负速度,较小的值可以更快地向上移动对象。

现在,当我们移动玩家时,球跟随着,如果我们按下空格键,球就会向上射击:

到目前为止,您将观察到我们游戏的一些行为:

  • 球在砖块后面渲染
  • 玩家可以离开屏幕的边界
  • 球可以离开屏幕的边界

球是在积木后面渲染的,因为它是在积木精灵组之前的创建函数中添加到游戏中的。在Phaser中,通常使用HTML5 canvas元素,最近添加的图像绘制在先前图像的顶部。

通过添加一些世界碰撞可以解决最后两个问题。

处理碰撞

世界碰撞

我们所有的精灵互动都在create函数中定义。使用Phaser轻松与世界场景碰撞,在create函数末尾添加以下内容:

player.setCollideWorldBounds(true);
ball.setCollideWorldBounds(true);

它应该给我们这样的输出:

当球员运动正常时,球似乎卡在顶部。为了解决这个问题,我们需要设置bounce球形精灵的属性。该bounce属性将告诉Phaser与对象碰撞后要维持多少速度。

将此添加到create()函数的末尾:

ball.setBounce(1, 1);

这告诉Phaser,球应保持其所有X和Y速度。如果我们用空格键释放球,则球应该在游戏世界中上下弹跳。我们需要从游戏世界的底部禁用碰撞检测。

如果我们不这样做,我们将永远不知道比赛何时结束。通过在create函数末尾添加以下行来禁用与游戏世界底部的碰撞:

this.physics.world.checkCollision.down = false;

我们现在应该有一个像这样的游戏:

撞砖

现在我们的运动精灵已正确地与我们的游戏世界碰撞,让我们开始研究球与砖块之间以及球与球员之间的碰撞。

在我们的create()函数中,将以下代码行添加到末尾:

this.physics.add.collider(ball, violetBricks, hitBrick, null, this);
this.physics.add.collider(ball, yellowBricks, hitBrick, null, this);
this.physics.add.collider(ball, redBricks, hitBrick, null, this);

hitBrick()当ball与各种砖精灵组发生碰撞时,碰撞器方法会告诉Phaser的物理系统执行该功能。

每次按下空格键,球就会向上射击。没有X速度,所以球会直接回到桨上。那将是一个无聊的游戏。因此,当我们第一次碰到一块砖时,我们将设置一个随机的X速度。

在以下breakout.js定义的底部hitBrick:

function hitBrick(ball, brick) {
brick.disableBody(true, true); if (ball.body.velocity.x === 0) {
randNum = Math.random();
if (randNum >= 0.5) {
ball.body.setVelocityX(150);
} else {
ball.body.setVelocityX(-150);
}
}
}

该hitBrick()函数接受collider()方法中使用的前两个参数,例如ball和violetBricks。该disableBody(true, true)砖上调用告诉Phaser,以使之失去活性,并从屏幕隐藏它。如果球的X速度为0,则根据随机数的值为球赋予速度。

如果一个小球以缓慢的速度向您的脚滚动,则在碰撞时它将停止。默认情况下,Arcade Physics引擎会模拟碰撞对速度的影响。对于我们的游戏,我们不希望球撞到砖头时失去速度。我们需要将immovable属性设置为sprite组true。

更新的定义violetBricks,yellowBricks并redBricks于以下内容:

// Add violet bricks
violetBricks = this.physics.add.group({
key: 'brick1',
repeat: 9,
immovable: true,
setXY: {
x: 80,
y: 140,
stepX: 70
}
}); // Add yellow bricks
yellowBricks = this.physics.add.group({
key: 'brick2',
repeat: 9,
immovable: true,
setXY: {
x: 80,
y: 90,
stepX: 70
}
}); // Add red bricks
redBricks = this.physics.add.group({
key: 'brick3',
repeat: 9,
immovable: true,
setXY: {
x: 80,
y: 40,
stepX: 70
}
});

我们的砖块碰撞现在已经完成,我们的游戏应该像这样工作:

开发技巧-开发游戏的物理原理时,您可能需要启用调试模式,以查看精灵的边界框以及它们如何相互碰撞。在您的游戏config对象中,在arcade我们定义的属性中gravity,您可以通过将其添加到对象中来启用调试功能:

debug: true

玩家冲突

处理球与player之间的碰撞是类似的。首先,确保播放器为immovable。在create()函数的末尾添加以下内容:

player.setImmovable(true);

然后我们在球和player之间添加一个对撞机:

this.physics.add.collider(ball, player, hitPlayer, null, this);

当球击中球员时,我们希望发生两件事:

  • 球应该移动得更快一些,以逐渐增加比赛难度
  • 球的水平方向取决于击中球员的哪一侧-如果球击中球员的左侧,那么它应该向左走,如果球击中球员的右侧,那么它应该向右走。

    为了适应这些情况,让我们更新breakout.js以下hitPlayer()功能:
function hitPlayer(ball, player) {
// Increase the velocity of the ball after it bounces
ball.setVelocityY(ball.body.velocity.y - 5); let newXVelocity = Math.abs(ball.body.velocity.x) + 5;
// If the ball is to the left of the player, ensure the X-velocity is negative
if (ball.x < player.x) {
ball.setVelocityX(-newXVelocity);
} else {
ball.setVelocityX(newXVelocity);
}
}

注意:一个精灵可以与另一个精灵碰撞,一个精灵可以与精灵组碰撞,并且精灵组可以相互碰撞。phaser足够聪明,可以使用我们在上下文中定义的碰撞函数。

现在我们的游戏既有玩家冲突又有砖块冲突:

添加文字

尽管我们的游​​戏可以完全正常运行,但是玩此游戏的人却不知道如何开始或不知道游戏何时结束。

让我们在gameStarted声明的顶部添加三个新的全局变量,这些变量将存储我们的文本数据breakout.js:

let openingText, gameOverText, playerWonText;

开始界面

让我们在加载游戏时添加一些文本,告诉玩家按下空格。在create()函数中添加以下代码:

openingText = this.add.text(
this.physics.world.bounds.width / 2,
this.physics.world.bounds.height / 2,
'Press SPACE to Start',
{
fontFamily: 'Monaco, Courier, monospace',
fontSize: '50px',
fill: '#fff'
}
); openingText.setOrigin(0.5);

该text方法的前两个参数是文本框的X和Y坐标。我们使用游戏场景的宽度和高度来确定其放置位置-居中。第三个参数是要显示的文本。第四个参数是包含字体相关数据的JS对象。

与精灵不同,文本对象的X和Y坐标是指对象最左上角的点,而不是其中心。这就是为什么我们使用该setOrigin()方法使坐标系像sprites一样工作,在这种情况下,它使定位在中心更加容易。

在玩游戏时,我们不再希望看到开头文字。在update()函数中,将if检查是否按下空格键的语句更改为以下内容:

if (cursors.space.isDown) {
gameStarted = true;
ball.setVelocityY(-200);
openingText.setVisible(false);
}

文本对象不是精灵,我们不能禁用它们的主体。当我们不需要看到它们时,可以使它们不可见。我们的游戏现在开始如下:

游戏结束和赢得比赛

像之前一样,我们需要在create()函数中添加文本对象,并使它们不可见,以便在游戏开始时不会看到它们:

// Create game over text
gameOverText = this.add.text(
this.physics.world.bounds.width / 2,
this.physics.world.bounds.height / 2,
'Game Over',
{
fontFamily: 'Monaco, Courier, monospace',
fontSize: '50px',
fill: '#fff'
}
); gameOverText.setOrigin(0.5); // Make it invisible until the player loses
gameOverText.setVisible(false); // Create the game won text
playerWonText = this.add.text(
this.physics.world.bounds.width / 2,
this.physics.world.bounds.height / 2,
'You won!',
{
fontFamily: 'Monaco, Courier, monospace',
fontSize: '50px',
fill: '#fff'
}
); playerWonText.setOrigin(0.5); // Make it invisible until the player wins
playerWonText.setVisible(false);

现在已定义它们,我们必须在update()函数中更改其可见性:

// Check if the ball left the scene i.e. game over
if (isGameOver(this.physics.world)) {
gameOverText.setVisible(true);
ball.disableBody(true, true);
} else if (isWon()) {
playerWonText.setVisible(true);
ball.disableBody(true, true);
} else {
...
}

我们禁用了球体,因此不再需要更新并显示该球。

如果我们输了比赛,我们将看到:

如果我们赢了比赛,我们将看到以下内容:

我们的打砖块游戏已完成!

结论

Phaser是一个HTML5游戏开发框架,可让我们在网络上快速构建视频游戏。除了通过HTML5 API进行抽象之外,它还为我们提供了有用的实用程序,例如物理引擎,并管理了游戏循环-所有游戏的执行生命周期。

完整代码

// Game objects are global variables so that many functions can access them
let player, ball, violetBricks, yellowBricks, redBricks, cursors;
// Variable to determine if we started playing
let gameStarted = false;
// Add global text objects
let openingText, gameOverText, playerWonText; // This object contains all the Phaser configurations to load our game
const config = {
/**
* The type can be Phaser.CANVAS, Phaser.WEBGL or Phaser.AUTO. AUTO means that
* Phaser will try to render with WebGL, and fall back to Canvas if it fails
*/
type: Phaser.AUTO,
// Parent element to inject the Canvas/WebGL element with the game
parent: 'game',
width: 800,
heigth: 640,
scale: {
// Ensure the canvas is resized to fit the parent div's dimensions
mode: Phaser.Scale.RESIZE,
// Center the game canvas both horizontally and vertically within the parent
autoCenter: Phaser.Scale.CENTER_BOTH
},
/**
* A scene is "self-contained" game world - all the logic and state of a game
* component. For e.g. it's common to a game menu to be one scene, whereas the
* first level is another scene. Phaser has a Scene object, but we can provide
* a regular JS object with these function names:
*/
scene: {
preload,
create,
update,
},
/**
* The physics engine determines how objects interact with the world. Phaser
* supports three physics engines out of the box: arcade, impact and matter.
* Arcade is understood to be the simplest one to implement
*/
physics: {
default: 'arcade',
arcade: {
gravity: false
},
}
}; // Create the game instance
const game = new Phaser.Game(config); /**
* The function loads assets as Phaser begins to run the scene. The images are
* loaded as key value pairs, we reference the assets by their keys of course
*/
function preload() {
this.load.image('ball', 'assets/images/ball_32_32.png');
this.load.image('paddle', 'assets/images/paddle_128_32.png');
this.load.image('brick1', 'assets/images/brick1_64_32.png');
this.load.image('brick2', 'assets/images/brick2_64_32.png');
this.load.image('brick3', 'assets/images/brick3_64_32.png');
} /**
* We create our game world in this function. The initial state of our game is
* defined here. We also set up our physics rules here
*/
function create() {
/**
* Coordinates start at 0,0 from the top left
* As we move rightward, the x value increases
* As we move downward, the y value increases.
*/
player = this.physics.add.sprite(
400, // x position
600, // y position
'paddle', // key of image for the sprite
); // Let's add the ball
ball = this.physics.add.sprite(
400, // x position
565, // y position
'ball' // key of image for the sprite
); // Add violet bricks
violetBricks = this.physics.add.group({
key: 'brick1',
repeat: 9,
immovable: true,
setXY: {
x: 80,
y: 140,
stepX: 70
}
}); // Add yellow bricks
yellowBricks = this.physics.add.group({
key: 'brick2',
repeat: 9,
immovable: true,
setXY: {
x: 80,
y: 90,
stepX: 70
}
}); // Add red bricks
redBricks = this.physics.add.group({
key: 'brick3',
repeat: 9,
immovable: true,
setXY: {
x: 80,
y: 40,
stepX: 70
}
}); // Manage key presses
cursors = this.input.keyboard.createCursorKeys(); // Ensure that the player and ball can't leave the screen
player.setCollideWorldBounds(true);
ball.setCollideWorldBounds(true);
/**
* The bounce ensures that the ball retains its velocity after colliding with
* an object.
*/
ball.setBounce(1, 1); /**
* Disable collision with the bottom of the game world. This needs to be added
* so the ball falls to the bottom, which means that the game is over
*/
this.physics.world.checkCollision.down = false; // Add collision for the bricks
this.physics.add.collider(ball, violetBricks, hitBrick, null, this);
this.physics.add.collider(ball, yellowBricks, hitBrick, null, this);
this.physics.add.collider(ball, redBricks, hitBrick, null, this); // Make the player immovable
player.setImmovable(true);
// Add collision for the player
this.physics.add.collider(ball, player, hitPlayer, null, this); // Create opening text
openingText = this.add.text(
this.physics.world.bounds.width / 2,
this.physics.world.bounds.height / 2,
'Press SPACE to Start',
{
fontFamily: 'Monaco, Courier, monospace',
fontSize: '50px',
fill: '#fff'
},
); /**
* The origin of the text object is at the top left, change the origin to the
* center so it can be properly aligned
*/
openingText.setOrigin(0.5); // Create game over text
gameOverText = this.add.text(
this.physics.world.bounds.width / 2,
this.physics.world.bounds.height / 2,
'Game Over',
{
fontFamily: 'Monaco, Courier, monospace',
fontSize: '50px',
fill: '#fff'
},
); gameOverText.setOrigin(0.5); // Make it invisible until the player loses
gameOverText.setVisible(false); // Create the game won text
playerWonText = this.add.text(
this.physics.world.bounds.width / 2,
this.physics.world.bounds.height / 2,
'You won!',
{
fontFamily: 'Monaco, Courier, monospace',
fontSize: '50px',
fill: '#fff'
},
); playerWonText.setOrigin(0.5); // Make it invisible until the player wins
playerWonText.setVisible(false);
} /**
* Our game state is updated in this function. This corresponds exactly to the
* update process of the game loop
*/
function update() {
// Check if the ball left the scene i.e. game over
if (isGameOver(this.physics.world)) {
gameOverText.setVisible(true);
ball.disableBody(true, true);
} else if (isWon()) {
playerWonText.setVisible(true);
ball.disableBody(true, true);
} else {
// Put this in so that the player doesn't move if no key is being pressed
player.body.setVelocityX(0); /**
* Check the cursor and move the velocity accordingly. With Arcade Physics we
* adjust velocity for movement as opposed to manipulating xy values directly
*/
if (cursors.left.isDown) {
player.body.setVelocityX(-350);
} else if (cursors.right.isDown) {
player.body.setVelocityX(350);
} // The game only begins when the user presses Spacebar to release the paddle
if (!gameStarted) {
// The ball should follow the paddle while the user selects where to start
ball.setX(player.x); if (cursors.space.isDown) {
gameStarted = true;
ball.setVelocityY(-200);
openingText.setVisible(false);
}
}
}
} /**
* Checks if the user lost the game
* @param world - the physics world
* @return {boolean}
*/
function isGameOver(world) {
return ball.body.y > world.bounds.height;
} /**
* Checks if the user won the game
* @return {boolean}
*/
function isWon() {
return violetBricks.countActive() + yellowBricks.countActive() + redBricks.countActive() == 0;
} /**
* This function handles the collision between a ball and a brick sprite
* In the create function, ball is a sprite and violetBricks, yellowBricks and
* redBricks are sprite groups. Phaser is smart enough to handle the collisions
* for each individual sprite.
* @param ball - the ball sprite
* @param brick - the brick sprite
*/
function hitBrick(ball, brick) {
brick.disableBody(true, true); if (ball.body.velocity.x == 0) {
randNum = Math.random();
if (randNum >= 0.5) {
ball.body.setVelocityX(150);
} else {
ball.body.setVelocityX(-150);
}
}
} /**
* The function handles the collision between the ball and the player. We want
* to ensure that the ball's direction after bouncing off the player is based
* on which side of the player was hit. Also, to make things more difficult, we
* want to increase the ball's velocity when it's hit.
* @param ball - the ball sprite
* @param player - the player/paddle sprite
*/
function hitPlayer(ball, player) {
// Increase the velocity of the ball after it bounces
ball.setVelocityY(ball.body.velocity.y - 5); let newXVelocity = Math.abs(ball.body.velocity.x) + 5;
// If the ball is to the left of the player, ensure the x velocity is negative
if (ball.x < player.x) {
ball.setVelocityX(-newXVelocity);
} else {
ball.setVelocityX(newXVelocity);
}
}

phaser3入门教程-从零开始开发一个打砖块游戏的更多相关文章

  1. Vue.js 入门:从零开始做一个极简 To-Do 应用

    Vue.js 入门:从零开始做一个极简 To-Do 应用 写作时间:2019-12-10版本信息:Vue.js 2.6.10官网文档:https://cn.vuejs.org/ 前言  学习 Vue ...

  2. Arduino可穿戴开发入门教程Arduino开发环境介绍

    Arduino可穿戴开发入门教程Arduino开发环境介绍 Arduino开发环境介绍 Arduino不像我们使用的PC端操作系统一样,可以直接在操作系统中安装软件为操作系统编程.Arduino的软件 ...

  3. 从零开始, 开发一个 Web Office 套件 (2): 富文本编辑器

    书接前文: 从零开始, 开发一个 Web Office 套件 (1): 富文本编辑器 这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Of ...

  4. 从零开始, 开发一个 Web Office 套件 (3): 鼠标事件

    这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office 套件, 包括: 文档, 表格, 幻灯片... 等等. 对应的Github r ...

  5. 从零开始, 开发一个 Web Office 套件(4):新的问题—— z-index

    <从零开始, 开发一个 Web Office 套件>系列博客目录 这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office ...

  6. 《从零开始, 开发一个 Web Office 套件》系列博客目录

    这是一个系列博客, 最终目的是要做一个基于HTML Canvas 的, 类似于微软 Office 的 Web Office 套件, 包括: 文档, 表格, 幻灯片... 等等. 对应的Github r ...

  7. Django1.8教程——从零开始搭建一个完整django博客(一)

    第一个Django项目将是一个完整的博客网站.它和我们博客园使用的博客别无二致,一样有分类.标签.归档.查询等功能.如果你对Django感兴趣的话,这是一个绝好的机会.该教程将和你一起,从零开始,搭建 ...

  8. React Native入门教程 1 -- 开发环境搭建

    有人问我为啥很久不更新博客..我只能说在学校宿舍真的没有学习的环境..基本上在宿舍里面很颓废..不过要毕业找工作了,我要渐渐把这个心态调整过来,就从react-native第一篇博客开始.话说RN也出 ...

  9. OsharpNS轻量级.net core快速开发框架简明入门教程-从零开始启动Osharp

    OsharpNS轻量级.net core快速开发框架简明入门教程 教程目录 从零开始启动Osharp 1.1. 使用OsharpNS项目模板创建项目 1.2. 配置数据库连接串并启动项目 1.3. O ...

随机推荐

  1. 20190705_关于winform程序修改程序名后, 报未将对象引用设置到对象的实例

    winform做了一个小项目, 其中要用到数据库连接, 字符串, private string ConnStringSource = System.Configuration.Configuratio ...

  2. [翻译自官方]什么是RDB和AOF? 一文了解Redis持久化!

    ​概述 本文提供Redis持久化技术说明,  建议所有Redis用户阅读. 如果您想更深入了解Redis持久性原理机制和底层持久性保证, 请参考文章 揭秘Redis持久化: http://antire ...

  3. 刚开始学习Javascript的一些基础小知识,从入门到崩溃,希望对大家有帮助(只适合初学者)

    一.简介 1.JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型,js不能操作文件. 重要的开始啦!!!!! 引入javascript: 行间js <d ...

  4. CSS知识点记录

    1.浏览器style中显示的样式 这里面的样式,是行内样式或者是使用js添加的样式

  5. Java8的Lambda表达式,你会不?

    目录 理解Lambda 基础语法 函数式接口 常用的函数式接口 消费型接口 供给型接口 断言型接口 函数型接口 方法引用 数组引用 构造器引用 总结 参考阅读 理解Lambda Lambda表达式可以 ...

  6. MySQL MHA安装配置

    1.环境规划 192.168.12.131 node01 192.168.12.132 node02 192.168.12.133 node03 2.环境准备 一主两从GTID,略. 3.配置关键程序 ...

  7. ubuntu 18 安装xgboost GPU版本

    综合上述两个帖子: https://www.cnblogs.com/huadongw/p/6161145.html https://blog.csdn.net/u011587516/article/d ...

  8. python+selenium笔记(一):元素定位方法

    一.环境准备: 1.浏览器选择:Firefox 2.安装插件:Firebug和FirePath(设置>附加组件>搜索:输入插件名称>下载安装后重启浏览器) 3.安装完成后,页面右上角 ...

  9. Spring自带的定时任务框架Schedule的优缺点及使用

    spring自带的定时任务框架的有点:简单,拆箱即用 spring自带的定时任务框架的缺点: 不支持集群:为避免重复执行的问题 不支持生命周期统一管理:不重启服务情况下关闭,启动任务 不支持分片任务: ...

  10. 小兔子有颗玻璃心A版【转】

    作者:诸君平身链接:https://www.zhihu.com/question/49179166/answer/116926446来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...