项目代码html
空格开始,左右箭头控制移动html5
体验一下python
Phaser是一个HTML5游戏框架。它使用了许多HTML5 API,例如Canvas,WebGL,Audio,Gamepad等,并添加了一些有用的逻辑,例如管理游戏循环并为咱们提供了物理引擎。nginx
使用Phaser,咱们能够只用HTML,CSS和JavaScript来构建2D游戏。git
在使用Phaser构建Breakout克隆以前,让咱们首先定义游戏的范围:github
这款单人游戏具备30个积木,一个球拍和一个球的一个关卡
目标是让球摧毁全部积木,同时确保其不离开游戏画面的底部。
玩家将控制一个可左右移动的桨
该游戏是为桌面版网络用户打造的,所以将使用键盘进行输入web
Phaser是一个JavaScript库,要开发和玩咱们的游戏,咱们须要一些基本的HTML来加载JS。在一个工做区中建立一个名为breakout的目录。apache
在目录中建立如下文件和文件夹:npm
一个index.html文件
一个breakout.js文件
名为的文件夹 assets
在您的assets文件夹中,建立一个images文件夹
游戏资产是游戏使用的艺术品,声音,视频和其余数据。对于这个简单的Breakout克隆,没有多少资产须要使用文件夹进行组织。可是,优良做法是将资产与代码分开,并按类型将资产分开。canvas
将如下代码添加到您的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代码执行如下操做:
game
div元素,其中将包含咱们的Breakout克隆breakout.js
当前不执行任何操做但包含游戏逻辑的文件为了使用Phaser有效开发游戏,咱们须要将这些文件放到web服务器中运行。若是web服务器,出于安全缘由,咱们的浏览器将不容许咱们的游戏脚本加载资产。
幸运的是,无需设置Apache或Nginx便可得到运行中的Web服务器。若是使用VisualStudio Code,则能够安装Live Server扩展。大多数IDE和文本编辑器都具备功能相似的插件。
若是您安装了Python版本3,则能够经过终端进入工做区并输入python3 -m http.server
。还有其余CLI工具可提供简单的Web服务器,请选择一种能够为您提供最快时间开发游戏的工具。
最后,下载咱们为此游戏建立的图像资产。将PNG文件复制并粘贴到images文件夹中。
经过设置HTML和CSS,让咱们编辑breakout.js
文件以设置游戏世界。
首先,咱们须要配置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的WebGL或Canvas元素来渲染咱们的游戏。经过将类型设置为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种不一样的物理引擎:Arcade,Impact和Matter。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);
当球击中球员时,咱们但愿发生两件事:
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); } }