查看原文
其他

和学霸一起学 | 手把手教你做游戏玩法开发

腾讯游戏学堂 腾讯游戏学堂 2022-11-17



“和学霸一起学”栏目每周推送游戏相关的专业课程内容,通过相对专业、体系的知识内容,帮助大家提升对游戏的认识水平和理解力。本篇内容源于由清华大学美术学院与腾讯游戏学堂联合制作“游戏程序设计”系列课程,课程名称为《游戏玩法开发》(讲师:马若遥。文末还设有学习互动环节,欢迎参与赢取奖励!

#01

游戏玩法开发的定义


游戏玩法开发的定义比较模糊,目前在业界未有完全的定论,主要是指与玩家交互行为相关的游戏功能和行为的开发。



技术与设计的衔接


中国的游戏团队,在传统上认为游戏玩法开发是技术与设计的交叉点,是MMORPG(Massive Multiplayer Online Role-Playing Game,大型多人在线角色扮演游戏)时代的工作习惯导致的。在MMORPG时代,游戏玩法是以数值为核心,结合流水线思维开发的产物。由于我国工业化时间较短,艺术和文娱行业经历过断代,所以游戏开发参考工业发展历程,从而衍生流水线思维。


技术与设计的融合


而西方团队,一般认为游戏玩法开发是技术和设计的融合,其中融合部分更多的包括技术和设计部分。最初,因为西方游戏开发团队规模小,并没有游戏设计师的概念。所以在FC时代,甚至PS时代,很多游戏团队的设计工作,都是由程序员和美术师兼职完成。基于此,西方游戏团队更倾向于用创作者的思维进行游戏玩法开发。而且因为现代独立游戏的兴盛,西方很多人会对工业化流水线的流程反感。


技术、设计与艺术的统一


技术、设计与艺术的统一,是游戏开发的终极形态,少数顶尖开发者可以做到。这个要求其实并非严苛,因为游戏玩法开发团队需要作为核心部分,从确定愿景开始,参与游戏的原始设计以及游戏的开发周期,会衔接众多的生产环节。例如美术、声音,甚至是运营、市场等,这些环节都是与游戏玩法直接相关的。所以,游戏玩法开发需要一个真正的多面手来完成。



#02

建立愿景


建立愿景,是所有游戏项目开发过程的第一步,也是最重要的一步。无论是做单人开发者的简单小型独立游戏,还是成百上千人的大型项目,第一步都是需要有愿景。



愿景的定义


愿景是描述游戏最核心、最重要的部分。清晰的愿景是项目成功的保障,但问题在于,愿景本身可能比较模糊。所以,很多时候需要通过迭代、通过项目的进展来解决愿景模糊的问题,随着项目进展,愿景可能会随之变化。


很多项目会有一个负责人作为Vision Holder,例如著名的游戏制作人小岛秀夫,例如顽皮狗工作室的最后生还者,有一个比较清晰的制作人;但也有很多游戏的制作人并不明确,例如《使命召唤(Call of Duty)》。


一个游戏项目是否有一个人作为明确的Vision Holder,其实没有那么重要。因为愿景是否清晰的标准,在于执行者是否清晰。


另一方面,不是只有Creative Director才可以考虑愿景,愿景也不是仅存在于大型游戏项目中。我们做一个很小的东西也有愿景,例如子系统。 


传达愿景的方式


传达愿景的方式多种多样,不仅限于项目文档。国内团队,在很多情况下喜欢用Excel、Word、网页或长篇大论的设计文档来传达设计愿景,但如果执行者不看或者没有看懂文档,也是无意义的。


无论如何,评价设计愿景的标准依然是:执行者是否清晰。因此,愿景最重要的是存在于执行者的心里。我们应该运用表演、写、画、举例参考等一切途径去尽可能清晰地传达愿景。


传达愿景之后,需要传达者和执行者一起达到结论与共识。执行者通过结论与共识,才能最后执行这个过程,所以结论与共识也是在游戏中看到的最后状态。但是,在落实到实际工作中时,还会有不同的情况和变化。


三消愿景


下面通过三消游戏的实例,来讲解具体如何建立愿景。


在做三消游戏前,首先尝试建立愿景。按照标准的三消游戏,大致玩法如下:当砖块下落,玩家可以交换砖块的位置,三个颜色相同的砖块连在一起就会消失。


但清晰地传达愿景并不容易,可能仅通过描述游戏玩法和具体的流程图,也并不能明确愿景是什么。例如三消游戏,如果大家没有玩过,当看到游戏玩法描述和流程图时,只能想到俄罗斯方块游戏。



我们参照一个已经存在的三消游戏进行描述,或许会对三消游戏的愿景更加清晰一些。比如:


① 首先,解释这是一个N×M尺寸的、布满砖块的板子;


② 然后给三消和Match下定义。三个或三个以上同色砖块连成一线叫Match,形成Match的砖块会消除并加分;


③ 最后,阐释玩家的操作是可以交换相邻两砖块的位置来形成Match,消除后的空位,会由上方的砖块自动下落补充。


通过一个其它游戏的例子,可以更加明晰愿景。但明确愿景之后,它仍然只有大概轮廓,具体的细节仍不明确。


因此,下一步需要用到分治策略。分治策略是游戏玩法开发中比较重要的一个思维方式,比如编程中的很多算法的实现都会用到此策略。


分治策略


分治策略(Divide and Conquer),简单来讲就是将一个大的任务分解成几个小的任务,从而一个个去攻克。对于愿景来说,分治是按照个人或团队的工作习惯,将其分拆为易于实现的模块。


分治策略也可以用于分析成品游戏,例如大家比较熟悉的游戏《超级玛丽》:



根据上方的图片,我们使用分治策略,大概能拆出关卡、地形、主角、敌人、水管、金币、分数等元素。如果我们要做这个游戏,当使用拆分方法与团队的其他成员一起讨论时,可能会面临一些问题:例如


① 分数是应该算给主角还是敌人?


② 水管只是水管,还是算关卡或者地形的一部分?


③ 关于火球和飞锤,做飞行物的程序员会认为两者属于同一类,都是在做飞行物系统时所用的。但其他人可能认为,漂浮在空中的金币,也算一种飞行物。


其实,这些问题没有绝对的答案。新团队可能会因此陷入争吵,但并没有必要纠结这些,此阶段最重要的是往下推动进程。


三消拆解



仍然以三消游戏为例做三消游戏的拆解,其要素大致分为:世界、板子、砖块、计分板、菜单。


世界是整个游戏的容器,包括关卡、道具、玩家形象等;板子是每一个关卡的静态要素之和;砖块是画面中掉落和玩家会去拖动的东西,即画面中的动态要素。


但这样拆解后,则会出现一些问题和矛盾:例如


① 砖块会占据一个格子,如果砖块消除了,剩下的格子是什么呢?它可以算是板子的一部分吗?


② 假如游戏没有关卡,只有一关,板子和世界是一起的,还是独立的呢?


③ 假如游戏有胜利的规则,如何界定游戏胜利,是消除到达一定分数还是时间结束为零?胜利的规则是由世界管理还是由板子管理?


这些问题可以暂不做争论,我们介绍一个简单的原则:K.I.S.S.。


K.I.S.S.(Keep It Simple and Stupid)


K.I.S.S.,指的是Keep It Simple and Stupid(保持最简)。当有A、B、C三个可供选择的方案时,我们不确定A、B、C哪一个方案是好的,此时可以选择一个最简单的方案,先做最好做的方案。


虽然有人认为这种做法太缺乏计划性,但是在游戏项目,尤其是在玩法开发上,不确定性是非常突出的特点。游戏玩法开发并不是一个单纯的技术性工作,当出现不确定性时,团队可能会争论,甚至产生一种恐惧感。争论和恐惧感便会影响团队决策,进而导致项目无法推进并且按时完成,最终可能会导致游戏无法上线。很多时候,我们不知道外部环境会怎样变化,也不清楚现在对于未来思考的决策,将来是否能奏效,所以不如使用此原则:Keep It Simple and Stupid(保持最简)。


除了K.I.S.S.之外,在业界比较流行的概念,还包括:


① YAGNI-You ain’t gonna need it(忌引而不发),主要指我们为未来做的一些准备工作,如果现在不用,将来很有可能会用不上。所以,基于“忌引而不发”的原则,我们没有必要过多提前准备,可以等需要的时候再去做。


② If it ain’t broke don’t fix it(忌未雨绸缪),主要指做的内容有瑕疵时,先不用急着解决,待出现问题时再解决。



使用K.I.S.S.原则,对拆解的三消元素进行简化。例如如果板子不确定,可以先把它放到世界中,不单独列出来;砖块所占的格子不确定,也可以先放到世界中。这样便只剩下世界和砖块两部分内容。


世界是所有静态要素,砖块是所有动态要素。此时,很多不确定性问题都已解决,同时项目的Scope(边界)也会变得清晰。


#03

划定边界



对于一个项目来说,Scope(边界)是非常重要的一部分,因为Scope决定了我们需要的资源,包括时间、金钱等。


无论是一个人的独立游戏,还是一千人做的大型游戏,都需要确定Scope,Scope与Vision是两个最重要的事情。 



接下来,便可以开始实际动手写代码,这里展示的代码都是简单的伪代码而不是实际代码。



上图是砖块类别和World类的伪代码。


左侧代码,将砖块的类别按颜色进行定义。这里我们犯了“引而未发”的错误,我们假设将来有合成炸弹的机制,但在整个三消游戏完成之后,也没有用到。


世界类也比较简单,我们可以先用一个map映射,把它与实际的图像对应起来,然后再定义世界的大小,例如10×10,最后写一个相机定义分辨率。


在这里,介绍一个我个人的经验,即快速上屏原则:通过越少的工作量,尽早的在屏幕上看到雏形,项目就会推进得越顺利。因为我们在看到具象的内容之后,才能与别人开始讨论并且有目的地改进它,而不是纸上谈兵。代码本身也只是一种文档,它经过编译后无论是变成机器码还是什么形态,最终只有运行起来,才是我们要的结果。


#04

迭代


迭代的核心想法就是快速试错,面对设计的不确定性,我们应该去拥抱它。这与上文的K.I.S.S.原则也是一致旳。


因为直到游戏项目上线之前,游戏设计都会遇到很多不确定性因素。设计的不确定性,本质上是因为现有的信息不足以支撑决策。“想好就不要改了”的状态是很难实现的,市场一直在变,玩家一直在变,项目的状态也会一直变。



迭代循环


由设计出发,到实现、体验、反馈,最后再到设计形成一个循环,是一次迭代;多次循环此过程,就是迭代循环。


如果要进行快速试错,则需降低单次的循环成本。迭代,本质上是时间上的一种分治策略。每当完成一个阶段时,都需要反复问自己:What’s Next(下一步是什么)?其目的是推进项目的快速进行、顺利完成项目。


面向对象



面向对象,是程序设计中的一种把数据和逻辑打包的思想范式。


例如,皮卡丘这一个体,它对应的种类就是皮卡丘,有等级、生命值、攻击、闪避、进化等属性,把相关的代码打包在一起,构成一个类,这个类就是一个对象。把相关的代码集中管理,其目的是便于维护和重用。现代很多商业引擎便是如此,Unity和Unreal中,有Actor、GameObject等。它是把图像、模型、声音等所有元素都放在一起,把代码也放在其中,这就是一个对象。


对象是可以继承的。假如皮卡丘进化成了雷丘,雷丘的攻击等行为就可以直接继承皮卡丘的属性。


但面向对象并不是唯一的范式。例如《使命召唤》的引擎,不是C++,而是纯C语言。C语言则不是一个面向对象的语言,它的编辑器也不是面向对象的。



以上表为例,如果不从程序的角度看,它并不是一个面向对象的范式。游戏策划可能并不在乎每一个对象里面所包含的内容;他在乎的,是每一个对象里的几类重要内容以及它们之间的关系。


所以,设计不一定必须面向对象。很多时候,面向对象并不一定是最好的解决办法,大家要把正确的工具用在正确的地方。



砖块,是一个比较自然的、可重复出现的个体,而且我们关注它的值。所以我们先把砖块写成一个对象。如上图,代码上半部分是砖块的属性,下半部分是砖块的起点、终点和移动方向等。砖块的移动,则需用插值函数完成。


插值(Interpolation)



插值相关的术语有:Lerp(Linear Interpolation,线性插值)、Interp,、MapRange、 Bezier/Hermite curve(贝塞尔/埃尔米特曲线)等。


插值,简单讲就是一条直线或曲线,当X变化时Y发生相应的变化。它属于很基础的内容,但应用范围很广泛,在平常的工作中可以解决大部分的“过渡”问题。屏幕上大量炫酷的动画,都可以用插值来完成。如上图中的例子,艺术师就只做了几个关键帧,中间过程则由插值完成。无论是三十帧、六十帧还是九十帧,都可以看起来很平滑,这其实也是一种Divide And Conquer的概念。


玩家输入



关于砖块的交换行为,有两种设计思路:第一种,是玩家点击选中砖块,再选择相邻砖块,将二者互换;第二种,是玩家向某方向拖拽砖块,砖块与相邻的该方向砖块交换位置。


第一种设计比较容易实现,但将复杂性留给了玩家。如果是用手机玩此游戏,玩家可能会遇到点错、点不中或手滑的问题。


第二种设计比较复杂,因为玩家的手势判断很难确定。此时,我们可以做一个折中方案,在方案二的基础上进行简化。不做标准的拖拽动作,而改用摁下(MouseDown)和抬起(MouseUp),并明确边界:一共四个方向,只考虑从摁下去到抬起来的点,这个点落在哪个区间,砖块就与该区间的相邻砖块交换位置。这种折中的方案没有比第一种复杂太多,只多了一点数学计算,但是又大致实现了第二种思路。这里是KISS原则的一个体现。


砖块销毁



最后,我们需要做的是砖块的出生和销毁。销毁的实现,只需删除并加一个简单的特效就可以。实际的代码中,则需要处理一些引用关系,例如某个砖块删除后会留下空位,某个砖块有引用指向,另外一个砖块需要跟它交换,这些情况都需要进行处理。


假如我们做了一个很好的删除和播放的特效,但前提是砖块需要三消,必须要攒齐三个才能触发。此时,我们可以先暂时不用处理,可以先写一些临时的测试逻辑。三消游戏的情况比较简单,可能不需要写测试逻辑,但也会有其他更复杂的情况。


例如《绝地求生》游戏里有可以在山坡上开的摩托车,我们做好车之后去和团队交流,可能发现车做好了但是玩家部分还没做好。此时,虽然工作量很大,但一般情况下,我们也需要再写一个复杂的逻辑,来让玩家一进入到关卡,立刻就能开上摩托车。经常验证,也是一种分治的概念。它可以防止我们把所有东西都留在上线或验收之前一起验收,以防所有东西一起验证出现问题。


砖块下落



砖块消除后,相邻砖块会有下落的动作。砖块下落有两种处理方式:


空位上方的所有砖块共同下落、砖块一个个下落。共同下落的处理方式有些复杂,我们可以让砖块一个个下落补空位。其优点就是可以套用砖块的交换逻辑,不用再写复杂的下落逻辑,把上下砖块相互调换或调一调数值即可,下落效果还是不错的。但在其中可能会有特别多bug,例如某个黄色砖块落到它下面的格子,假如它落下的位置相邻两个格子都是黄色砖块,这行在下落的过程中、整个画面还没有稳定时,就直接消除了。因为消除的逻辑是在每一次下落以后检查Match Three,落一格检查一次,这时就会出现bug。


生成新砖块



如果有空格子,上面的砖块会落下,然后顶端会生成新的砖块一起落下来。把按照空位数量在顶端生成新砖块和砖块下落作为一个问题太复杂,我们可以简化成让砖块一个个生成,这样的处理方式还可以解决第一屏的问题。


第一屏问题,是指一开始屏幕里生成了所有的砖块后,有可能出现三个同色相连的砖块,但MatchThree程序是在砖块下落的时候才进行检测,所以已有的程序无法消除这三个砖块。但我们可以让砖块一排排生成,待顶端生成一排再下落,然后再生成一排下落,这样就可以解决此问题。


检测三消(MatchThree)



检测三消是触发砖块销毁的过程,此过程仅在砖块移动后发生,其实也是对算法的一种优化。此算法的运行代价比较昂贵,假如有一万个砖块,如果只能对砖块一个一个检测,这个检测算法可能需要运行千万次。在实际玩法中会经常遇到这种情况,所以需要想办法对其优化。优化也是一种分治策略,包括时间分治和空间分治。


优化检测属于在时间上分治,如果在特定事件发生以后再执行操作,就不会出现每一帧、每一个都要检测的情况,在时间上检测发生的几率也就小很多。


但我们做的三消检测仍是有一个bug:如果某个砖块下落,它右边相邻格子不同色,但左边相邻两个个格子同色,这种情况下不会发生消除。因为算法只检测下落砖块的上下左右四个方向,所以需要多检测几次。 


测试和除错



简单的测试代码有很多用处。


首先,可以在功能完整实现之前,消除一部分错误。这样可以避免所有错误集中到一起。


其次,我们可以有意地修改一些代码,帮助重现bug。因为有些bug比较特殊,只有在外部条件非常复杂时才会出现,但它出现的几率又不够小,可能是万分之一而不是百万分之一。当游戏中有百万玩家的时候,则会有很多玩家碰到此bug。此时,我们需要进行测试,在拥有充足信息的前提下,知道bug产生依赖的逻辑线,便可以进行修改,使程序只走这条逻辑线,从而帮助测试代码。


此外,还有一种更极端的方式,是有些游戏的客户端和服务器是分开的,客户端负责渲染,服务器给客户端发包指导客户端进行操作。假如客户端的逻辑是确定性的,即每次只要进行同样的输入,它会执行同样的输出。在这样的条件下,可以把外部环境录制下来。例如有些游戏中录像功能可以进行回放,假如在回放过程中渲染端出了任何的Bug,则都可以用比较高级的开发工具快速debug。


最后,有时测试甚至可以帮助迭代,加快个人的迭代速度。


虽然Log(屏幕消息)被认为是原始社会的产物,但它有很多好处,它与debugger是两种共存的元素。


debugger的优点在于,善于追踪更深一层的逻辑树,例如在一个点中看到的bug,实际逻辑不一定在这点发生了,可能是之前或之后的某一个数据出了问题。此时我们用Log(屏幕消息)debug会非常困难,需要加很多内容以后,才会可能看到某些有问题的数据,而且在使用Log的过程中,还会出现打得过多,找不到要点的情况,但debugger就可以很好地发现问题数据。


但Log也有自身的优势,它可以很清楚地看到bug,在大型团队里,非程序技术人员可以随时开关Log把bug显示在屏幕上,不用借助编辑器,便可以直接截图发给负责程序的人员,来简单快速地进行debug。此外,许多游戏发售之后,Log可能是唯一有效的执行手段。假如用户崩溃了,我们可以获取的只有一个Log。


辅助图形(Debug Draw),就是在屏幕上画一些条条框框,它与Log有相似之处。遇到bug直接截图,并将相关的Debug Draw一起发送给技术人员,技术人员就能直接明确问题出现在哪里。


Debug Draw也可以进行辅助开发。例如一个敌人AI,其构成复杂,可能是机器人、僵尸、动物和人类的结合体,它的行为也比较复杂,可以使用十八般兵器。这时敌人AI做了一个动作,Creative Director认为在这个时间点不应该做此动作,便需要程序员去找原因。此时,假如我们把AI各个系统里的参数、评估函数的结果等都画出来,输出到AI脑袋上方,我们或许就能明白动作的缘由。


断言(Assert),是在写一个函数时把值的预设提前写出来。例如值应该是在0到1之间,或者值不应该是负的,当把它写进程序时,如果程序崩溃出现问题,就会报错,之后便可以明确出错的问题。


但是程序崩溃也会造成新的问题,例如,正在做的编辑器希望设计师在文本框填一个0到1之间的数字,在写了断言后,设计师填上数字2之后编辑器直接崩溃了;如果在游戏逻辑中写了一个断言,别人做了改动后,游戏直接崩溃,最后会发现断言失去作用了。这种情况下,常用的处理方式是自己写一个断言库,出现断言时只是把Call Stack(调用堆栈)调出来,而编辑器不会崩溃。


#05

抛光Polish



游戏设计的最后一步是抛光,抛光是游戏从功能到艺术的过程。


游戏设计有一个MDA模型,指的是Mechanics, Dynamics, Aesthetics。例如,按下按键后角色走动,叫做Mechanics;如果角色身上着火,角色走动时把身边的树丛点着,不同系统之间引燃的交互,叫Dynamics(动态);Aesthetics是从玩家视角描述的,比如我们刚才说玩家的走动和着火,其实从开发视角来看,只是某个属性和特效从玩家转移到树丛上,而火/水的概念,就是Aesthetics的概念。


#06

开发者视角和玩家视角


开发者视角,从开发的角度看MDA,是以功能为基础,再进行系统之间的动态交互,最后才是美的层面。


玩家视角,首先接触到的是美学层面,然后再理解动态和功能,有一些玩家甚至不会理解功能。


所以,玩家与游戏开发者看到的质量是有所区别的。玩家会很在乎小数值的不平衡、bug、卡顿崩溃等质量问题。例如,《刺客信条:大革命》游戏,游戏完整地还原了巴黎圣母院。但当它刚发售时,玩家因为头发系统、脸系统等出现问题、则会因为这些小bug对游戏打出非常低的评分。但这些bug,对于开发者来说,可能感觉不是那么严重,只是一些显示错误而已。


抛光就是解决这个问题的。最重要的一点,在于开发者需要留出足够的时间。因为功能做完后,其实并不算将整个游戏做完,还需要做出游戏的艺术效果。


学习有奖!

我们将会在2021年9月13日17:00抽出2名幸运同学,分别送出UPUP牛生肖系列潮玩手办套装。参与方式如下:

①点击文末右下角的“在看”②评论30字以上的学习心得③发送关键词“打卡”至公众号后台完成资格验证

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存