添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Unity老游戏复刻计划之《雪人兄弟》(2)

Unity老游戏复刻计划之《雪人兄弟》(2)

(本文作者Khalil)

上篇传送门:

大家好,游戏开发新人Khalil又来了。

书接上文,我将上次未完成的工程进行了完善和优化,并在这期间又发现了更多新的细节。

先来说说上篇中曾讲到的,游戏角色控制中需要优化或实现的几个操作:

  1. 死亡;
  2. 从下方通过跳跃顶起雪球;
  3. 推动和踢走雪球;
  4. 站在雪球上踢走雪球(按下攻击键),或者被滚动中的雪球撞到,可以跟随雪球移动,在跟随雪球移动的期间为无敌状态;
  5. 在跟随雪球移动的期间按下跳跃键,即可脱离雪球,并进入一段时间的无敌状态。

我发现这里有一点理解错误的地方。其实在boss关中,角色被雪球撞到,并不会被雪球带着走,而是直接进入闪烁状态,它不同于吃了绿色药水的无敌状态(无视地形在地图上飞行,碰撞到的小怪直接死亡),而是一种不可选定,敌人和滚动雪球都不会碰到的状态。所以第四种操作应该改为:被滚动雪球撞到时,进入闪烁状态。死亡也不是一种“操作”,而是一个状态。

(1)那么死亡状态的实现非常简单,只要角色碰撞到小怪和Boss,就立刻去世(播放死亡动画,并将碰撞体关掉),在死亡动画的最后几帧内,添加一个移除gameObject的动画帧事件,就可以达到展示角色死亡状态的效果。

(2)接着是从下方顶起雪球的操作,这个操作我目前并未去实现,因为在实际游玩的过程中,这一关基本不会用到这一操作。并且我之前将雪球的质量设置得过大了,不管是理论上还是实际上,角色都无法通过刚体和碰撞体将雪球顶起,只能通过脚本来实现。感兴趣的朋友可以拿工程去试着实现,也欢迎在评论区进行讨论~

(3)接下来是推动和踢走雪球的优化,上一篇没有讲到踢走雪球的操作,是因为尚未确定实现它的方法。我发现之前我使用的方法有较大问题——我在角色脚本中写了PushBall方法,用于控制玩家踢走雪球和推动雪球。我最开始使用了碰撞体去检测雪球,但如果这样的话就会出现“就算雪球在角色右方,角色向左移动时也会进入推雪球状态”的情况。

在和皮皮关老师讨论过后,我改为了使用向玩家前方发射的射线去检测雪球,这样可以适当改变检测的范围,在检测到雪球的范围内,角色开始移动时,才会切换到推雪球的动画,使得只有当玩家面向雪球并开始推动雪球时,才会进入推雪球的状态。

在讲解踢走雪球的实现方法前,我先讲讲在优化过后,雪球脚本的变化。

雪球现在有五个状态:

三分之一雪球(1)

一半雪球(2)

完整雪球(3)

滚动中的雪球(4)

切换到小怪(5)

前三者状态的切换上一篇教程中已经简单讲解了,就是遭受到玩家攻击时(被玩家子弹打中时)就切换到下一个状态。而在观察后我发现,其实并非一受到攻击就会切换状态,雪球的hp其实也是有自己特殊的计算公式的。

