经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 游戏设计 » 查看文章
phaser3入门教程-从零开始开发一个打砖块游戏
来源:cnblogs  作者:师者乐享  时间:2021/1/4 9:03:22  对本文有异议

项目代码

项目代码

体验一下

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

体验一下

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文件中:

  1. <!doctype html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
  6. <title>Breakout</title>
  7. <style>
  8. html,
  9. body {
  10. margin: 0 auto;
  11. padding: 0;
  12. width: 100%;
  13. height: 100%;
  14. }
  15. #game {
  16. margin: 10px auto;
  17. padding: 0;
  18. width: 800px;
  19. height: 640px;
  20. }
  21. </style>
  22. </head>
  23. <body>
  24. <noscript>You need to enable JavaScript to run this app.</noscript>
  25. <div id="game"></div>
  26. <script src="//cdn.jsdelivr.net/npm/phaser@3.17.0/dist/phaser.min.js"></script>
  27. <script src="breakout.js"></script>
  28. </body>
  29. </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实例:

  1. // This object contains all the Phaser configurations to load our game
  2. const config = {
  3. type: Phaser.AUTO,
  4. parent: 'game',
  5. width: 800,
  6. heigth: 640,
  7. scale: {
  8. mode: Phaser.Scale.RESIZE,
  9. autoCenter: Phaser.Scale.CENTER_BOTH
  10. },
  11. scene: {
  12. preload,
  13. create,
  14. update,
  15. },
  16. physics: {
  17. default: 'arcade',
  18. arcade: {
  19. gravity: false
  20. },
  21. }
  22. };
  23. // Create the game instance
  24. 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()功能。创建游戏实例后,向其中添加以下空白函数:

  1. function preload() { }
  2. function create() { }
  3. function update() { }

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

游戏设定

加载资产

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

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

  1. function preload() {
  2. this.load.image('ball', 'assets/images/ball_32_32.png');
  3. this.load.image('paddle', 'assets/images/paddle_128_32.png');
  4. this.load.image('brick1', 'assets/images/brick1_64_32.png');
  5. this.load.image('brick2', 'assets/images/brick2_64_32.png');
  6. this.load.image('brick3', 'assets/images/brick3_64_32.png');
  7. }

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

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

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

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

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

添加精灵

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

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

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

屏幕播放器

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

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

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

游戏坐标

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

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

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

添加精灵组

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

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

  1. // Add violet bricks
  2. violetBricks = this.physics.add.group({
  3. key: 'brick1',
  4. repeat: 9,
  5. setXY: {
  6. x: 80,
  7. y: 140,
  8. stepX: 70
  9. }
  10. });

代替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属性,但是我们不需要在游戏中使用它。让我们为砖添加另外两个剩余的精灵组:

  1. // Add yellow bricks
  2. yellowBricks = this.physics.add.group({
  3. key: 'brick2',
  4. repeat: 9,
  5. setXY: {
  6. x: 80,
  7. y: 90,
  8. stepX: 70
  9. }
  10. });
  11. // Add red bricks
  12. redBricks = this.physics.add.group({
  13. key: 'brick3',
  14. repeat: 9,
  15. setXY: {
  16. x: 80,
  17. y: 40,
  18. stepX: 70
  19. }
  20. });

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

添加所有精灵

游戏胜利与结束

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

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

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

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

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

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

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

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

让我们更新update()函数:

  1. function update() {
  2. // Check if the ball left the scene i.e. game over
  3. if (isGameOver(this.physics.world)) {
  4. // TODO: Show "Game over" message to the player
  5. } else if (isWon()) {
  6. // TODO: Show "You won!" message to the player
  7. } else {
  8. // TODO: Logic for regular game time
  9. }
  10. }

使用键盘输入移动播放器

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

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

cursors = this.input.keyboard.createCursorKeys();
Phaser中的光标键可跟踪6个键盘键的用法:上,右,下,左,Shift和空格键。

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

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

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

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

等待开始

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

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

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

  1. let gameStarted = false;

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

  1. if (!gameStarted) {
  2. ball.setX(player.x);
  3. if (cursors.space.isDown) {
  4. gameStarted = true;
  5. ball.setVelocityY(-200);
  6. }
  7. }

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

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

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

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

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

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

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

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

处理碰撞

世界碰撞

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

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

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

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

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

  1. ball.setBounce(1, 1);

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

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

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

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

