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

作者:青铜的幻想
2016-08-10
21 34 46

编者按

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_xphy_position_y 这两个值,所以我们将这个脚本中的所有包含 x、y 的语句进行替换,例如:

x = x - 4;

更改为

phy_position_x = phy_position_x - 4;

相反,在你没有打开房间的物理选项的情况下,如果你编写了修改 phy_position_xphy_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 项目库

近期点赞的会员

 分享这篇文章

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. SevenK 2016-08-10

    非常感谢带来的这4期关于GMS的讲解。看过以后收获很大,对GMS也有了一个更完整地认识。对于后续教程的话如果条件允许当然是希望以上全部啦,不过非要从中选择的话个人感觉还是人物的攻击和技能的控制,这部分内容更基础也更关乎游戏的核心玩法。

  2. ayame9joe 2016-08-10

    同意楼上。这套 gms 教程其实不止对这个引擎,其实对于整个游戏开发也是非常好的入门。作者的细致让人敬佩!

  3. 黑轮酱 2016-08-11

    - -我还记得为了解决穿墙问题,我懵逼了好久……

  4. moonagent 2016-08-11

    图的顺序有点问题

    • craft 2016-08-11

      @moonagent:实在抱歉,是我这边的错。已经修改了~ 初排的时候弄反了,之前应该改过一次……好像不小心给复原了。

  5. Cloudmy 2016-08-11

    首先谢过作者,另外提醒一下,遮挡测试的两张图可能上反了。第一张图是遮挡正确,后一张是错误。
    下一期讲什么都好个人比较希望讲敌人的AI,以及多一下GML的内容。
    其实更贪心希望作者可以多更一些。

    • 青铜的幻想 2016-08-11

      @Cloudmy:感谢提醒,现在应该是正确的了。也感谢楼上的所有回复和支持。其实我一开始也觉得能够更新的更快一些,但后来实际做起来发现还是挺花时间的,目前暂时估计只能保证一周一更了。

    • ayame9joe 2016-08-11

      @青铜的幻想:这样细致的教程做起来肯定是会花费大量时间的,作者不必为更新速度担忧。

    • Cloudmy 2016-08-11

      @青铜的幻想:这里关于遮挡有一个小bug,如果沿着y轴长时间推着木桶移动,最后会造成遮挡不正确。
      个人认为可能是由于推着木桶速度变慢,理论位置得到的y值与实际不符,导致depth 显示错误。我尝试修复没有得到很好的解决办法。

    • 青铜的幻想 2016-08-18

      @wuyuchen88: @EternalNight: @Cloudmy:我感觉很可能你们三个人问的问题是相关的,但因为我这里并没法重现Cloudmy和EternalNight的问题,所以只是猜测。试试看把depth = -y改成depth = -phy_position_y,看这样能不能解决。我的理解是y是sprite的坐标,而phy_position_y是物理模拟环境内的坐标,GMS是在开启物理选项后,自动把y同步成phy_position_y的值。

    • allnan 2016-10-04

      @Cloudmy:木桶也需要设置一下step事件 。因为木桶的深度是初始的时候设置的,推动了以后深度不变,人的深度是变着的,所以会出现你说的问题。

    • fromjune 2017-01-13

      @allnan:非常感谢!加了step后就解决了!

  6. wuyuchen88 2016-08-17

    非常感谢青铜开这个系列教程,正是我苦苦寻找的。我有一些gamemaker8的经验,但对gamemaker studio不了解,正想进行学习。对于此篇中深度的设定我有一个疑问,为什么是depth = -y;而不是depth = -phy_position_y;二者有何区别,因为物理碰撞推开了木桶,y与phy_position_y是怎么变化的?请教了!谢谢!

  7. x360656112 2016-08-18

    感谢青铜大大的分享,我想知道如何做一款和Undertale相当的游戏,不知道里面的弹幕躲避如何用GMS实现呢。

    • 青铜的幻想 2016-08-18

      @x360656112:后面的教程会涉及到子弹的发射和与目标碰撞检测的内容,应该会和你想做的弹幕相关。

    • x360656112 2016-08-20

      @青铜的幻想:感谢回复,如果能通知子弹的轨迹什么的简直太酷炫了呢

  8. EternalNight 2016-08-18

    作者大大,我想问一下。根据您的教程来了一遍之后我发现当我用角色往上推桶子的时候如果推出去一段距离遮挡就又出现问题了,而且碰撞体积似乎也往上了。是怎么回事啊。个人猜想是不是碰撞体积的判定区域被推到了贴图上方啊?数据是没有问题的。

    • 青铜的幻想 2016-08-22

      @EternalNight:已经发你私信了,具体QQ聊

    • z549891325 2017-04-16

      @EternalNight:我也有遇到这样的问题,我把Y改成Y+6后解决了问题··

  9. JackyEdward 2016-08-24

    谢谢青铜大大!我是个小白啊,还有很多不太懂的地方,还请大大见谅Orz我现在有一个问题,就是控制人物行走的时候会走到窗口(也就是地图)之外,这个问题应该怎么解决呢?另外,教程中与墙的碰撞是不是一直也没有做呢?是在之后的章节中会做么?

    • 青铜的幻想 2016-08-25

      @JackyEdward:嗯,只演示了与上面那面墙的碰撞检测。更方便的办法是添加一个隐形的object,然后为这个object设置你想要的物理碰撞形状,就可以当作墙来阻挡人物走到外面了。

    • JackyEdward 2016-08-25

      @青铜的幻想:原来如此,谢谢大大ヾ(・ω・`。)

  10. Jeason1997 2016-08-28

    寻路跟攻击应该都属于AI的一部分,那我就贪心点,要AI的教程哈哈。
    感谢作者的更新。

  11. SandCas 2016-09-01

    看完之后一定要留名,感谢大大的教程。
    个人对攻击和技能方面的设计比较感兴趣,毕竟人物站在那只会跑跑跑的,也没啥意思嘛~

    这边工程出了个问题,可能是自己粗心导致的:
    角色在水池下的方时候(没有绕道水池后面去,但是人已经贴在水池下方了),角色还是被水池遮挡了。
    往回检查的时候发现spr_ysera的四个素材中心点都没动,改到脚下之后ok了,但是角色在移动的时候贴图会上下移,现在正在检查问题……
    然后检查完了,idle素材的中心点太靠上,粗心害死人啊。

    最近由 SandCas 修改于:2016-09-01 22:06:04
  12. OMFG 2016-09-02

    非常感谢作者大大写的这个教程,非常清晰实用。但是在过程遇到一个问题就是,当角色站在水池侧面的时候,角色的武器由于比人物长,是在水池的正面的。这样就感觉有点违反常理,但是如果判定让人物的武器抵住水池的侧边的话,也很奇怪。这里没办法发截图,用语言有点难描述,见谅~

    • 青铜的幻想 2016-09-02

      @OMFG:很多游戏都会有类似的问题,我觉得应该不用特别处理

    • OMFG 2016-09-03

      @青铜的幻想:哈哈。我之前玩很多游戏好像都没怎么注意到。人物的武器左右手的问题,我也是看你之前那篇文章说的才注意到的~

  13. doodle 2016-09-04

    感谢青铜大大!跟着教程一步一步走,每次都感受到了自己的进步0.0

  14. lzh175765 2016-09-05

    请问一下一旦桶被推离了原来的坐标遮挡关系就不正确了怎么办?准确说是y坐标改变之后。

    • 青铜的幻想 2016-09-06

      @lzh175765:木桶的深度值是在每一帧的Step里都会执行depth = -y; 所以应该始终是有正确的相对深度的

  15. x360656112 2016-09-08

    青铜您好,在我们执行depth=-y的时候,并没有告诉系统具体的y值是多少,为何伊瑟拉就直接被物体遮挡了呢。
    再次感谢您的教程。

    最近由 x360656112 修改于:2016-09-08 11:20:14
    • 青铜的幻想 2016-09-09

      @x360656112:y就是人物或者物品在垂直方向上的坐标值啊,就是他所在的高度

  16. skyswordkill 2016-09-11

    支持作者大大,还有这一章里,为什么人物能推动桶呢?是密度的原因吗?

  17. Canaan 2017-01-03

    感谢大大,很喜欢这样的教程。

  18. dingwuwo 2017-01-28

    非常感谢作者,有一个问题是,墙的碰撞遮挡不好(和其他的如水池等设置相同),调了半天,人物有一半会钻进柱子下面,其他地方却没问题,不知道怎么回事。

  19. tcdona 2017-01-29

    学到了太多,感谢!不知道这个和 cocos-2dx 比起来优劣点是啥,安装包应该要小点吧?

    最近由 tcdona 修改于:2017-01-29 22:01:19
  20. phoenizyb 2017-03-09

    请问如何做走在草上的减速效果,但是草不动

  21. Lucille 2017-04-03

    谢谢~用的是Game Maker Studio 2,有时候从桶旁边擦肩而过,但是桶会一起动但是离得很远动的比走的慢请问是为什么呢?~

    最近由 Lucille 修改于:2017-04-03 04:53:22
    • z549891325 2017-04-16

      @Lucille:我想有可能是你的obj_ysera的Physics的Modify Collision Shape没调整成只是人物的脚下,所以人物的碰撞面积就会比较大了。

  22. biibibibibi 2017-06-07

    非常感谢作者写出很棒的教程~

  23. eddie8285 2017-06-08

    谢谢大大,这些干货很有帮助。

  24. kongao0204 2017-06-16

    非常感谢青铜大大,日后如果GMS游戏在中国取得长足的发展,一定有大大的一份功劳

  25. Swanfal 2017-07-11

    碰撞目标这个错了吧,一个一个写岂不是要烦死……物理属性里面有一个Collision group就可以让一组物体互相之间有碰撞了啊。

  26. yoyomeng2463 2017-10-19

    too few vertices in polygon fixture
    这个怎么办。在 obj-player

  27. 凍鸡33 2018-04-21 微信会员

    感谢青铜大的分享与详细的带图教程。我想问一下,我移动Ysera木桶时遮挡上没有问题,但在走向血池等静态obj时候却只在下方时有遮挡,站上方时人物是在物体上面的。看了青铜大的动图却没有这回事。回头看了#3跟这篇好几遍还是不明白。

    • 青铜的幻想 2018-04-21

      @凍鸡33:你可以检查一下,1、静态物品的初始化是不是设置了深度值 2、这些静态物品的中心是设定在什么地方的,要保证当人物在它上方的时候,物品y值是大于人物y值的。

您需要登录或者注册后才能发表评论

登录/注册