角色在没有吃到蓝药水时,普通攻击有时需要多次才会使雪球切换状态,而有时又只需要一次。因为没有查到精确的hp计算方式,我姑且直接将雪球的hp设定在0,1,2之间切换。切换的方式为:

  1. 雪球初始状态为(1),初始hp为0。当受到攻击,切换到(2)状态时,将hp设定为1,将受到攻击时的时间保存为beFrozonTime;
  2. 当hp为1时受到攻击,就切换到(3)状态,将hp设定为2,将受到攻击时的时间保存为beFrozonTime;
  3. 当未受到攻击一段时间后(即Time.time过了beFronzonTime加上雪球需要保持状态的时间后),切换状态的顺序就变为逆序,完整雪球会切换到一半雪球,并且每次切换后,beFrozonTime刷新(重新记录为切换状态的时间,即不管状态怎么切换,beFrozonTime都会记录状态切换的时间);
  4. 只有(3)状态的雪球被玩家踢了之后,才会进入状态(4),由于在游戏中,雪球一旦开始滚动,它的结局就是消失,最终肯定是碰到Boss或是DeadZone,所以雪球一旦进入滚动状态,就不会切换到别的状态。
  5. 当雪球(1)变为小怪(5)时,自然是自身消失,然后将小怪释放(Instantiate)出来,所以进入这个状态后也不需要进行其他状态切换了。

理解了雪球状态切换的方式后,先实现让雪球滚动。我在雪球脚本中写了一个Roll方法,传入一个方向值来控制雪球的运动方向,改变雪球的标签和图层,以此实现一些特殊效果。

并在这个方法里改变雪球的状态,给予它运动的速度:

一开始自以为这样就OK了。但是后来发现有很大的问题:这个方法还不足以让雪球保持匀速运动,需要在update中给滚动的雪球持续不断的速度。所以我在状态(4)中也写了和Roll方法中改变物体速度语句一样的语句(因为状态机[switch]在update中),这样的话不难发现dir就获取不到了。很简单,再写一个公共的dir1,将dir1始终保持与dir一致即可,这样,雪球就会不停地匀速运动了。有了dir1之后雪球的转向就更简单了,雪球若是碰到了需要让其转向的物体,将dir1=-dir1即可。

然后就可以试着让玩家踢动雪球了。其实很简单,只要检测雪球的射线检测到了雪球,玩家就可以按下开火键将其踢走(调用雪球的Roll方法)。

另外在游戏中,不仅玩家可以使雪球滚动,滚动中的雪球也可以,若滚动中的雪球撞到了雪球,则被撞到的雪球也会开始滚动并变色,如果玩家使用的角色为Nick(蓝色服装的雪人)雪球就变为蓝色,若是Tom则雪球就变为红色。

实现这个效果其实也很简单。在雪球脚本中,如果滚动雪球撞到了其他的雪球,则调用被撞到的雪球的Roll方法即可,前进的方向dir就是撞到它的雪球移动的方向,而撞击其他雪球的那个雪球的移动方向又会改为反方向(dir1=-dir1)。

变色的流程也是一样的,只不过要多写一个玩家2的角色脚本。因为这个游戏本身就可以两个玩家游玩。在角色脚本1的PushBall方法中给雪球传入PlayerNum(玩家编号),编号为1,这个雪球撞击到的雪球都会变为蓝色;在角色脚本2中传入雪球的玩家编号为2,这个雪球撞击到的雪球就都会变为红色。

站在雪球上推动雪球,只要在脚底也添加一条射线,用于检测雪球即可,没有难度。并且在Boss关卡中,玩家不会被滚动中的雪球带着走,而且是改为直接进入闪烁状态,所以第五个操作也就不存在了。

现在是无敌(闪烁)状态的实现方法。本质也是改变图层,先设定一个bool变量isInvinsibility,控制玩家闪烁状态和普通状态的切换。如果角色站在雪球上踢走雪球或是被滚动中的雪球撞到,则它为true。再编写一个改变状态的方法,如果isInvinsibility为true则启用,记录启用时的时间invinsibilityTime,将角色的图层改变为小怪、Boss以及滚动中的雪球都不会与其发生碰撞的层级,并让角色闪烁(进行Renderer颜色的切换)。在update中编写:若过了闪烁的时间则变为普通状态,层级也改回普通状态下的“Player”。

这里颜色切换出了一个问题:闪烁状态结束时,有时颜色不会改回白色。虽不是啥大问题不过有点funny,有点老游戏bug内味儿了。

FC时代经常见到的图像bug。由于卡带载体的缘故,金手指的接触和晃动都会让图像出现奇异的问题

