这篇文章由Alvin Ourrad 和 Richard Davey所著。
已更新使用Phaser 2.0版本。
欢迎来到我们的第一篇关于使用Phaser制作一个游戏的教程。这里我们将学习如何制作一个关于角色在平台间奔跑跳跃收集星星的小游戏。在整个制作流程间我们会解释Phaser框架的一些核心特性。
Phaser是什么?
Phaser是一个HTML5游戏框架,致力于帮助开发者快速制作强大的跨浏览器HTML5游戏。不像有些框架,Phaser已完全调配手机浏览器。Phaser唯一的浏览器要求是支持canvas标签。Phaser也从Flixel借鉴很多东西。
要求
如果你已经浏览了入门指南你应该已经下载了Phaser,一切已经准备就绪就等着编写代码了。下载本教程的资源然后把它解压到你的本地web服务器根目录。
在你选择的文本编辑器打开part1.html网页,让我们仔细看看代码。在包含Phaser的样板HTML后面的代码结构如下:
var game = new Phaser.Game(800, 600, Phaser.AUTO, '', { preload: preload, create: create, update: update });
function preload() {
}
function create() {
}
function update() {
}
第一行你创建了一个Phaser.Game对象的实例并赋值给本地变量game,以此来激活Phaser。把它叫做game是惯例,但不是必需的,你会在Phaser的示例里发现这种作法。
前两个参数是Phaser要创建的canvas元素的宽高。这里是800 x 600像素。这个是游戏要显示的分辨率,你的游戏世界则可以是任意你喜欢的尺寸。第三个参数可以是Phaser.CANVAS,Phaser.WEBGL,或Phaser.AUTO。这个是你想使用的渲染上下文。推荐的参数是Phaser.AUTO,它会自动尝试使用WebGL,如果浏览器或设备不支持它就会回滚成Canvas。
第四个参数是一个空字符串,这个是你想插入Phaser创建的canvas元素的DOM元素的id。因为我们把它放空,canvas元素会简单地被附加到body。最后一个参数是一个包含四个Phaser基本函数引用的对象。它们的用法在这里被详细解释。注意这个对象不是必需的——Phaser支持一个完整的状态系统,允许你把你的代码分散到几个非常简洁的单一对象。对于这篇入门指南,我们使用这个方法有助于快速构建原型。
加载资产
让我们加载我们游戏需要的资产。你通过在preload函数里调用game.load来完成这个。Phaser在它启动时会搜寻这个函数并加载任何在其中被定义的资产。
当前preload函数是空的。把它变成这样:
function preload() {
game.load.image('sky', 'assets/sky.png');
game.load.image('ground', 'assets/platform.png');
game.load.image('star', 'assets/star.png');
game.load.spritesheet('dude', 'assets/dude.png', 32, 48);
}
它会加载四个资产:三张图片和一张精灵图。对于你们中的有些人这可能看起来很显然,但是我想说明下第一个参数,又称为资产的键。这个字符串是已加载资产的链接,当你创建子图形时会用到。对于资产的键你可以随意使用任何合法的JavaScript字符串。
创建一张子图形
为了添加一张子图形到我们的游戏,放置以下代码到create函数里:
game.add.sprite(0, 0, 'star');
如果你在浏览器里查看这个网页你应该会看到一个黑色的游戏场景,在左上角有一个单一星星的子图形。
显示的项的渲染顺序匹配你创建它们的顺序。所以如果你希望把背景放置在星星子图形的下面你应该确保背景早于星星被添加。
世界建立
game.add.sprite在内部创建一个新的Phaser.Sprite对象然后把它添加到游戏世界。这个世界就是你的所有对象所在之地,和Actionscript3的Stage差不多。
注意:这个游戏世界没有固定的尺寸,在所有方向无限延伸,(0,0)坐标是它的中心。为了你的方便Phaser把(0,0)放置在你的游戏的左上角,但是通过内建的摄像机你可以根据需求进行移动。
世界类可以通过game.world获取,它带来了很多便利的方法和属性,以帮助你在世界里分发你的物体。它包含一些简单的属性像game.world.height,但是也包含一些更高级的东西,我们会在另一个教程里用到。
现在让我们通过添加一个背景和一些平台来搭建场景。更新后的create函数如下:
var platforms;
function create() {
// We're going to be using physics, so enable the Arcade Physics system
game.physics.startSystem(Phaser.Physics.ARCADE);
// A simple background for our game
game.add.sprite(0, 0, 'sky');
// The platforms group contains the ground and the 2 ledges we can jump on
platforms = game.add.group();
// We will enable physics for any object that is created in this group
platforms.enableBody = true;
// Here we create the ground.
var ground = platforms.create(0, game.world.height - 64, 'ground');
// Scale it to fit the width of the game (the original sprite is 400x32 in size)
ground.scale.setTo(2, 2);
// This stops it from falling away when you jump on it
ground.body.immovable = true;
// Now let's create two ledges
var ledge = platforms.create(400, 400, 'ground');
ledge.body.immovable = true;
ledge = platforms.create(-150, 250, 'ground');
ledge.body.immovable = true;
}
如果你运行part4.html,你会看见一个更像游戏的场景:
第一部分和我们之前的星星子图形一样,只是我们把键替换成sky,最终显示一张天空背景而不是一颗星星。这是一张填充了整个游戏屏幕的800×600的PNG图片。
组
组很强大。正如它们的名字,组允许你集合简单的对象然后把它们当做一个单一单元进行控制。你也能在不同组间检测碰撞。这个游戏我们将使用两个不同的组,其中一个是上面代码创建的平台组。
platforms = game.add.group();
正如子图形,game.add方法创建我们的组对象。我们把它赋值给一个新的本地变量platforms。创建组后,现在我们可以给它添加物体。首先是地面ground。它被放置到游戏的底部,使用了之前加载的ground图片。地面被缩放以便填满游戏的宽。最后我们设置它的immovable属性为true。如果我们不这么做当玩家碰撞到它它会移动(细节查看Physics部分)。
当正确放置地面后我们用相同的技术创建两个更小的可供跳跃的壁架。
准备一个角色
创建一个新的本地变量player然后添加以下的代码到create函数。你能在part5.html看到:
// The player and its settings
player = game.add.sprite(32, game.world.height - 150, 'dude');
// We need to enable physics on the player
game.physics.arcade.enable(player);
// Player physics properties. Give the little guy a slight bounce.
player.body.bounce.y = 0.2;
player.body.gravity.y = 300;
player.body.collideWorldBounds = true;
// Our two animations, walking left and right.
player.animations.add('left', [0, 1, 2, 3], 10, true);
player.animations.add('right', [5, 6, 7, 8], 10, true);
这个创建一个新的子图形称作player,放置在x轴32像素,从游戏底部向上150像素的地方。我们指定它使用之前加载的dude资产。如果你回到preload函数看看你会看到dude被加载为精灵图,而非图片。这是因为它包含动画帧。整个精灵图看起来像这样:
你能看到一共9帧,4帧向左跑,1帧面朝摄像机,4帧向右跑。注意:Phaser支持翻转子图形来节省动画帧,但是作为入门教程我们使用守旧的方法。我们定义两个动画left和right。letf动画使用了0,1,2,3帧并且以10帧每秒的速度播放。true参数指定动画循环播放。这就是我们标准的奔跑循环动画,我们在相反的方向再重复一遍。动画设置完后我们创建一些物理属性。
实体和速度:物理世界
Phaser已支持一些不同的物理系统,载有Arcade Physics,,Ninja Physics 和 P2.JS Full-Body Physics。出于教程目的我们将使用Arcade物理系统,一个对手机浏览器而言简单轻量高效的物理系统。你会注意到我们不得不先启动物理系统,然后再启用每个我们想运用物理的子图形或组。
一旦完成子图形会获得一个新的body属性,它是ArcadePhysics.Body的一个实例。这表示子图形在Phaser的Arcade物理引擎里将被当做一个物理实体。body对象拥有很多有用的属性。为了在子图形上模拟重力效果,可以简单写成:
player.body.gravity.y = 300;
这可以是一个任意值,但是逻辑上,值越大,你的物体就越重,下落地越快。如果你添加这个到你的代码或者运行part5.html你会发现角色不停地下落,完全无视我们之前创建的地面:
原因是我们没有在地面和角色间检测碰撞。我们已经告诉Phaser我们的地面和壁架是固定不动的。如果我们不这么做当角色碰撞到它们角色会停止下落一会儿然后所有东西都会塌下。这是因为如果不指明不动,地面子图形是一个可动的物理物体(也称为动态物体),当角色撞击它,撞击力将作用于地面,这两个实体互换速度然后地面也就开始下落。
所以为了启用角色的碰撞和利用物理属性,我们需要在update函数里引入碰撞检测:
function update() {
// Collide the player and the stars with the platforms
game.physics.arcade.collide(player, platforms);
}
update函数在核心游戏循环的每帧都会调用。Physics.collide函数是施展魔术的表演者。它在两个物体间检测碰撞然后将碰撞的物体分开。所以我们在角色和平台组间使用这个函数。它足够聪明能为组的所有成员启用碰撞检测,所以角色将能和地面和所有的壁架碰撞。结果就是平台变得坚固:
使用键盘控制角色
碰撞很好,但是现在我们需要让角色能移动。你可能想去文档里找找如何添加一个事件处理程序,但是这里不需要这样。Phaser内建了键盘管理器,可以使用这个便利的函数:
cursors = game.input.keyboard.createCursorKeys();
这将给cursors对象填入四个属性:up,down,left,right,这些属性都是Phaser.Key对象的实例。接下来我们需要的就是在update循环里轮询这些:
// Reset the players velocity (movement)
player.body.velocity.x = 0;
if (cursors.left.isDown)
{
// Move to the left
player.body.velocity.x = -150;
player.animations.play('left');
}
else if (cursors.right.isDown)
{
// Move to the right
player.body.velocity.x = 150;
player.animations.play('right');
}
else
{
// Stand still
player.animations.stop();
player.frame = 4;
}
// Allow the player to jump if they are touching the ground.
if (cursors.up.isDown && player.body.touching.down)
{
player.body.velocity.y = -350;
}
虽然我们添加了很多代码不过它应该非常易读。我们做的第一件事是重置子图形的水平速度。然后我们检测左光标键是否被按下。如果是,我们应用一个负的水平速度然后开始播放向左奔跑的动画。如果右光标键被按下则采取相反的措施。通过在每帧清除速度然后再设置它的方法创建了停止—开始式的运动。
角色的子图形只有在一个键被按下的时候移动,没有按下则立即停止。Phaser也允许你使用动量和加速度创建更复杂的运动,但是之前的效果就是我们这个游戏需要的。键检测的最后部分就是如果没有键被按下就把帧设置为4。精灵图的第四帧就是角色看着你,没有运动。
跳跃
代码的最后部分就是添加跳的能力。上光标键就是我们的跳跃键,我们会检测它是否被按下。然而我们也要检测角色是否接触到地面,否则它们可能会在半空中跳跃。如果这些条件都满足我们会应用一个350的竖直速度。角色会自动落回地面因为我们给它应用了重力值。当控制设置好后我们得到了一个我们可以探索的游戏世界。加载part7.html然后玩一玩。尝试调整类似350能使角色跳的更高或更低的值然后看看会有什么效果。
星光
是时候给我们的小游戏一个目的。让我们在场景里撒下一些星星然后让角色去收集它们。为了完成这个目标我们会创建一个新的组stars然后填充它。在我们的create函数我们添加以下代码(详见part8.html):
stars = game.add.group();
// Here we'll create 12 of them evenly spaced apart
for (var i = 0; i < 12; i++)
{
// Create a star inside of the 'stars' group
var star = stars.create(i * 70, 0, 'star');
// Let gravity do its thing
star.body.gravity.y = 6;
// This just gives each star a slightly random bounce value
star.body.bounce.y = 0.7 + Math.random() * 0.2;
}
流程和我们创建平台组的时候相似。我们使用JavaScript的for循环在游戏里创建了12颗星星。它们拥有i*70的x轴坐标,这意味着它们每隔70像素等距放置。如同角色我们给这些星星一个重力值使得它们能下落,给它们一个反弹值使得它们撞击平台后会反弹几下。反弹是一个介于0(不会反弹)到1(无消耗反弹)的值。我们会设置反弹值在0.7到0.9左右。如果我们运行了代码会发现星星跌下了游戏的底部。为了防止那样我们需要在update循环里在它们和平台间启用碰撞检测:
game.physics.arcade.collide(stars, platforms);
同时我们也要检测星星是否和角色重叠了:
game.physics.arcade.overlap(player, stars, collectStar, null, this);
这行代码告诉Phaser去检测角色和星星组里任何星星的重叠。如果发现重叠就把它们传入collectStar函数:
function collectStar (player, star) {
// Removes the star from the screen
star.kill();
}
很简单,当一个星星被清除就不再显示它。运行游戏,现在我们的角色可以左冲右撞,可以跳跃,可以从平台上反弹和收集从天而降的星星。很不错只用了很少的几行代码,大多数非常易读。
最后润色
最后的调整是添加分数。实现这个我们将使用一个Phaser.Text对象。这里我们创建两个新变量,一个保存着实际的分数一个是文本对象本身:
var score = 0;
var scoreText;
scoreText在create函数里赋值:
scoreText = game.add.text(16, 16, 'score: 0', { fontSize: '32px', fill: '#000' });
16×16是文本显示位置的坐标。score 0是默认显示的字符串,最后一个对象包含了字体尺寸和填充颜色。没有指定字体我们实际上使用了浏览器的默认字体,在Windows系统是Arial。接下来我们需要修改collectStar函数,以便当角色拾起一颗星星分数会增加而文本会更新以反映分数:
function collectStar (player, star) {
// Removes the star from the screen
star.kill();
// Add and update the score
score += 10;
scoreText.content = 'Score: ' + score;
}
所以每颗星星十分,scoreText会更新以显示新分数。如果你运行part9.html你会看到最终的游戏:
结尾
你现在学习了如何创建一个带有物理属性的子图形,如何控制它的运动,如何使它和一个小游戏世界里的其它物体交互。还有很多东西你可以去增强这个游戏,比如说它没有结束或惩罚的概念。为什么不添加一些你必须躲避的钉子?你可以创建一个新的钉子组然后让它和角色检测碰撞,只是这次碰撞将杀死角色而不是钉子。或者做一个非暴力的游戏,你必须快速移动,挑战在尽可能短的时间内收集所有的星星。我们已经在压缩包里放置了额外的图形以帮助激发你的灵感。
在你学习了这篇教程和这250+个例子后,你应该对未来的工程有一个结实的基础。但是只要你有疑问,需要建议或者想分享,随意来Phaser forum。
译者注
本文翻译自http://www.photonstorm.com/phaser/tutorial-making-your-first-phaser-game。本人英语老师死得早,如果发现错误请随意评论。