Unity老游戏复刻计划之《超级马里奥兄弟》(1)
(本文作者Khalil)
大家好我又来了。
超级玛丽是大多数8090、还有少数00年的朋友都耳熟能详的游戏。当然这个翻译来自于早年的港台盗版商,人家原名叫做《超级马里奥兄弟(Super Mario Bros)》,1985年由任天堂出品。

等等。
1985年什么概念?
我是肯定还没出生的了。
我的家乡居然都还没建省!(无奖竞猜:能猜到是哪个省吗?)
虽然严格说,本作并非马里奥的首次登场(在这之前还有不带“超级”俩字儿的《马里奥兄弟》,更之前还有虽然没上标题但担当实质主角的《大金刚》),但要论在游戏史上的贡献,本作的意义绝对非同寻常。

随便一列就能列出一大堆标志性特征:
划时代的横向卷轴推进方式;
极其良好的操作反馈和手感(物理引擎?なんだそりゃ);
前平后陡的难度曲线;
麻雀虽小五脏俱全的音乐音效设计;
异常丰富的隐藏要素;

如此等等。另外,和当年的大多数游戏一样,它是个轮换制的双人游戏——不能俩人同时玩、只能一个玩家“死”了之后换成另一个玩家。所以我小时候不喜欢这个游戏,因为我那会儿太菜了,第一关都过不去,只能看着我的小伙伴玩到通关,体验极差(喂

似乎铺垫多了点?
入正题入正题,讲讲我是怎么来尝试复刻的吧。
首先还是准备好素材,我的常规流程:
去素材网站上找到相应的素材,然后通过P图网站把需要的精灵图抠出来,在Unity中把需要的动画制作好。
素材准备好后,先铺设一部分地面让角色得以站立,然后把角色的脚本、碰撞体、刚体挂上。
接着——去玩原版游戏。反复玩。

当然可不是乱玩,这是在挖细节。复刻游戏,最重要的是当玩家一上手就会脱口而出:“有内味儿了”。所以为了尽量和原版游戏贴近,我要像QA一样去逐个测试游戏的功能,玩家做了什么之后游戏内会呈现出什么效果,然后通过文字、图片和录屏记录下来,反复琢磨。琢磨清楚了再上手实现。
角色左右移动和动画,通过改变localScale来实现人物的转向效果,这两点实在是简单得一匹就不多说了。
接下来是跳跃。原版马里奥得跳跃机制,是按的时间越长跳的越高,小小的跳跃有大大的区别。这也是装饰手感的重要环节。这个效果的实现不难,下面链接介绍了一个好办法:
blog.csdn.net/qq_62440805/article/details/124799615
文中写到,马里奥起跳和下落的速度不同,并且长按跳跃键能跳的更高,这个效果通过改变刚体的重力加速度即可实现。起初我合计:这代码刚好契合需求嘛!简直是打瞌睡送来了枕头,我就直接照抄了下来,调整了一下数值,跳跃的效果就和原版游戏很接近了。
但这时候我尚未理解其中含义,只是单纯的面向CV编程。马老师review了一下后,让我把游戏中的重力关了,免得影响代码的效果。由于我没完全理解代码具体的意思所以没get到马老师的意思,关掉重力人咋动啊?
后来还是在马老师的指导下优化了代码且理清了思路。上面文章中写的代码本质是在三种不同情况下改变物体所受的重力大小,从而形成不同的效果。在老师改进的代码中,在FixedUpdate中写到:下落时要加大所受重力(设重力gravityScale为3),使角色更快掉落,在按住跳跃键时,所受重力为1,松开时所受重力为2(也可能更追求细节的朋友觉得还不够贴合原版游戏,可以慢慢多调试):

跳跃的速度是不变的,则跳跃后所受重力越大,跳的就会越矮,下落得就会越快。在这里告诫自己也提醒大家,写代码的时候思路一定要灵动、清晰,不要死板,不要照抄没理解透的代码。
跳跃的动画比起其他游戏就简单了,由于下落滞空跳跃全是一个动作,所以只要角色不在地上,它就保持跳跃动作就ok了。

人物可以正常移动后就开始关卡搭建。
进入游戏,往前走几步会遇到”?”砖块,跳跃从下面将它顶起,它会弹出金币并且给我们加上200分,然后变为不会被顶起的空砖块。这个效果起初我是这样实现的:

这样做,效果虽然是有了,不过步骤太乱,十分麻烦,且代码死板笨拙。如果继续再往下做,要是之后这一块再出现问题,别说解决了,可能连问题从哪出来的都不知道。
马老师看了我的做法后提醒我,游戏就是造假的艺术。
确实,如果游戏全部做成”真的”,可能反而在游戏世界里展现出来的效果对于玩家来说却是很假、很怪的,所以为了游戏效果更“真实”,我们游戏制作者需要的不是去完全模仿真实的情况去实现功能,因为游戏本来就不是真实的,也永远做不到和现实完全一样,按现实中的思路做反而会失真,特别是我这样的做法复用性太差太麻烦、后续产生问题的话难以解决。我们需要的是开大脑洞,用“造假”来模拟出效果。
我之前的想法太死板,我想着既然这砖块要跳,那我就给它块地板让它跳就好了,但是我没考虑到原版游戏中,砖块仿佛就是世界中的固定存在,玩家做什么都无法影响它的存在,他就在那个固定的位置,无论你顶它多少次,它还是在那个位置——除了变大后的“超级马里奥”可以顶坏某些砖块。

仔细想想,如果它真的会被玩家顶起来,玩家还会被他挡住吗?玩家应该可以继续向上跳跃吧,但是在原版游戏中,就算砖块被顶起来了,玩家还是会被砖块挡住、不能继续向上跳了。如果按我的做法,玩家能给砖块施加力让它弹起,那么砖块也应该给玩家施加一个力让玩家马上下落,才能达到让玩家被砖块“挡住”的效果。
马老师提供了一种思路:砖块只需要有碰撞体,不需要刚体。没有刚体它就会悬在固定位置不会动,也就不需要考虑玩家受到砖块的力,突变为u砖块的碰撞体它就在那不动,玩家只能被挡住。
那么砖块被顶起来的效果怎么实现呢?
一个方法是只要移动贴图即可,通过协程实现。问号砖块被撞到变为空砖块后,贴图每帧移动一小段距离,一共四帧,这样就可以呈现出砖块被顶起然后下落的简单视觉效果了。
听起来很简单,我现在来看马老师帮忙写的示例代码也觉得很简单,不过对新手来说,做的时候极有可能想不到。就和看推理小说里的侦探破案一样,侦探拨开重重迷雾,向大家展示出了案件的原貌,但是最终案件结束,大家都置于案件外了,这时候明白了侦探的推理方法后,都会觉得“好简单,我好像也能做到”。其实难的不是拿到一个个线索后推理出案件的真相,而是找到线索的方法和正确的推理方式。通过老师提供的优秀思路来写出好的代码固然简单,难的是在没有老师帮助的情况下该如何写出好代码。
等等等等。为什么是“马老师帮忙写的代码”?

说来惭愧,因为我写的代码过于繁杂多余,马老师用他的方式来对我的代码进行了优化。

因为我的方法本来就是“急中生智”,思路没完全捋清,所以我对它的记忆现在几乎完全消散了,毕竟程序员昨天写好的代码今天都不一定看得懂(狗头)。
我就讲讲在以我的Bad方法为基底的情况下老师改良后的做法:
在这里需要注意的是,协程是由Bounce方法开启的,Bounce方法又是由砖块调用的,而砖块的BeHit方法又是由角色脚本通过射线检测调用的,层层嵌套。而射线检测是持续的,如果玩家跳起来之后射线持续接触到砖块,可能会开启多次协程,互相冲突后出现问题。这里我们可以给定砖块一个名为isUp的bool变量,初始值为false,在Bounce方法中:如果isUp为true,则return,若是砖块已经被顶起,则isUp为true,在JackUp中,贴图位置移动的两个循环都退出后,再将isUp恢复为false,这样Bounce和JackUp方法就不会在一次弹跳完成之前被重复调用了。


对比一下咱们能感觉出区别不算太大,有点“内味儿了”的感觉了。
由于进度和篇幅问题,本篇就讲到这(本篇没有工程)
下篇会讲到玩家吃蘑菇变形态、怪物的制作和生成的实现,大家敬请期待。
欢迎加入游戏开发群欢乐搅基:1082025059
对学习游戏开发、游戏制作感兴趣的童鞋,欢迎访问咱们的主页:http://levelpp.com/