上篇提到但是没有做完的功能就讲完了,那么就到一些新的功能和优化了。

  1. 子弹优化
  2. 道具掉落
  3. 道具效果及加分条件
  4. 死亡后复活
  5. 计分板上展示玩家分数、玩家生命数、玩家游戏币数

到这个时间点我又回去游玩了原版游戏,想着有没有忽略的东西,果不其然,发现了不少细节:

首先,1p在推动雪球时,2p是碰不到1p在推动的雪球的,就好像1p和雪球融为一体了,作为上世纪90年代初的一款动作游戏,细节真的多到令人发指。不过现在我已经设定好了物体之间的图层和它们的切换方式,这点细节我就偷懒不实现了。真要做的话,例如可以让两个角色分为不同的图层,在玩家推动雪球时,再给雪球暂时改成另外一个玩家碰不到的图层。当然也可以有更高明的做法,欢迎讨论。

接着,角色子弹并不是一开始就会掉落,而是直线运动到一定距离后,才会下落,所以一开始先将其设定为Kinematic,在子弹脚本中编写,在子弹诞生后过段时间,刚体的isKinematic为false。

还有,雪球下落的时滚动出地面时,“飞行”的距离一般不会很长(在原版游戏中仿佛都是设定好的),所以雪球所受的重力要增加一些(可以在刚体中改变)。

雪球撞到不完整的雪球或者怪物时有概率掉落道具(加成道具“药水”或者加分道具):

并且撞到的越多,掉落的道具是药水和高分的道具的几率就会越高。这块属于战斗设计的细节,我尚未实现,只是简单的实现了道具的掉落,不过只要道具掉落实现了,改变概率啥的就是分分钟的事情(那你为什么不做!)。

道具的掉落很简单,先在雪球或者小怪的脚本中(其实只要在雪球中写就行了)定义一个int变量,在Start中取随机值,在未完成的雪球或者小怪被滚动中雪球撞到并消失时,如果这个变量的值在糖果区域内(比如0~60),就掉落糖果,在什么道具的取值区域内就掉落什么道具(其实只要在滚动中的雪球里面写就好了,如果撞到未完成的雪球或者小怪就掉道具,并且还可以记录撞到的是第几个,方便更改概率)。

虽然在实际攻略这个Boss关卡时,为了尽快通关,场景中往往不会累积多个怪物来让玩家获取道具,boss送出一个小怪后,玩家就会尽快将其变为雪球来攻击boss。但是为了尽量还原,我还是写出了捡到某些道具会出现的增益效果(糖果,黄红蓝药水)。首先创建所有道具的预制体,给它们赋予刚体和碰撞体,设置一个道具图层,使得所有角色内只有玩家角色可以接触到道具,并将其设置为触发器。不嫌麻烦的做法是,给每一个道具创建一个脚本,但其实没有必要,直接在角色脚本里就可以呈现出所有的效果,创建三个bool变量,用于控制角色是否加速(speedUp),力量是否提升(powerUp),攻击范围是否增加(rangeUp)。在OnTriggerEnter2D中写到:玩家接触到道具时,道具消失,并根据碰到的道具给玩家添加增益效果,比如“捡到”红药水,则speedUp为true,玩家切换到加速状态:

捡到加分道具,就会飘起来加分数,并在计分板上加上:

蓝药水的效果,就是让吃了蓝药水后改变子弹的预制体,使其变为大子弹即可(由于伤害一开始就没正确进行计算,所以这里的实现实际上也只是让子弹变大,并没有实际增加攻击力。不过这一块好改)。

黄药水的效果,就是延长子弹飞行和延后消失的时间,但是其实原版游戏中这个药水就没什么作用,它的效果我就懒得写了(懒狗)。