撞砖

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

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

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

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

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

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

  1. function hitBrick(ball, brick) {
  2. brick.disableBody(true, true);
  3. if (ball.body.velocity.x === 0) {
  4. randNum = Math.random();
  5. if (randNum >= 0.5) {
  6. ball.body.setVelocityX(150);
  7. } else {
  8. ball.body.setVelocityX(-150);
  9. }
  10. }
  11. }

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

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

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

  1. // Add violet bricks
  2. violetBricks = this.physics.add.group({
  3. key: 'brick1',
  4. repeat: 9,
  5. immovable: true,
  6. setXY: {
  7. x: 80,
  8. y: 140,
  9. stepX: 70
  10. }
  11. });
  12. // Add yellow bricks
  13. yellowBricks = this.physics.add.group({
  14. key: 'brick2',
  15. repeat: 9,
  16. immovable: true,
  17. setXY: {
  18. x: 80,
  19. y: 90,
  20. stepX: 70
  21. }
  22. });
  23. // Add red bricks
  24. redBricks = this.physics.add.group({
  25. key: 'brick3',
  26. repeat: 9,
  27. immovable: true,
  28. setXY: {
  29. x: 80,
  30. y: 40,
  31. stepX: 70
  32. }
  33. });

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

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

  1. debug: true

玩家冲突

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

  1. player.setImmovable(true);

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

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

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

  • 球应该移动得更快一些,以逐渐增加比赛难度
  • 球的水平方向取决于击中球员的哪一侧-如果球击中球员的左侧,那么它应该向左走,如果球击中球员的右侧,那么它应该向右走。
    为了适应这些情况,让我们更新breakout.js以下hitPlayer()功能:
  1. function hitPlayer(ball, player) {
  2. // Increase the velocity of the ball after it bounces
  3. ball.setVelocityY(ball.body.velocity.y - 5);
  4. let newXVelocity = Math.abs(ball.body.velocity.x) + 5;
  5. // If the ball is to the left of the player, ensure the X-velocity is negative
  6. if (ball.x < player.x) {
  7. ball.setVelocityX(-newXVelocity);
  8. } else {
  9. ball.setVelocityX(newXVelocity);
  10. }
  11. }

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

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

添加文字

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

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

  1. let openingText, gameOverText, playerWonText;

开始界面

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

  1. openingText = this.add.text(
  2. this.physics.world.bounds.width / 2,
  3. this.physics.world.bounds.height / 2,
  4. 'Press SPACE to Start',
  5. {
  6. fontFamily: 'Monaco, Courier, monospace',
  7. fontSize: '50px',
  8. fill: '#fff'
  9. }
  10. );
  11. openingText.setOrigin(0.5);

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

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

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

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

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

游戏结束和赢得比赛

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

  1. // Create game over text
  2. gameOverText = this.add.text(
  3. this.physics.world.bounds.width / 2,
  4. this.physics.world.bounds.height / 2,
  5. 'Game Over',
  6. {
  7. fontFamily: 'Monaco, Courier, monospace',
  8. fontSize: '50px',
  9. fill: '#fff'
  10. }
  11. );
  12. gameOverText.setOrigin(0.5);
  13. // Make it invisible until the player loses
  14. gameOverText.setVisible(false);
  15. // Create the game won text
  16. playerWonText = this.add.text(
  17. this.physics.world.bounds.width / 2,
  18. this.physics.world.bounds.height / 2,
  19. 'You won!',
  20. {
  21. fontFamily: 'Monaco, Courier, monospace',
  22. fontSize: '50px',
  23. fill: '#fff'
  24. }
  25. );
  26. playerWonText.setOrigin(0.5);
  27. // Make it invisible until the player wins
  28. playerWonText.setVisible(false);

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

  1. // Check if the ball left the scene i.e. game over
  2. if (isGameOver(this.physics.world)) {
  3. gameOverText.setVisible(true);
  4. ball.disableBody(true, true);
  5. } else if (isWon()) {
  6. playerWonText.setVisible(true);
  7. ball.disableBody(true, true);
  8. } else {
  9. ...
  10. }

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

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

游戏结束

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

游戏胜利

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

结论

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

