GameMaker: Studio 中文教程 #4:碰撞与遮挡

2018-05-29 16:09:25 田贻飞 19

编者按

indienova 会员青铜的幻想为希望了解学习 GameMaker: Studio 的中文读者专门撰写了本系列教程,本文为第四期,本期内容将教会大家如何在 GMS 中表现场景中物品的碰撞与遮挡关系。欢迎读者朋友在文章后留言,以便作者能够针对性地安排接下来的教程内容。

回顾

往期教程参见这里

在正式进入游戏教程之前,很想聊一聊独立游戏的开发流程。在游戏行业,所谓的敏捷开发(Scrum)几乎已经成为了标配,一副听起来很厉害的样子,很适合用来忽悠不明真相的围观群众。其实我理解中它的核心就是:围绕一系列的里程碑(milestone)来推动开发,每个时段只专注于完成特定的里程碑目标。

对于《冰杖秘闻》来说,我们将它的第一个里程碑设定为:在游戏中加入“洞穴世界”场景和人物“伊瑟拉”,并能使玩家控制人物在场景中的走动。

设定这样一个里程碑的好处多多,第一是可以明确团队中每个人的工作内容,例如美术需要完成这个场景和人物的静态图片和动画,策划设定场景人物尺寸比例、人物行走的操作方式是鼠标点击移动还是键盘 WASD 移动等,最后程序把这些功能实现出来。

第二是能够很好的控制开发节奏,在每一个里程碑中进行开发-完善-反馈的循环。在前两次的GMS教程中,我们先后加入了游戏的人物行走和场景,这可以认为是开发阶段,我们所做的是在里程碑描述中明确指出的功能。而在上次教程结尾所提到的:

  • 物体间的碰撞处理

  • 物体间的遮挡关系

  • 摄像机的跟随(由于本次教程篇幅所限,这项内容将推迟至之后的教程)

这些对所需功能的补充,则可以认为是本次里程碑的完善阶段。而在完善阶段过后,我们就得到了游戏《冰杖秘闻》的第一个里程碑版本。这个里程碑版本就可以拿给团队成员或者你的亲朋好友来进行测试了,从而获取反馈。然后根据反馈的结果,制定下一个里程碑的目标,如此这般,不断进行一个个的开发-完善-反馈循环。

最后我个人认为里程碑式的开发,对独立开发来说最有意义的是能够实际感受到项目的进展,这样能够一定程度上减少独立开发者在漫长的开发过程中因为看不到未来而弃坑的概率……

教程目标

本次教程的目标为完善第一次里程碑版本:

  • 添加ARPG中物品及人物的基本物理碰撞

  • 正确显示物品及人物的遮挡关系

  • 准备工作

如果是之前已经跟着教程动手实践过的同学,可以直接以上一次的项目文件为基础继续开发。而想要跳过之前内容的同学,可以点击这里下载完整的项目文件。

解压后的 GMS_TUT_02 文件夹中的 “Tutorial02/Tutorial02.gmx” 目录内即为上一次教程结束时的项目内容,可以以此为基础开始本次教程。

GMS的物理系统

不像大多数的三维游戏需要物体与环境间真实的互动,例如爆炸时周围物体所受到的冲击、人物在水中所受到的浮力或者车辆的速度、加速度等。对于 2D ARPG 来说,需要引擎提供的物理系统其实非常简单:

  1. 具有空间体积的物体不能占据重叠的空间。

  2. 物体间发生碰撞后的事件通知。

对应到游戏系统的实际内容举例来说就是:

  1. 人不能走进墙里。

  2. 子弹打到人了通知我。

我们这次教程先只涉及到第一种情况。

设置物体的物理属性

我们先来看看物体(Object)的属性面板,双击打开 obj_ysera

1

其中有一个选项是 Uses Physics(使用物理属性),在勾选了这个选项后就会展开物理属性的面板:

2

我们按照从上到下的顺序来说明。
首先是 Collision Shape(碰撞形状),它的三个选项是:

  • Circle —— 圆形

  • Box —— 长方形

  • Shape —— 自定义形状

下面紧接的是按钮 Modify Collision Shape(修改碰撞形状),点击它就会弹出一个窗口让你通过鼠标拖拽的方式来设定形状,对于人物角色来说,我们这里把碰撞形状设为长方形,这个长方形占据的尺寸是人物双脚的大小(这样设置的原因将在下一节说明):