若玩家已经吃过了药水,下次捡到同种颜色的药水时,状态也不会进一步提升了,而是会加上2000分。若玩家使用普通攻击攻击了怪物、尚未完整的雪球或者是Boss,就会加10分,并且推动雪球会加500分,若是雪球撞到了怪物或是尚未完整的雪球,也会加500分,貌似雪球下落也会加10分,切换运动方向也会加10分。

上面是我观察到的游戏中的得分变动情况,不过我只写了攻击时的加分(懒狗*2),即在子弹脚本也添加playerNum中判断是谁发出的子弹,譬如属于玩家1的子弹碰到了可加分的角色,则玩家1加10分。

不过既然在说得分,那么也顺便说说计分板。计分板的实现那就灰常简单了,可以在很多游戏中通用。先在场景中创建画布,画布下创建Image,并设为黑色,做个黑板。在黑板下创建各个需要显示的内容:

从左至右分别是:

  1. 表示出下方这是1P的分数的1P字样,下面的0为1P目前的分数,我将它命名为1PScore;
  2. 代表下方是1P生命数的笑脸图片,下方的2为1P目前剩余的生命数;
  3. 中间的是最高分,谁的分数高中间就显示谁的分数(方便玩家们进行攀比,那会儿有街机血统的游戏,即使后来移植到家用机等其他平台了,往往也会保留有“Hiscore”这么个没什么卵用的系统);
  4. 2P字样和2P分数;
  5. 没有2P的生命数,因为我懒得做2P的复活效果了(想做的朋友直接在工程里复制粘贴1P复活方法的编写语句就行)

在名为1PScore和2PScore文本物体上,分别挂上Score1和Score2脚本,分别用于计算1p和2p的分数。两个脚本写的东西是一样的,只不过计算的分数是不同角色的分数罢了。先添加上using UnityEngine.UI,需要使用它里面的:Text,

通过改变Text的text的值,就可以改变UI展示的内容,再给它定义一个静态的int变量(score1P 或者是 score2P),用于表示玩家分数。在游戏开始时(即start中)将这个变量归零,

加分的实现之前就讲到了,若是分数变化,就改变score1P or score2P的值即可。

玩家生命数和游戏币数记录在WorldManager脚本中,先创建WorldManager空物体,挂上脚本。与玩家生命有关的逻辑如下:

玩家死亡后,生命数会减1,若生命数没有降到0以下,则玩家会直接复活;若生命值降到0之后,玩家死亡且剩余游戏币数不为0时,显示生命数的数字就会在0和剩余游戏币数之间闪烁,玩家按下A键(或者B键,忘记了)后,生命数就会变为2,并让玩家复活。

明白了逻辑就很简单了,创建分别代表玩家游戏币数、玩家生命数的变量,并将玩家生命数设为static,方便其他脚本调用(若玩家触发了死亡条件,则生命数-1)。创建玩家预制体,在此脚本中,若玩家死亡后可以复活,则在固定位置复活(Instantiate)玩家即可,记得复活的时候有段时间的无敌状态。这一部分没什么难度就不细说了,有疑问的朋友下载工程来看源码就万事OK啦。

到这里,《雪人兄弟》制作之旅暂时就告一段落了。虽然还是有很多细节没有实现(跳跃时,因为可以垂直从下方的地面跳跃到上方的地面,所以在跳跃期间CheckGround有时会检测到地板,造成玩家明明尚未落地却又可以跳跃了的情况),但至少在我看来,已经与原版十分相似了。

这次的制作耗费了很多时间,因为自己的技术尚不成熟,没有制定详尽的计划,而是想到什么做什么,并且在制作前没有把游戏游玩和观察透彻,导致了总是在修修补补,新功能的制作总是停滞不前,在下一次游戏的制作中,我会进行仔细地游玩与观察、指定详尽的计划后再开始行动,敬大家期待我进化后的作品~

工程链接: pan.baidu.com/s/1RPI5gV

提取码:bros

欢迎加入游戏开发群欢乐搅基:1082025059

编辑于 2022-11-01 16:05

文章被以下专栏收录