Phaser 编程技巧

来源:互联网 发布:功德人生软件下载 编辑:程序博客网 时间:2024/06/09 06:01

2015年2月6日

欢迎!

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

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

获取源代码

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

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

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

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

Let's Code

基础设施的构建

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

Tiled

这个软件导出了一个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);

当前的问题

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

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

Intersection

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

你被包围了

所以,汽车如何知道他是否可以转弯,什么时候它应该转弯呢?为了弄清楚这个,我们在汽车四周创建了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 数组中。启用渲染调试开关,我们能够看到汽车知道他四周的相关信息:

Debug

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

放置一个转弯标识

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

可以利用 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);}

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

Turn Point

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;

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

Vroom Vroom

进入Pacman

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

Pacman

动画由 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');    }},

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

Munch Munch

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

Star Bug

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

StarBug

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

0 0