3

再往下的物理属性依次是:

  • Density —— 密度

  • Restitution —— 弹性

  • Collision Group —— 碰撞分组

  • Linear Damping —— 线速度阻尼

  • Angular Damping —— 角速度阻尼

  • Friction —— 摩擦力

这些属性里,暂时我们要用到的只有密度这一项,即对于固定位置的物体,应当将 Density 设为0,这样系统会把它当作静态物体来处理。对于人物来说,保留默认的值0.5 就好了。
最后是三个勾选框,分别是:

  • Sensor —— 感应器模式

  • Start Awake —— 初始激活

  • Kinematic —— 动力学模式

我们在本次教程不会更改这里的选项,暂且大致了解下选项的作用。其中“传感器模式”打开后,该物体将不会与其他物体发生碰撞,而仅仅是检测是否有物体进入,因此适合用于作为游戏的触发器。例如玩家走进一个特定区域后触发剧情等。“初始激活”是默认选中的,即从房间被创建时这个物品的物理特性就开始发挥作用。“动力学模式”在打开后,将使该物体不受外界影响,例如重力及其他物品的碰撞。但作用在物体本身的力、加速度及速度依然遵守动力学规律。

碰撞形状设置的原因

下面简单的解释下这里把伊瑟拉的碰撞形状设置成双脚大小的矩形的考虑,主要是为了正确的进行行走的碰撞检测。举个例子,假如我们在场景中添加了一个池塘,这里简单的用一个蓝色的矩形来表示,同时也为这个池塘设定了与这个蓝色矩形相同大小的碰撞形状。设想当人物角色站在池塘下方往上走的情形,如果角色的碰撞矩形是人的全身,那么当她的头部碰到池塘时就使她停下来了:

4

而实际上我们希望的是,当人的双脚移动到池塘边缘时才停止:

5

不只是人物,在场景中的物体也需要遵循同样的原理,即将碰撞形状设置为物品底部的大小。但这样将碰撞形状设定为人物双脚的问题在于,当进行子弹与人体的碰撞检测时,这个设置就不对了。在实际开发中,我曾经试图搜索GMS是否支持添加多个碰撞形状,但结果是不行,最终的解决方案是为人体添加一个同样大小、始终随之移动的隐形物体来进行。全身的碰撞检测,这会在以后的教程中详细说明。

其他物品的物理属性

现在我们已经完成了伊瑟拉的碰撞设置,接下来为桶(obj_barrel)、雕像(obj_statue)、水池(obj_pool)和血池(obj_blood_pool)来设置碰撞形状,同时将静态物体的密度设为0(这里只有桶是非静态物品)。这里将我设置的碰撞形状列出作为参考:

6
7
8
9

GMS 在设置碰撞形状上所提供的选择略微有些不足,只有圆形、长方形以及最多由 8 个点组成的任意多边形这三种方案。如果想要更复杂的形状,就只能通过组合多个物体来实现了。

开启房间的物理选项

想要让这个场景能够正确的进行物理模拟,还需要开启房间的物理选项。打开房间属性面板中的physics(物理)栏,更改其中的两项。首先是勾选“Room is physics world”(房间是一个物理空间),其次是把“Gravity”(重力)的Y值从10改为0。如果制作的是一个 2D 平台跳跃游戏,我们需要 Y 方向的重力来使人物和物体能够在没有支撑的情况下自由落下。但对于 2D ARPG 来说,人物是可以静止的停留在场景中的任一地方的,因此不需要重力的存在。

10

修改人物行走代码

在更改完房间的物理选项后,还需要对之前编写的人物行走代码稍作修改。如果此时你运行游戏进行测试的话,会发现人物在原地不能移动。因为在脚本 YseraStep 中,我们是通过更改人物的 x、y 坐标来移动人物的。而当人物具备了物理属性后,她的x、y坐标是通过物理引擎来控制的,因此不能再直接修改 x、y 坐标。取而代之的是更改 phy_position_x 和 phy_position_y 这两个值,所以我们将这个脚本中的所有包含 x、y 的语句进行替换,例如:

x = x - 4;

更改为

phy_position_x = phy_position_x - 4;

