phaser3入门教程-快速入门
第一节 - 前言
欢迎来看我们的第一个Phaser 3 游戏实例教程!在此我们将学习怎样做一个小游戏,它会有个玩家(player)在平台(platform)上来回跑、跳,收集星星,躲避坏蛋。在此过程中,我们将解释这个框架的一些核心特性。
Phaser是什么?
Phaser是一个HTML5游戏框架,它的目的是辅助开发者真正快速地制作强大的、跨浏览器的HTML5游戏。 做这个框架,主要是想发掘现代浏览器(兼及桌面和移动两类系统)的优点,。对浏览器的唯一要求是,支持画布(canvas)标签。
基本要求
下载这个zip文件 ,它含有本教程每一步的代码和资源。
你需要有一点非常、非常基础的JavaScript知识。
也请确认,你已经读过 《起步指南》 ,它会引导你怎样下载Phaser,搭建本地开发环境,再让你看一眼Phaser工程的结构及其核心函数。
如果你已经看过《起步指南》,那么应当下载了Phaser,已经把一切搭建好,准备好写代码了。请下载本教程所需资源,并解压到你的web服务器根目录下。
在你的编辑器中选中、打开part1.html页面,让我们详细看看它的代码。前面是一小段HTML样板代码,它引入了Phaser;接下来的代码结构如下:
var config = { type: Phaser.AUTO, width: 800, height: 600, scene: { preload: preload, create: create, update: update }};var game = new Phaser.Game(config);function preload (){}function create (){}function update (){}
这个config(配置)对象意味着你怎么配置Phaser游戏。有很多选项可以放在这个对象里,当你的Phaser知识增加时,你会碰到它们。不过在本教程中,我们只打算设置渲染器(renderer)、尺寸和默认Scene(场景)。
一个Phaser.Game对象实例(instance)赋值给一个叫game的局部变量,上述配置对象传给这个实例。这将开始启动Phaser的过程。
在Phaser 2 中,对象game用作几乎所有内部系统的入口,并常常是通过全局变量访问它。在Phaser 3 中不再如此,在全局变量中存储游戏实例不再有用。
属性type可以是Phaser.CANVAS,或者Phaser.WEBGL,或者Phaser.AUTO。这是你要给你的游戏使用的渲染环境(context)。推荐值是Phaser.AUTO,它将自动尝试使用WebGL,如果浏览器或设备不支持,它将回退为Canvas。Phaser生成的画布元素(canvas element)将径直添加到文档中调用脚本的那个节点上,不过也可以在游戏配置中指定一个父容器,如果你需要的话。
属性width和height设定了Phaser即将生成的画布元素的尺寸,在此例中是800 x 600 像素。这是游戏显示所用的分辨率,而你的游戏世界(world)可以是任意尺寸。
本教程后面还会涉及配置对象的scene属性的更多细节。
第二节 - 加载资源
By Richard Davey on 20th February 2018 @photonstorm
让我们加载游戏所需资源。要做到这一点,你要在场景中的一个叫preload(预加载)的函数内部,调用Phaser的Loader(加载器)。Phaser启动时会自动找到这个函数,并加载里面定义好的所有资源。
目前preload函数是空的。把它改为:
function preload (){ this.load.image('sky', 'assets/sky.png'); this.load.image('ground', 'assets/platform.png'); this.load.image('star', 'assets/star.png'); this.load.image('bomb', 'assets/bomb.png'); this.load.spritesheet('dude', 'assets/dude.png', { frameWidth: 32, frameHeight: 48 } );}
这样将加载5个资源:4张图(image)和一个精灵表单(sprite sheet)。也许对于某些人,它看起来够明白了,但我还想说说第一个参数,它叫资源的key(键值,即'sky','bomb')。这个字符串是一个链接,指向已加载的资源,你在代码中生成游戏对象时将用到它。你可以随意使用任何有效的JavaScript字符串作为键值。
显示图像
要显示已经加载的一张图像,我们把下面的代码到create(生成)函数中:
this.add.image(400, 300, 'sky');
你可以在part3.html中看到这行代码。如果你是在浏览器中加载的,你现在应该看到一个游戏画面,布满蓝色天空作为背景:
400和300是图像坐标的x值和y值。为什么是400和300呢?这是因为,在Phaser 3 中,所有游戏对象的定位都默认基于它们的中心点。这个背景图像的尺寸是800 x 600像素,所以,如果我们显示它时将它的中心定在0 x 0,你将只能看到它的右下角。如果我们显示它时定位在400 x 300,你能看到整体。
提示:你可以用setOrigin(设置原点)来改变这种情况。比如代码this.add.image(0, 0, 'sky').setOrigin(0, 0),将把图像的绘制定位点重置为左上角。在Phaser 2 中,定位点是通过属性anchor(锚点)获取的,但在Phaser 3 中则通过属性originX和originY。
游戏对象的显示顺序与你生成它们的顺序一致。所以,如果你想放一个星星的精灵在背景上,你就要保证在添加天空(sky)图像之后才添加星星(star)图像:
function create (){ this.add.image(400, 300, 'sky'); this.add.image(400, 300, 'star');}
如果你先放star(星星)图像,它将被sky(天空)图像盖住。
第三节 - 建立游戏世界
By Richard Davey on 20th February 2018 @photonstorm
在底层,代码this.add.image生成一个新的Image(图形)类游戏对象,并把它添加到当前场景的显示列表(display list)中。你的所有游戏对象都活在这个列表中。你可以把图像放置在任何位置,Phaser不会介意。当然,如果图像位于0x0到800x600这个区域之外,那么你视觉上看不到它,因为它已“脱离画面”,但它仍旧在场景中存在。
场景(Scene)自身没有确定的尺寸,在所有方向上都是无限延展的。镜头(Camera)系统控制着你观看场景的视野,你可以随意移动、推拉已激活的镜头。你还可以另外生成一些镜头,用于别的观看场景的视野。这一话题已经超出本特定教程,完全可以说,Phaser 3 的镜头系统,能力大大地超过Phaser 2的。以前完全不可能的东西现在可以了。
现在让我们搭建场景,添加一张背景图像和几个平台。这是更新后的create函数:
var platforms;function create (){ this.add.image(400, 300, 'sky'); platforms = this.physics.add.staticGroup(); platforms.create(400, 568, 'ground').setScale(2).refreshBody(); platforms.create(600, 400, 'ground'); platforms.create(50, 250, 'ground'); platforms.create(750, 220, 'ground');}
快速扫一眼这些代码,你可以看到一个对this.physics的调用。这意味着我们在使用Arcade(游乐场)物理系统(Physics system),不过在此之前我们还需要把它添加到游戏配置中,以便告诉Phaser我们的游戏需要它。所以让我们更新一下,引入对物理系统的支持。这是修订后的游戏配置:
var config = { type: Phaser.AUTO, width: 800, height: 600, physics: { default: 'arcade', arcade: { gravity: { y: 300 }, debug: false } }, scene: { preload: preload, create: create, update: update }};
补充信息:在Phaser 3 中,动画管理器(Animation Manager)是全局系统。其中生成的动画是全局变量,所有游戏对象都能用到它们。它们分享基础的动画数据,同时管理自己的时间轴(timeline)。这就使我们能够在某时定义一个动画,却可以应用到任意多的游戏对象上。这有别于Phaser 2,那时动画只属于据以生成动画的特定游戏对象。
第6节 - 添加物理系统
By Richard Davey on 20th February 2018 @photonstorm
Phaser支持多种物理系统,每一种都以插件形式运作,任何Phaser场景都能使用它们。在本文写作时,已经装有Arcade, Impact, Matter.js三种物理系统。针对本教程,我们将给我们的游戏使用Arcade物理系统,它简单,轻量,完美地支持移动浏览器。
物理精灵在生成时,即被赋予body(物体)属性,这个属性指向它的Arcade物理系统的Body。它表示精灵是Arcade物理引擎中的一个物体。物体对象有很多属性和方法,我们可以玩一下。
比如,在一个精灵上模仿重力效果,可以这么简单写:
player.body.setGravityY(300)
这是个随意的值,但逻辑讲,值越大你的对象感觉越重,下落越快。如果你把这些加到你的代码里,或者运行part5.html,你会看到玩家不停地往下落,完全无视我们先前生成的地面:
原因在于,我们还没有测试地面和玩家之间的碰撞。
我们已经跟Phaser说,我们的地面和平台将是静态物体。但是我们没有那么做,反而生成了动态的。如此,当玩家和他们碰撞时,玩家会停止一瞬,然后全部崩塌。这是因为,除非别那么说,否则地面精灵是会移动的物体,当玩家碰到它时,碰撞导致的力会作用语地面,因此两个物体交换彼此的速度,于是地面也开始下落。
要想玩家能与平台碰撞,我们可以生成一个碰撞对象。该对象监控两个物体(可以是组),检测二者之间的碰撞和重叠事件。如果发生事件,这时它可以随意调用我们的回调函数。不过仅仅就与平台间的碰撞而言,我们没必要那么做:
this.physics.add.collider(player, platforms);
碰撞器(Collider)是施魔法的地方。它接收两个对象,检测二者之间的碰撞,并使二者分开。在本例中,我们把玩家精灵和平台组交给它。它很聪明,可以执行针对所有组成员的碰撞,所以这一个调用就能处理与组合以及所有平台的碰撞。结果就有了一个稳固的平台,不再崩塌:
第7节 - 键盘控制
By Richard Davey on 20th February 2018 @photonstorm
碰撞很棒了,不过我们非常想玩家动起来。你可能想到了,去找文档,搜一搜怎样添加事件监听器,但这里不需要。Phaser有内置的键盘管理器,用它的一个好处体现在这样一个方便的小函数:
cursors = this.input.keyboard.createCursorKeys();
这里把四个属性up, down, left, right(都是Key对象的实例),植入光标(cursor)对象。然后我们要做的就是在update循环中做这样一些轮询:
if (cursors.left.isDown){ player.setVelocityX(-160); player.anims.play('left', true);}else if (cursors.right.isDown){ player.setVelocityX(160); player.anims.play('right', true);}else{ player.setVelocityX(0); player.anims.play('turn');}if (cursors.up.isDown && player.body.touching.down){ player.setVelocityY(-330);}
我们添加了很多代码,不过都还相当易读。
它做的第一件事,是查看方向左键是不是正被按下。如果是,我们应用一个负的水平速度,开动奔跑动画'left'。如果是方向右键正被按下,我们按字面意思做反向动作。通过清除速度值,再如此设置,一帧一帧,形成一个“走走停停”(stop-start)式的运动。
玩家精灵只有键被按下时才移动,抬起时立即停止。Phaser也允许你用动量(momentum)和加速度(acceleration)生成更为复杂的动作,不过这里已经得到我们的游戏所需要的效果了。键盘检测的最后部分,如果没有键被按下,就设置动画为'turn',水平速度为0。
赶快
代码的最后部分添加了跳起功能。方向up键是跳起键,我们检查它有没有被按下。不过我们同时也检测玩家是不是正与地面接触,否则在半空中还会往上跳。
如果所有这些条件都符合,我们应用一个垂直速度,330像素每秒。玩家会自动落回地面,因为有重力。控制已经就位,我们现在有了一个可以探索的游戏世界。请加载part7.html,玩一玩。尝试调整各个值,比如跳起值330,调低,调高,看看会有什么效果。
第8节 - 收集星星
By Richard Davey on 20th February 2018 @photonstorm
该给我们的小游戏定个目标了。让我们撒几颗星星到场景中,让玩家来收集。要做到这一点,我们会生成一个新的组,叫'stars',再充实它。在生成函数中,我们加入如下代码(这些可以在part8.html中看到):
stars = this.physics.add.group({ key: 'star', repeat: 11, setXY: { x: 12, y: 0, stepX: 70 }});stars.children.iterate(function (child) { child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));});
这个过程跟我们生成平台组近似。因为需要星星移动、反弹,我们生成动态物理组,而不是静态的。
组可以接收配置对象,以便于设置。在本例中,组配置对象有3个部分:首先,它设置纹理key(键值)为星星图像。这意味着配置对象生成的所有子项,都将被默认地赋予星星纹理。然后,它设置重复值为11。因为它自动生成一个子项,重复11次就意味着我们总共将得到12颗,这正好是我们的游戏所需要的。
最后的部分是setXY——这用来设置组的12个子项的位置。每个子项都将如此放置:初始是x: 12,y: 0,然后x步进70。这意味着第一个子项将位于12 x 0;第二个离开70像素,位于82 x 0;第三个在152 x 0,依次类推。'step'(步进)值用于组生成子项时加以排布,真是很方便的手段。选用值70是因为,这意味着所有12个子项将完美地横跨着布满画面。
下一段代码遍历组中所有子项,给它们的bounce.y赋予0.4到0.8之间的随机值,反弹范围在0(不反弹)到1之间(完全反弹)。因为星星都是在y等于0的位置产出的,重力将把它们往下拉,直到与平台或地面碰撞为止。反弹值意味着它们将随机地反弹上来,直到最终恢复安定为止。
如果现在我们这样就运行代码,星星会落下并穿过游戏底边,消失不见了。要防止这个问题,我们就要检测它们与平台的碰撞。我们可以再使用一个碰撞器对象来做这件事:
this.physics.add.collider(stars, platforms);
与此类似,我们也将检测玩家是否与星星重叠:
this.physics.add.overlap(player, stars, collectStar, null, this);
这会告诉Phaser,要检查玩家与组中任何一颗星星的重叠。如果检测到,他们就会被传递到'collectStar'函数:
function collectStar (player, star){ star.disableBody(true, true);}
简单来说,星星带着个已关闭的物体,其父级游戏对象被设置为不活动、不可见,即将它从显示中移除。现在运行一下游戏,我们得到一个玩家,它左冲右突的,跳起,从平台反弹,收集头顶上落下的星星。不错,毕竟就这么几行、多半看起来还很好理解的代码:)
第9节 - 计分
By Richard Davey on 20th February 2018 @photonstorm
最后我们打算给游戏增加两处改进:一个需要躲避的敌人,它会杀死玩家;收集到星星时得分。首先是得分。
为了做这个,我们将使用游戏对象Text(文本)。在此我们生成两个新的变量,一个持有实际得分,一个文本对象本身:
var score = 0;var scoreText;
scoreText在create函数中构建:
scoreText = this.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
16 x 16是显示文本的坐标位置。'score: 0'是要显示的默认字符串,接下来的对象包含字号、填充色。因为没有指定字体,实际上将用Phaser默认的,即Courier。
下一步我们要调整collectStar函数,以便玩家捡到一颗星星时分数会提高,文本会更新以反映出新状态:
function collectStar (player, star){ star.disableBody(true, true); score += 10; scoreText.setText('Score: ' + score);}
Part 10 - 跳跳弹
By Richard Davey on 20th February 2018 @photonstorm
现在该添加一些坏蛋,以此给我们的游戏收尾。这将给游戏增添很棒的挑战因素,这是此前缺乏的。
想法是这样的:你第一次收集到所有星星后,将放出一个跳跳弹。这个炸弹只是随机地在平台上各处跳,如果收集它,你就死了。所有星星会重新产出,以便你可以再次收集,如果你完成了,又会放出另一个炸弹。这将给玩家一个挑战:别死掉,取得尽可能高的分数。
我们首先需要的东西是给炸弹用的一个组,还有几个碰撞器:
bombs = this.physics.add.group();this.physics.add.collider(bombs, platforms);this.physics.add.collider(player, bombs, hitBomb, null, this);
炸弹当然会跳出平台,如果玩家碰到它们,我们将调用hitBomb函数。这个函数所作的就是停止游戏,使玩家变成红色:
function hitBomb (player, bomb){ this.physics.pause(); player.setTint(0xff0000); player.anims.play('turn'); gameOver = true;}
现在看来还不错,不过我们要放出一个炸弹。要做到这一点,我们改一下collectStar函数:
function collectStar (player, star){ star.disableBody(true, true); score += 10; scoreText.setText('Score: ' + score); if (stars.countActive(true) === 0) { stars.children.iterate(function (child) { child.enableBody(true, child.x, 0, true, true); }); var x = (player.x < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400); var bomb = bombs.create(x, 16, 'bomb'); bomb.setBounce(1); bomb.setCollideWorldBounds(true); bomb.setVelocity(Phaser.Math.Between(-200, 200), 20); }}
查看更多关于phaser3入门教程-快速入门的详细内容...