天天看点

Phaser 编程技巧

2015年2月6日

欢迎!

Phaser 编程技巧

在连续几周讨论了平台跳跃游戏的机制之后,我们也应该休息一下了。因此,本周的教程将基于论坛上经常谈论的特性:网格运动,或者可以说得更加明确一点:如何像Pacman(译者注:在红白机上叫做吃豆子,或者小精灵)那样在网格中移动。

我们本周讨论的代码可以让玩家优雅地在瓦片地图中移动,在很小的空间中转弯。我们还将构建Pacman游戏的核心代码。

获取源代码

在这里我只会着重高亮最重要的部分代码。所以请先浏览一下代码。如果你对一些代码有疑问,你可以去论坛询问。

运行/编辑汽车游戏代码可以到 jsbin 或者 codepen (译者:国内用户可访问runjs)

运行/编辑Pacman代码可以到jsbin or codepen (译者:国内用户可访问runjs)

克隆Phaser编程技巧代码可以到git repo。

Phaser 编程技巧

基础设施的构建

我们需要一个玩家精灵和一张瓦片地图。瓦片地图包含来了关卡的布局。在这里,我们在Tiled(译者注:Tiled官网 http://www.mapeditor.org/)中画了一个简单的关卡:

Phaser 编程技巧

这个软件导出了一个JSON文件,然后和tileset一起导入游戏中。在 create 方法中我们生成了所有的对象:

this.map = this.add.tilemap('map');
this.map.addTilesetImage('tiles', 'tiles');

this.layer = this.map.createLayer('Tile Layer 1');

this.map.setCollision(20, true, this.layer);

this.car = this.add.sprite(48, 48, 'car');
this.car.anchor.set(0.5);

this.physics.arcade.enable(this.car);

this.cursors = this.input.keyboard.createCursorKeys();

this.move(Phaser.DOWN);      

我们创建了地图和图层。碰撞被设置到20号瓦片上(暗灰色的砖块)。汽车摆放在地图的左上角,并且一开始就向下移动。.

在 update 函数中,我们检测与瓦片地图的碰撞:

this.physics.arcade.collide(this.car, this.layer);      

当前的问题

玩家的地图中的移动是一件需要解决的游戏的挑战。 尽管表面上这个看起来很简单,但是实际上这需要玩家对象对周边的情况有一个基本的了解。

玩家通过上下左右方向键来控制,当按键按下的时候,玩家开始向着对应的方向不停的移动,直到撞到墙壁或者按下其他方向键转到其他方向。

Phaser 编程技巧

在上面这个截图中,汽车即将接近一个交叉路口。如果用户不按任何键,它将继续向下移动。然而,如果正好在到达墙的右边的空隙时按下右键,汽车就会向右开。

你被包围了

所以,汽车如何知道他是否可以转弯,什么时候它应该转弯呢?为了弄清楚这个,我们在汽车四周创建了4块临时瓦片。

在 update 函数中,我们将使用Phaser Tilemap类中内建的特性,此特性可用于扫描特殊的瓦片:

this.marker.x = this.math.snapToFloor(Math.floor(this.car.x), 32) / 32;
this.marker.y = this.math.snapToFloor(Math.floor(this.car.y), 32) / 32;

var i = this.layer.index;
var x = this.marker.x;
var y = this.marker.y;

this.directions[Phaser.LEFT] = this.map.getTileLeft(i, x, y);
this.directions[Phaser.RIGHT] = this.map.getTileRight(i, x, y);
this.directions[Phaser.UP] = this.map.getTileAbove(i, x, y);
this.directions[Phaser.DOWN] = this.map.getTileBelow(i, x, y);      

Tilemap.getTileLeft

 及其同类函数正是用于实现我们上面所说的功能:返回给定坐标左边的瓦片(如果没有找到,则范围

null

)。

因为这些函数是基于瓦片坐标而不是像素的,所以我们首先要找出我们的汽车的地图中的确切位置。我们对汽车的x和y值调用 

Math.floor

 函数,然后使用 

Phaser.Math.snapToFloor

 转换为网格坐标。这几步就会得到汽车所在的确切瓦片。我们把结果存放在 

marker

 变量中。

周围四块瓦片存放在 

directions

 数组中。启用渲染调试开关,我们能够看到汽车知道他四周的相关信息:

Phaser 编程技巧

绿色的瓦片表示汽车可以安全的移动过去。红色的表示是障碍物,白色的表示当前的移动方向。

放置一个转弯标识

确认汽车是否可以转弯是第一个要解决的事情。第二个要解决的是要告诉它什么时候转弯,因为我们想要它在快达到地图合适的位置的时候转弯,否则,它就会撞到墙上停止向前。

可以利用 

checkDirection

 函数来实现这个。

这个函数需要传入一个参数:汽车将要转弯的方向。这是一个Phaser方向常量,例如

Phaser.LEFT

 或 

Phaser.DOWN

这个函数做的第一件事就判断是否满足以下任意一个条件:

  • 汽车的运动方向就是要转向的那个方向
  • 那个方向上没有瓦片
  • 那个方向上的瓦片不是一个“安全瓦片”(例如是一堵墙)

如果不满足上面任意一个条件,接下来就会设置一个转向标志。这个标志保存在 

turnPoint

 中,这是个  

Phaser.Point

  对象,保存了我们要汽车改变方向时所在的点的坐标。

if (this.current === this.opposites[turnTo])
{
    this.move(turnTo);
}
else
{
    this.turning = turnTo;

    this.turnPoint.x = (this.marker.x * this.gridsize) + (this.gridsize / 2);
    this.turnPoint.y = (this.marker.y * this.gridsize) + (this.gridsize / 2);
}      

理想的转弯点就是汽车当前正驶入的那个方块的中心。在图中可以看到一个黄点:

Phaser 编程技巧

Warm and Fuzzy Inside

当 car.x 和 car.y 与转弯点的值匹配时,汽车将转向新的方向。如果汽车以每帧一个像素的速度移动的话,那没问题。但是如果我们想要应用加速效果,或者其他改变汽车速度的方法,就会出问题。因为汽车的x/y坐标将只会是与转弯点接近而不是正好相等。

为了解决这个问题,我们使用了 

Phaser.Math.fuzzyEqual

 这个函数。它接受两个值和一个阈值。它将比较两个值,如果他们在差在阈值范围内,他们被认为是相等:

this.math.fuzzyEqual(a, b, threshold)      

这正满足我们的需求。对于速度为150,我们的阈值可以设置为3。这个值足够可以保证汽车不会跳过转弯点。在 

update

 函数中,我们将检查汽车是否已经到达了转弯点。

当汽车到达时,我们将重新设置它的坐标,同时重新设置汽车的刚体的坐标,然后转向新的方向:

this.car.x = this.turnPoint.x;
this.car.y = this.turnPoint.y;

this.car.body.reset(this.turnPoint.x, this.turnPoint.y);

this.move(this.turning);

this.turning = Phaser.NONE;      

通过增加上面的这最后一部分代码,你现在可以自由的在地图上行驶,在拐角转弯,随意的的撞墙。

Phaser 编程技巧

进入Pacman

让我们来用上面的工作机制去克隆一个Pacman。一开始的构建是一样的:一个玩家精灵和一幅瓦片地图。不同的在于我们的Pacman精灵是动态的:

Phaser 编程技巧

动画由 Phaser Animation Manager 来处理:

this.pacman.animations.add('munch', [0, 1, 2, 1], 20, true);
this.pacman.play('munch');      

然而,在 

move

 函数中,我们需要将它的脸转向新的方向:

this.pacman.scale.x = 1;
this.pacman.angle = 0;

if (direction === Phaser.LEFT)
{
    this.pacman.scale.x = -1;
}
else if (direction === Phaser.UP)
{
    this.pacman.angle = 270;
}
else if (direction === Phaser.DOWN)
{
    this.pacman.angle = 90;
}      

我们重设了它的水平缩放和角度,然后基于它的方向进行调整。它的脸默认是朝右的,所以我们可以通过调整 

scale.x

 来让它朝左。要让它朝上或者朝下,就要进行旋转。

另外一个重要的部分就是:Pacman实际上比网格要大,所以我们需要调整它的刚体以网格大小:

this.pacman.body.setSize(16, 16, 0, 0);      

这一句是在精灵(32x32大小)的中心位置设置了一个16x16大小的刚体,这就完美的适应了16x16大小的网格。

豆子

Pacman 需要一些豆子来吃。豆子已经使用了7号瓦片画到了瓦片地图上。所以我们将使用Phaser特性来将所有的7号瓦片转变为精灵。

this.dots = this.add.physicsGroup();

this.map.createFromTiles(7, this.safetile, 'dot', this.layer, this.dots);

this.dots.setAll('x', 6, false, false, 1);
this.dots.setAll('y', 6, false, false, 1);      

这里首先获取豆子瓦片,用空白瓦片(安全瓦片)来代替,然后为每一个空白瓦片在 

dots

 组中添加一个精灵。 

setAll

 函数用来调整豆子精灵的位置,添加6个像素的偏移,因为他们只有4x4大小,这可以让他们处于瓦片的中心。

Pacman与豆子之间的碰撞检测在 

update

 函数中:

this.physics.arcade.overlap(this.pacman, this.dots, this.eatDot, null, this);      

如果他们重叠了,就调用:

eatDot: function (pacman, dot) {

    dot.kill();

    if (this.dots.total === 0)
    {
        this.dots.callAll('revive');
    }

},      

豆子被销毁了。如果豆子组中的豆子数量变为零,我们要让豆子重生。所以你可以重新把豆子吃光!

Phaser 编程技巧

希望你可以看到,我们只用很少的代码,我们现在实现了一个最基本的吃豆子游戏。

Star Bug

基于上面这些代码,我们利用这些概念将游戏进一步演变。增加更多的关卡、会飞的敌人和大量的收藏品。最终的结果就是Star Bug游戏:

Phaser 编程技巧

这个游戏将会出现在我们即将面世的 Phaser Book of Games 这本书中。

继续阅读