相反,在你没有打开房间的物理选项的情况下,如果你编写了修改 phy_position_x 和 phy_position_y 的代码,就会在运行的时候出错。因为在没有物理模拟的房间里,这两个值是无效的。

设置碰撞目标

与其他物理引擎略有不同的一点是,在 GMS 中,即使你设置了物体的物理属性并添加了碰撞形状,你还是需要明确的指定该物体会和哪些其他物体发生碰撞。不然的话,系统默认该物体不与任何其他物品发生碰撞。添加想要发生碰撞的物体是通过添加一个新碰撞事件来完成的,例如,我们通过如下方式来添加伊瑟拉与桶的碰撞:

11

值得注意的是,在这个动图的最后,我为这个碰撞事件添加了一个空的脚本,因为如果不这么做的话系统会认为你没有处理这个碰撞事件,从而自动把这个碰撞事件删除掉。

重复以上步骤,依次再添加伊瑟拉与雕像、水池和血池的碰撞,完成后如下图:

12

第一次碰撞测试

现在我们可以运行游戏测试一下人物间的碰撞是否如预料之中一样:

13

这里可以看到,虽然碰撞是发生了,但与物体的碰撞让人物发生了旋转,这却是我们不想要的效果。因此我们需要将 phy_fixed_rotation 这个变量设置为1,它的作用是固定住物体的角度,不让它发生旋转。

禁止物体的旋转

设置 phy_fixed_rotation 这一变量的最好时机是在物品被创建出来的时候,即利用它的 Create 事件来触发执行。于是我们对上述人物及物体添加 Create 事件,并设置该变量:

14

再对木桶物体做同样的设置即可,因为其他密度设置为0的物体被视为静态物体,是既无法移动也无法旋转的。

第二次碰撞测试

我们再运行一次游戏,可以看到刚才的问题已经解决了:

19

但貌似在遮挡上还存在问题。

设置正确的遮挡显示

我们先看一下当前的问题在哪里,在上面这段测试的动图里,我刻意将伊瑟拉从木桶的上方移动到了木桶的下方。可以看到,无论在上方还是下方,伊瑟拉始终被木桶遮挡在后面。而正确的显示是应该让伊瑟拉在木桶上方时被木桶遮挡,而当移动到木桶下方时,显示在木桶的前面。
在 GMS 中决定对象渲染先后的是变量 depth(深度)。以下是官网对该变量的说明示例:

16

即深度值越小,离相机的距离越近。对于这种 45 度视角的 ARPG 来说,可以认为越在上方的物体,其深度值越大;反之越在下方的物体,深度值越小。这个关系可以简单的通过以下语句来实现:

depth = -y;

对于静态物体,即位置不会移动的物体来说(雕像、水池及血池),可以在它创建的时刻设置它的深度值:

17

而对于会发生移动的物体,即教程范例中的人物和木桶,则需要不断更新它的深度值,因此可以放在Step事件中执行。下面以伊瑟拉为例,在其Step对应的代码中加入这条语句:

18

遮挡测试

在加入设置物体深度的语句后,再次运行游戏进行测试:

15

可以看到,这次人物与木桶、血池之间能够正确的显示遮挡关系了。

总结

在这次教程里,我们讲解了如何设置物理碰撞参数以及设置物体深度以便正确的显示遮挡关系。关于摄像机的设置,由于篇幅的限制我们放到下一章再讲。下一次教程里,我们还会演示如何对现有项目进行重构以便增加项目的可维护性和扩展性。

同时,关于再后面教程的内容方面,我还想尝试在这里问一下独立精神 indienova 上关注这个系列教程的网友:
你最希望之后的教程讲解 GMS 制作独立游戏的哪个方面的内容?请留言回复。

  • GMS的寻路功能

  • 继续讲解ARPG的人物控制——攻击和技能

  • GMS实现游戏的UI(用户界面)

  • 敌人的AI

  • 以上全部

虽然上述选项 1~4 并非完全平行的选项,例如要想实现 AI 那么必须先完成人物的攻击和技能的控制,但是出发点是希望了解下大家的大概想法,以便规划之后的内容。
最后感谢网友的浏览、收藏和留言,你们的关注是我继续这个教程最大的动力!

附录:教程资源链接

该系列教程的项目/代码及原始美术素材全部更新至 GitHub 项目库