完整代码

  1. // Game objects are global variables so that many functions can access them
  2. let player, ball, violetBricks, yellowBricks, redBricks, cursors;
  3. // Variable to determine if we started playing
  4. let gameStarted = false;
  5. // Add global text objects
  6. let openingText, gameOverText, playerWonText;
  7. // This object contains all the Phaser configurations to load our game
  8. const config = {
  9. /**
  10. * The type can be Phaser.CANVAS, Phaser.WEBGL or Phaser.AUTO. AUTO means that
  11. * Phaser will try to render with WebGL, and fall back to Canvas if it fails
  12. */
  13. type: Phaser.AUTO,
  14. // Parent element to inject the Canvas/WebGL element with the game
  15. parent: 'game',
  16. width: 800,
  17. heigth: 640,
  18. scale: {
  19. // Ensure the canvas is resized to fit the parent div's dimensions
  20. mode: Phaser.Scale.RESIZE,
  21. // Center the game canvas both horizontally and vertically within the parent
  22. autoCenter: Phaser.Scale.CENTER_BOTH
  23. },
  24. /**
  25. * A scene is "self-contained" game world - all the logic and state of a game
  26. * component. For e.g. it's common to a game menu to be one scene, whereas the
  27. * first level is another scene. Phaser has a Scene object, but we can provide
  28. * a regular JS object with these function names:
  29. */
  30. scene: {
  31. preload,
  32. create,
  33. update,
  34. },
  35. /**
  36. * The physics engine determines how objects interact with the world. Phaser
  37. * supports three physics engines out of the box: arcade, impact and matter.
  38. * Arcade is understood to be the simplest one to implement
  39. */
  40. physics: {
  41. default: 'arcade',
  42. arcade: {
  43. gravity: false
  44. },
  45. }
  46. };
  47. // Create the game instance
  48. const game = new Phaser.Game(config);
  49. /**
  50. * The function loads assets as Phaser begins to run the scene. The images are
  51. * loaded as key value pairs, we reference the assets by their keys of course
  52. */
  53. function preload() {
  54. this.load.image('ball', 'assets/images/ball_32_32.png');
  55. this.load.image('paddle', 'assets/images/paddle_128_32.png');
  56. this.load.image('brick1', 'assets/images/brick1_64_32.png');
  57. this.load.image('brick2', 'assets/images/brick2_64_32.png');
  58. this.load.image('brick3', 'assets/images/brick3_64_32.png');
  59. }
  60. /**
  61. * We create our game world in this function. The initial state of our game is
  62. * defined here. We also set up our physics rules here
  63. */
  64. function create() {
  65. /**
  66. * Coordinates start at 0,0 from the top left
  67. * As we move rightward, the x value increases
  68. * As we move downward, the y value increases.
  69. */
  70. player = this.physics.add.sprite(
  71. 400, // x position
  72. 600, // y position
  73. 'paddle', // key of image for the sprite
  74. );
  75. // Let's add the ball
  76. ball = this.physics.add.sprite(
  77. 400, // x position
  78. 565, // y position
  79. 'ball' // key of image for the sprite
  80. );
  81. // Add violet bricks
  82. violetBricks = this.physics.add.group({
  83. key: 'brick1',
  84. repeat: 9,
  85. immovable: true,
  86. setXY: {
  87. x: 80,
  88. y: 140,
  89. stepX: 70
  90. }
  91. });
  92. // Add yellow bricks
  93. yellowBricks = this.physics.add.group({
  94. key: 'brick2',
  95. repeat: 9,
  96. immovable: true,
  97. setXY: {
  98. x: 80,
  99. y: 90,
  100. stepX: 70
  101. }
  102. });
  103. // Add red bricks
  104. redBricks = this.physics.add.group({
  105. key: 'brick3',
  106. repeat: 9,
  107. immovable: true,
  108. setXY: {
  109. x: 80,
  110. y: 40,
  111. stepX: 70
  112. }
  113. });
  114. // Manage key presses
  115. cursors = this.input.keyboard.createCursorKeys();
  116. // Ensure that the player and ball can't leave the screen
  117. player.setCollideWorldBounds(true);
  118. ball.setCollideWorldBounds(true);
  119. /**
  120. * The bounce ensures that the ball retains its velocity after colliding with
  121. * an object.
  122. */
  123. ball.setBounce(1, 1);
  124. /**
  125. * Disable collision with the bottom of the game world. This needs to be added
  126. * so the ball falls to the bottom, which means that the game is over
  127. */
  128. this.physics.world.checkCollision.down = false;
  129. // Add collision for the bricks
  130. this.physics.add.collider(ball, violetBricks, hitBrick, null, this);
  131. this.physics.add.collider(ball, yellowBricks, hitBrick, null, this);
  132. this.physics.add.collider(ball, redBricks, hitBrick, null, this);
  133. // Make the player immovable
  134. player.setImmovable(true);
  135. // Add collision for the player
  136. this.physics.add.collider(ball, player, hitPlayer, null, this);
  137. // Create opening text
  138. openingText = this.add.text(
  139. this.physics.world.bounds.width / 2,
  140. this.physics.world.bounds.height / 2,
  141. 'Press SPACE to Start',
  142. {
  143. fontFamily: 'Monaco, Courier, monospace',
  144. fontSize: '50px',
  145. fill: '#fff'
  146. },
  147. );
  148. /**
  149. * The origin of the text object is at the top left, change the origin to the
  150. * center so it can be properly aligned
  151. */
  152. openingText.setOrigin(0.5);
  153. // Create game over text
  154. gameOverText = this.add.text(
  155. this.physics.world.bounds.width / 2,
  156. this.physics.world.bounds.height / 2,
  157. 'Game Over',
  158. {
  159. fontFamily: 'Monaco, Courier, monospace',
  160. fontSize: '50px',
  161. fill: '#fff'
  162. },
  163. );
  164. gameOverText.setOrigin(0.5);
  165. // Make it invisible until the player loses
  166. gameOverText.setVisible(false);
  167. // Create the game won text
  168. playerWonText = this.add.text(
  169. this.physics.world.bounds.width / 2,
  170. this.physics.world.bounds.height / 2,
  171. 'You won!',
  172. {
  173. fontFamily: 'Monaco, Courier, monospace',
  174. fontSize: '50px',
  175. fill: '#fff'
  176. },
  177. );
  178. playerWonText.setOrigin(0.5);
  179. // Make it invisible until the player wins
  180. playerWonText.setVisible(false);
  181. }
  182. /**
  183. * Our game state is updated in this function. This corresponds exactly to the
  184. * update process of the game loop
  185. */
  186. function update() {
  187. // Check if the ball left the scene i.e. game over
  188. if (isGameOver(this.physics.world)) {
  189. gameOverText.setVisible(true);
  190. ball.disableBody(true, true);
  191. } else if (isWon()) {
  192. playerWonText.setVisible(true);
  193. ball.disableBody(true, true);
  194. } else {
  195. // Put this in so that the player doesn't move if no key is being pressed
  196. player.body.setVelocityX(0);
  197. /**
  198. * Check the cursor and move the velocity accordingly. With Arcade Physics we
  199. * adjust velocity for movement as opposed to manipulating xy values directly
  200. */
  201. if (cursors.left.isDown) {
  202. player.body.setVelocityX(-350);
  203. } else if (cursors.right.isDown) {
  204. player.body.setVelocityX(350);
  205. }
  206. // The game only begins when the user presses Spacebar to release the paddle
  207. if (!gameStarted) {
  208. // The ball should follow the paddle while the user selects where to start
  209. ball.setX(player.x);
  210. if (cursors.space.isDown) {
  211. gameStarted = true;
  212. ball.setVelocityY(-200);
  213. openingText.setVisible(false);
  214. }
  215. }
  216. }
  217. }
  218. /**
  219. * Checks if the user lost the game
  220. * @param world - the physics world
  221. * @return {boolean}
  222. */
  223. function isGameOver(world) {
  224. return ball.body.y > world.bounds.height;
  225. }
  226. /**
  227. * Checks if the user won the game
  228. * @return {boolean}
  229. */
  230. function isWon() {
  231. return violetBricks.countActive() + yellowBricks.countActive() + redBricks.countActive() == 0;
  232. }
  233. /**
  234. * This function handles the collision between a ball and a brick sprite
  235. * In the create function, ball is a sprite and violetBricks, yellowBricks and
  236. * redBricks are sprite groups. Phaser is smart enough to handle the collisions
  237. * for each individual sprite.
  238. * @param ball - the ball sprite
  239. * @param brick - the brick sprite
  240. */
  241. function hitBrick(ball, brick) {
  242. brick.disableBody(true, true);
  243. if (ball.body.velocity.x == 0) {
  244. randNum = Math.random();
  245. if (randNum >= 0.5) {
  246. ball.body.setVelocityX(150);
  247. } else {
  248. ball.body.setVelocityX(-150);
  249. }
  250. }
  251. }
  252. /**
  253. * The function handles the collision between the ball and the player. We want
  254. * to ensure that the ball's direction after bouncing off the player is based
  255. * on which side of the player was hit. Also, to make things more difficult, we
  256. * want to increase the ball's velocity when it's hit.
  257. * @param ball - the ball sprite
  258. * @param player - the player/paddle sprite
  259. */
  260. function hitPlayer(ball, player) {
  261. // Increase the velocity of the ball after it bounces
  262. ball.setVelocityY(ball.body.velocity.y - 5);
  263. let newXVelocity = Math.abs(ball.body.velocity.x) + 5;
  264. // If the ball is to the left of the player, ensure the x velocity is negative
  265. if (ball.x < player.x) {
  266. ball.setVelocityX(-newXVelocity);
  267. } else {
  268. ball.setVelocityX(newXVelocity);
  269. }
  270. }

原文链接:http://www.cnblogs.com/neuedu/p/14202667.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号