Phaser是什么?
Phaser是一个HTML5游戏框架,目的是为了快速地制作跨浏览器的HTML5游戏。 这个框架,主要发掘了现代浏览器(兼及桌面和移动两类系统)的优点,所以对浏览器的唯一要求是,就是只要支持画布(canvas)标签就可以了。
第一个Phaser 3 游戏实例教程
我们将通过一个小游戏学习怎样使用Phaser。这个小游戏里会有个玩家(player)在平台(platform)上来回跑、跳,收集星星,躲避炸弹。在实践过程中,除了熟悉Phaser的api使用外,顺便阐述Phaser框架的一些核心特性。
基本要求
- 会用javascript
- 需要一个搭建好的web服务(比如nginx、apache、iis、tomcat等等)
- 下载这个zip文件,它含有本教程每一步的代码和资源。
再次确认,你已经读过《起步指南》,它会引导你怎样下载Phaser,搭建本地开发环境,顺便看一眼Phaser项目的结构及其核心api。
如果你已经看过《起步指南》,那么应当已经已经把一切准备工作都搭建好,准备好写代码了。请下载本教程所需资源,并解压到你的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
属性的更多细节。
加载资源
让我们加载游戏所需资源。要做到这一点,你要在场景中的一个叫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
(天空)图像盖住。
建立游戏世界
在底层,代码this.add.image
生成一个新的Image(图形)类游戏对象,并把它添加到当前场景的显示列表(display list)中。你的所有游戏对象都活在这个列表中。你可以把图像放置在任何位置,Phaser不会介意。当然,如果图像位于0x0到800×600这个区域之外,那么你视觉上看不到它,因为它已“脱离画面”,但它仍旧在场景中存在。
场景(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 } };
新加的是physics
属性。这些代码就位之后,如果运行它(你可以在本教程的zip文件中part4.html
中找到),你将看到一个更有游戏样子的场景:
我们有了一个背景和一些平台,可以这些平台到底怎么样才能运作起来呢?
平台
我们刚加了一堆代码到create
函数中,此函数应该更详尽地解释一下。首先,这一部分:
platforms = this.physics.add.staticGroup();
这一句生成一个静态物理组(Group),并把这个组赋值给局部变量platforms
。在Arcade物理系统中,有动态的和静态的两类物体(body)。动态物体可以通过外力比如速度(velocity)、加速度(acceleration),得以四处移动。它可以跟其他对象发生反弹(bounce)、碰撞(collide),此类碰撞受物体质量和其他因素影响。
与此明显不同的是,静态物体只有位置和尺寸。重力对它没有影响,你不能给它设置速度,有东西跟它碰撞时,它一点都不动。名副其实,完全是静态的。所以用作地面和平台很完美,我们打算让玩家在上面来回跑动。
那么什么是组呢?如其名所示,是把近似对象组织在一起的手段,控制对象全体就像控制一个统一的个体。你也可以检查组与其他游戏对象之间的碰撞。组能够生成自己的游戏对象,这是通过便利的辅助函数如create
实现的。物理组会自动生成已经开启物理系统的子项(children),省得你处理时跑腿。
平台组做好了,我们现在可以用它生成平台:
“` platforms.create(400, 568, ‘ground’).setScale(2).refreshBody();
platforms.create(600, 400, ‘ground’); platforms.create(50, 250, ‘ground’); platforms.create(750, 220, ‘ground’); “`
这就生成了场景,如前所见。
在预加载时,我们输入了图像’ground’。它是个简单的绿色长方形,尺寸是400 x 32像素,将用于我们的基础平台:
上述代码的第一行,添加一张新的地面图像到400 x 568的位置(请记住,图像定位基于中心点)——问题是,我们需要这个平台撑满游戏的宽度。否则玩家就会掉出边界。要做到这一点,我们用函数setScale(2)
把它按x2(两倍)缩放。现在它的尺寸是800 x 64了,恰好符合我们的要求。要调用refreshBody()
,这是因为我们缩放的是一个 静态 物体,所以必须把所作变动告诉物理世界(physics world)。
地面已经缩放、就位,现在该别的平台了:
platforms.create(600, 400, 'ground'); platforms.create(50, 250, 'ground'); platforms.create(750, 220, 'ground');
这个步骤跟前面完全相同,只是不需要缩放,因为他们的尺寸本来就正好。
3个平台已经放到画面各处,距离合适,以便玩家能蹦上去。
那么让我们添加玩家。
玩家
我们已经有了可爱、诱人的平台,但还没有人在上面跑动。让我们改一下。
做一个新的变量player
,并把下面的代码添加到create
函数中。你可以在part5.html
中看到这些:
“` player = this.physics.add.sprite(100, 450, ‘dude’);
player.setBounce(0.2); player.setCollideWorldBounds(true);
this.anims.create({ key: ‘left’, frames: this.anims.generateFrameNumbers(‘dude’, { start: 0, end: 3 }), frameRate: 10, repeat: -1 });
this.anims.create({ key: ‘turn’, frames: [ { key: ‘dude’, frame: 4 } ], frameRate: 20 });
this.anims.create({ key: ‘right’, frames: this.anims.generateFrameNumbers(‘dude’, { start: 5, end: 8 }), frameRate: 10, repeat: -1 }); “`
这里有两件不同的事情:生成物理精灵(sprite),生成精灵能用到的几个动画。
物理精灵
代码第一部分生成精灵:
“` player = this.physics.add.sprite(100, 450, ‘dude’);
player.setBounce(0.2); player.setCollideWorldBounds(true); “`
这样生成一个新的精灵,叫player
(玩家),位于100 x 450像素,在游戏的下部。精灵是通过物理游戏对象工厂函数(Physics Game Object Factory,即this.physics.add
)生成的,这意味着它默认拥有一个动态物体。
精灵生成后,被赋予0.2的一点点反弹(bounce)值。这意味着,它跳起后着地时始终会弹起那么一点点。然后精灵设置了与世界边界(bound)的碰撞。——边界默认在游戏尺寸之外。我们(通过player.setCollideWorldBounds(true)
)把游戏(的世界边界)设置为800 x 600后,玩家就不能不跑出这个区域了。这样会让玩家停下来,不能跑出画面边界,或跳出顶边。
动画
如果回顾一下preload
函数,你会看到’dude’是作为精灵表单(sprite sheet)载入的,而非图像。这是因为它包含了动画帧(frame)。完整的精灵表单是这个样子的:
总共有9帧,4帧向左跑动,1帧面向镜头,4帧向右跑动。注意:Phaser支持翻转精灵,以节省动画帧,不过因为这是教程,我们将保持老派做法。
我们定义两个动画,叫’left’和’right’。这是’left’动画:
this.anims.create({ key: 'left', frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }), frameRate: 10, repeat: -1 });
‘left’动画使用0, 1, 2, 3帧,跑动时每秒10帧。’repeat -1’告诉动画要循环播放。
这是我们的标准跑动周期。反方向的动画把这些重复一下,键值用’right’。最后一个动画键值用’turn'(转身)。
补充信息: 在Phaser 3 中,动画管理器(Animation Manager)是全局系统。其中生成的动画是全局变量,所有游戏对象都能用到它们。它们分享基础的动画数据,同时管理自己的时间轴(timeline)。这就使我们能够在某时定义一个动画,却可以应用到任意多的游戏对象上。这有别于Phaser 2,那时动画只属于据以生成动画的特定游戏对象。
添加物理系统
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)是施魔法的地方。它接收两个对象,检测二者之间的碰撞,并使二者分开。在本例中,我们把玩家精灵和平台组交给它。它很聪明,可以执行针对所有组成员的碰撞,所以这一个调用就能处理与组合以及所有平台的碰撞。结果就有了一个稳固的平台,不再崩塌:
\
键盘控制
碰撞很棒了,不过我们非常想玩家动起来。你可能想到了,去找文档,搜一搜怎样添加事件监听器,但这里不需要。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,调低,调高,看看会有什么效果。
收集星星
该给我们的小游戏定个目标了。让我们撒几颗星星到场景中,让玩家来收集。要做到这一点,我们会生成一个新的组,叫’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); }
简单来说,星星带着个已关闭的物体,其父级游戏对象被设置为不活动、不可见,即将它从显示中移除。现在运行一下游戏,我们得到一个玩家,它左冲右突的,跳起,从平台反弹,收集头顶上落下的星星。不错,毕竟就这么几行、多半看起来还很好理解的代码:)
\
计分
最后我们打算给游戏增加两处改进:一个需要躲避的敌人,它会杀死玩家;收集到星星时得分。首先是得分。
为了做这个,我们将使用游戏对象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);
} “`
这样一来,每颗星星加10分,scoreText
将更新,显示出新的总分。如果运行part9.html
,你可以看到星星掉下来,收集星星时分数会提高。
最后一节我们将添几个坏蛋。
跳跳蛋
现在该添加一些坏蛋,以此给我们的游戏收尾。这将给游戏增添很棒的挑战因素,这是此前缺乏的。
想法是这样的:你第一次收集到所有星星后,将放出一个跳跳弹。这个炸弹只是随机地在平台上各处跳,如果收集它,你就死了。所有星星会重新产出,以便你可以再次收集,如果你完成了,又会放出另一个炸弹。这将给玩家一个挑战:别死掉,取得尽可能高的分数。
我们首先需要的东西是给炸弹用的一个组,还有几个碰撞器:
“` 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);
}
} “`
我们使用一个组的方法countActive
,看看有多少星星还活着。如果没有了,那么玩家把它们收集完了,于是我们使用迭代函数重新激活所有星星,重置它们的y位置为0。这将使所有星星再次从画面顶部落下。
下一部分代码生成一个炸弹。首先,我们取一个随机x坐标给它,始终在玩家的对侧画面,以便给玩家个机会。然后生成炸弹,设置它跟世界碰撞,反弹,拥有随机速度。
最终结果是个很棒的小炸弹精灵,它在画面上跳呀跳。尺寸小,开始的时候易于躲避。不过数量增加后就变得比较棘手!
我们的游戏已经做好了:)
结论
现在你已经学会怎样生成有物理属性的精灵,学会控制它的动作,学会使它与其他对象在一个小小的游戏世界里互动。你还可以做很多事情,以便增强它。为什么不扩展平台的尺寸并允许镜头摇动呢?也许可以增加不同类型的坏蛋,不同分值的收集活动,或者给玩家一个血条(health bar)。
或者,为了做个非暴力型的,你可以把它做成比快游戏(speed-run),仅仅挑战人们去尽可能快地收集星星。
有了本教程中所学到的东西,还有你能得到几百个实例的帮助,你现在已经为将来的项目准备了牢靠的基础。不过你总还会有疑问,需要建议,或者想分享你一直在做的东西,到时候请随意到Phaser论坛请求帮助。
Facebook即时游戏
Phaser 3 完全支持生成Facebook即时游戏。现在你已经学会怎样做Phaser游戏,为什么不看看怎样方便地转换为即时游戏呢?在我们专门 的《起步指南》里有。
今天的文章phaserjs_pscs3入门自学教程分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/64231.html