Phaser 编程技巧

2015年2月6日

欢迎!

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

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

获取源代码

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

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

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

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

基础设施的构建

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

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

当前的问题

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

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

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

你被包围了

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

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

放置一个转弯标识

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

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

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

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;

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

进入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‘);
    }

},

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

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

Star Bug

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

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

时间: 2024-10-28 07:39:44

Phaser 编程技巧的相关文章

ZStack中的编程技巧

1. 像函数一样使用的宏 //这个宏,用来被其他宏使用,构造一个正确有效的表达式.这个适合于一些离散语句的组合,不适合函数的重新命名 #define st(x)      do { x } while (__LINE__ == -1) 例如:#define aps_GroupsRemaingCapacity() ( APS_MAX_GROUPS - aps_CountAllGroups() ) 上述的这个宏,调用的其他函数来实现其功能,因此,不适合使用st()宏. 使用场景:  aps_Grou

【VC编程技巧】窗体?3.5对单文档或者多文档程序制作启动画面

(一)概要: 文章描述了怎样通过Visual C++ 2012或者Visual C++ .NET,为单文档或者多文档程序制作启动画面.在Microsoft Visual Studio 6.0中对于单文档程序(SDI)我们可以很方便利用微软提供的组件Visual C++ Component (Splash Screen).因为在Microsoft Visual Studio 6.0以后的版本或者Visual C++ .NET没有提供这个组件,我们可以通过自定义对话框来实现Splash Screen

单片机应用编程技巧问答

1. C语言和汇编语言在开发单片机时各有哪些优缺点? 答:汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言.其主要优点是占用资源少.程序执行效率高.但是不同的CPU,其汇编语言可能有所差异,所以不易移植. C语言是一种结构化的高级语言.其优点是可读性好,移植容易,是普遍使用的一种计算机语言.缺点是占用资源较多,执行效率没有汇编高. 对于目前普遍使用的RISC架构的8bit MCU来说,其内部ROM.RAM.STACK等资源都有限,如果使用C语言编写,一条C语言指令编译

编程技巧之表格驱动编程

/* Image format-dependent operations. */ typedef struct { jas_image_t *(*decode)(jas_stream_t *in, char *opts); /* Decode image data from a stream. */ int (*encode)(jas_image_t *image, jas_stream_t *out, char *opts); /* Encode image data to a stream.

java命名规范和编程技巧

一个好的java程序首先命名要规范. 命名规范 定义这个规范的目的是让项目中所有的文档都看起来像一个人写的,增加可读性,方便维护等作用 Package 的命名 Package 的名字应该都是由一个小写单词组成. Class 的命名 Class 的名字必须由大写字母开头而其他字母都小写的单词组成 Class 变量的命名 变量的名字必须用一个小写字母开头,后面的单词用大写字母开头. Static Final 变量的命名  Static Final 变量的名字应该都大写,并且指出完整含义. 参数的命名

分享25个实用的博客,有助你提高编程技巧

编程是一个不断变化的领域,一旦你选择了它作为你的职业,你就不可能停下学习的脚步了.因为科技的发展月新日异,要跟得上它发展的脚步,你必须不断地努力学习.在Forbes.com最近发表的一篇文章(25 Practical Blogs To Sharpen Your Coding Skills)中,作者Tomas Laurinavicius 推荐了25个他认为很适合程序员或学习编程的人在工作或学习中阅读的博客. 1. Scott Hanselman 在微软担任网络平台开发的Scott Hanselma

Python高效编程技巧

下面我挑选出的这几个技巧常常会被人们忽略,但它们在日常编程中能真正的给我们带来不少帮助. 1. 字典推导(Dictionary comprehensions)和集合推导(Set comprehensions) 大多数的Python程序员都知道且使用过列表推导(list comprehensions).如果你对list comprehensions概念不是很熟悉——一个list comprehension就是一个更简短.简洁的创建一个list的方法. >>> some_list = [1,

Matlab.NET混合编程技巧之——直接调用Matlab内置函数(附源码)

原文:[原创]Matlab.NET混合编程技巧之--直接调用Matlab内置函数(附源码) 在我的上一篇文章[原创]Matlab.NET混编技巧之--找出Matlab内置函数中,已经大概的介绍了matlab内置函数在混合编程中的优点,并通过程序找出了matlab中的大部分内置函数,当然更多人关心是如何像我所说得那样,不用直接编译,就直接在C#中调用这些内置函数.本文就带你揭开这些谜团. 声明,这篇文章是需要一点点混合编程基础的,基本概念和过程要懂一点,如果能简单成功混编一个简单的计算或者绘图例子

Matlab.NET混合编程技巧之——找出Matlab内置函数

原文:[原创]Matlab.NET混合编程技巧之--找出Matlab内置函数 Matlab与.NET的混合编程,掌握了基本过程,加上一定的开发经验和算法基础,肯定不难.反之,有时候一个小错误,可能抓破脑袋,加班几个晚上,调试才能解决.同样,由于Matlab.NET混编的特殊性,加上MathWorks的原因,英文文档和没有披露一些详细的细节(甚至不允许反编译MWArray.dll,呵呵,它不允许,不代表你不会哦).经过很多项目,和大量的实验,也发现了一些小技巧和小秘密,今天就分享其中一个,先做一个