01
数据结构设计是游戏设计的70%工作内容
这道理非常简单,因为游戏本身的运行原理,就是一系列逻辑系统在不断地改变游戏中运行的数据,最终当数据达到了一定情况的时候,一局游戏就结束了,游戏的结果(包括胜负、得分等等元素)也都在数据中了。这也正是EntityComponentSystem(简称ECS)这个逻辑框架之所以能走红的原因。ECS本身形式,就表现为System可以理解为游戏系统的逻辑,Component可以理解为单纯的数据结构,而Entity则是数据结构的集合,通过System对于带有特定Component的Entity进行处理从而完成整个游戏系的逻辑结构。(GDC上暴雪大佬发表了守望先锋的架构设计之后,ECS才进入了国人的视野,大多团队才开始逐渐重视起数据设计这个工作)为什么说“数据结构设计好了,游戏的设计就完成了70%了”?因为设计数据结构这件事情,不但是对于游戏玩法的一次精确地抽象设计,还是游戏所有扩展性创意灵感的根源。当然,我们并不是说所有数据结构的设计应该遵循ECS的思路,甚至绝大多数时候设计游戏的数据结构都和ECS无关,游戏的数据结构设计,本质上是对游戏元素的逻辑化归纳。接下来我们就用一个实际的例子来看这件问题——中国象棋游戏的数据结构:(截图来自《qq游戏中国象棋》)在中国象棋的运行过程中,存在2个核心的数据结构,也就是游戏的核心元素,他们分别是“棋子”和“棋局”:首先是棋子(ChessObj),棋子的数据结构中包括的属性有:图形(iconstring):这个棋子的贴图是什么,其实就是我们肉眼能看到的,比如“将”。阵营(side
number/byte):是红方还是黑方,这里之所以不用boolean,是因为也许扩展为3方甚至4方可能更好玩,我们并不在这一层去“约束创意”,留多一些可能性,这也正是“创意发展点”的根本所在——当策划去思考数据结构的时候,自然而然会多想一些玩法扩展,尽管这些扩展大多未必合适(玩法设计),但是他们一定是合理的(从逻辑上来说是对的,只是设计上是否pick这个idea的问题,至少绝不会是“天马行空的创意”)。行走规则(moveOffset
arrayarraypoint):以自身position为中心,取所有偏移单元格,作为这个棋子允许移动的单元格,即offset[落点信息][路径]。当策划设计到这里的时候,应该思考一个问题——中国象棋里的车,移动范围是左右各8格、上下各9格(因为一些格子不在棋盘内,因此依然不能移动到,这就是规则,也就是系统的约束)?这个问题其实很有意思,因为我们作为玩家的时候,一直根深蒂固的理解就是“车可以横竖走满”,但是从没有对这个定义进行过批判,而假如游戏被扩展为棋盘是18x20的时候,车的移动范围如何呢?还应该“走满”吗?这是一个非常有意思的思考,也是一个游戏策划设计游戏时候必须要做出的思考,因为这是一个游戏玩法平衡性的规则。而细心的朋友到这里可能发现了一个问题,为什么这采用的是二维数组?可以移动的单元格不应该一维point就足够了吗?只要列出所有能走的offset就行了?这里有一个细节,就是第一层array实际是指这个棋子有哪些可以走的路径,而第二维则是这个路径上的点,第二维最后一个元素,则有着这个棋子“落点”的性质,因为中国象棋里面有个“卡脚”的设计,我们看下图(图解1):(图解1)在“图解1”中,红色圈的象,原本的移动范围应该是3个红圈+1个紫色的圈,紫色的圈我们知道有了盟*的象所以就不能走了,但是其实左上角那个红色的圈依然无法走到,是因为象走路的规则中检查青色和蓝色的圈是否有子,有子(比如蓝色圈中有个将,不论敌我)他都不能走到,因此我们在数据结构中应该定义一个“路径”,路径中的任何一格有其他棋子都不能移动。而从这里,我们可以扩展出一个概念——如果我们把路径做了修改,他就能成为另一个棋子,比如我把所有青色或者蓝色的圈去掉,也就是最后结果是一个只有一单元(落点)的数组,那么象就变成了“飞象”。特殊规则(special
enum):行动的特殊规则,用于炮吃子和飞将等“不符合逻辑”的规则。炮吃子,即炮的所有行动规则中,如果落点有非本阵营棋子,那么路径上必须有且只有一个任意棋子;而飞将则是根据运行时的地图,动态判断,如果双方将之间没有任何棋子,那么行动方的将移动规则中将多出一条{对方将坐标}的数据。接下来就是棋局的设计,棋局肯定是一个存在的数据结构,因为一局游戏玩的就是这个(即游戏中核心的数据就是这个):棋子(chess
arrayarrayChessObj):实际上按照中国象棋标准棋盘来说,他是一个9x10的二维数组,每一个单元可能是null(没有棋子)或者1个棋子(ChessObj),和我们肉眼能看到的棋盘是对应的。但是我们在这里其实偷了一个懒,事实上即使双方对战的中国象棋,他的棋盘也应该是三维数组:Map[side][x][y],即各方阵内的5格高度才是真正的一个子棋盘,由规则(而非数据)建立起棋盘之间的“交通”,如“图解2”:(图解2)在图解2中,我们可以看到,每一家阵营是一个“子棋盘”他是一个9x5的数组,而对方阵营在双人象棋中是一个度旋转后的“子棋盘”,这意味着规则上我们必须建立在2个子棋盘之间的交通,比如位于map[0][2][4]的棋子,前进一格达到的是map[1][6][4],即map[toSide][width-this.x][this.y]的单元格;除此之外,由于棋子移动有方向性,比如“兵”只能向前,因此在对方棋盘,即第一维下标不等于棋子的“阵营”时,他的“行走规则”中的y向是乘以-1的。到这里可能有人要问了,为什么要自讨麻烦?明明二维数组就可以做好了的,其实这里不光有一个非双人象棋的问题:(在我小时候,玩过这么一个“三国象棋”)其实“三国象棋”本身是好玩的,因为热闹,而玩游戏,本质也并不是为了追求输赢,毕竟不是体育比赛象棋,玩,最重要的是开心,输赢,是带来开心的“标尺”。当然也不光是因为多方阵营的可能性,还有比如我们为中国象棋增加一个“技能玩法”,某个技能可以在2轮内将敌人的小兵变为自己的,那么这时候,这个小兵应该如何移动呢?因此真正的数据结构抽象,对于这种具有“双反阵营”概念的棋类游戏而言,他应该是3维数组,而非2维数组,同理《四国大战》等也是如此。行动方(side
number):现在是谁行动,正如我们说的,不光是三国象棋可能会有超过双方行动的时刻,为了好玩,我们可以让4个人走象棋,玩家1和3轮流控制一方,2和4轮流控制一方,行动顺序1黑2红3黑4红,2人同时控制一局也可以带出一些乐趣来,比如情侣象棋(当然现在的情侣多半不会土到去玩象棋就对了),这就是一个很好的拓展。而之所以没有回合这个概念,也正是“回合”是一个虚的概念,他是f(总的行动次数)可以算出的东西,所以不需要储存在数据结构里。到此,一个中国象棋的核心数据结构被设计出来了,而基于这些数据结构,也可以轻易就实现出象棋的玩法。同时在这个设计过程中,我们看到了许多“存疑点”和“可扩展点”。比如“车的移动真的是全屏吗?”,就像俄罗斯方块“旋转方块,真的是旋转90度,而不是切换成另一种组合了吗?L旋转一下一定不能变成正方形吗?”,这些批判性的存疑点,并不是用来批判老的规则设计的,而是给我们一些启迪,告诉我们还有什么新的扩展契机,和“可扩展点”比如“能不能设计一个buff,让我方2回合内所有的棋子都飞起来不受路径上棋子影响?”一样,任何扩展契机,都是一个逻辑十分完美的“创意”,但是这个“创意”本身是否是“好的创意”(合适现在的游戏)就要回到设计层深入思考一下。而看到这里的小伙伴也许还会问“中国象棋哪儿有这么麻烦的?我棋盘是一个arrayarrayint,负的代表红方,正的代表黑方,1代表将,2代表士……这样做不好吗?为什么要这么麻烦呢?”,其实这个想法也是对的,只是这就是“需求实现者(coder)”和玩家的理解,不是一个“游戏设计师(GameDesigner)”应有的理解,GameDesigner应该不放过游戏可能出现的创意。
02
游戏策划如何去从做数据结构设计游戏玩法
其实设计一个游戏本身是设计多个玩法,每一个玩法的设计,都是30%的UI和系统规则+70%的数据结构(事实上在设计数据结构的时候,会对规则有一次巩固和反思),所以一个玩法的设计过程,通常可以分为这样几个步骤,来做到用从数据结构出发设计玩法:01
用一句简练的话说清一个玩法
任何玩法,其实都是可以用一句、最多几句话就能说清楚的。只要首先去掉那些感动自己,或者感动自己心中用户的那些话术,比如“这样设计玩家就能产生心流”,“这个设计的目的是为了让玩家不会疲劳”,这些话正是因为听起来非常有意义,所以会让你陶醉其中,感觉这个设计完美无缺。但是这些话,在真正的游戏设计这件“冰冷”的事情面前,就是纯粹的浪费时间,毫无意义。你应该去掉一个“设计”中所有类似的语句,只留下清晰的逻辑性语句,比如:我们要做的是一个小镇经营的游戏,小镇上有几个建筑是玩家可以买下来的,买下后就会产钱给玩家。随着小镇等级的提高,每个建筑物会产生的产品变多,每个产品都会带来不同的产出。(经营小镇的游戏产品其实挺多的,但是每个产品的细节设计都不一样,因此这一句话要说清楚自身的特点,而不是简单地“抄谁”)这就是一句非常清晰的话,直观的就表达出了一个玩法,但是他并不是完整的设计,因为缺乏数据结构的设计,因此会缺失很多细节设计,由此会带来许多逻辑问题,因此我们接下来要:02
分清每一件事情,找到玩法和数据结构
从语文上来说,这句话说的是一件事情,但是从逻辑设计上来说,这句话里藏着几件事情,我们需要进一步的分析一下,这些事情分别是:我们经营的上有一些。本来就在那里,玩家要把它买下来变成自己的。本身不会产钱,产钱的是建筑物中的。的解锁和挂钩。到此,我们从这4件事情里面,就能分析出一个初步的数据结构:接着,我们就要针对这个数据结构和对应的玩法,来进一步深入思考,因为很显然这个结构反馈出的玩法里还漏了不少细节:先从产品出发:产出是单位时间产出吗?应该是的,那么单位时间是多久?整个游戏所有建筑物的产出都是一样的时间间隔吗?(在《芭芭农场》中,不同“建筑物”中不同“产品”的产出速度是不同的)如果是一个挂机游戏,是不是应该鼓励玩家经常上线,所以产出应该有一个时间上限的限制?然后再看建筑:建筑物的外观总是一尘不变的吗?建筑物的外观应该如何变化?除了本身变化规则会不会还有节日等影响?建筑物的名称会不会随着外观变化?(建筑外观随等级变化是经营类游戏常有的)如果产品有个产出上限,这个上限是否应该由建筑物承担?这个产出上限和什么属性有关吗?这个产出属性是否也可以升级?最后我们来看小镇:等级是如何提升的?当我们有了这些思考之后,会发现原来的一句话描述里,有太多还没说清楚的具体细节,然后这时候我们基于这些问题,又可以进一步修改数据结构,把它扩展为:经过一轮对这些事情的思考和批判,我们得出了初步的数据结构,但这还远远不够,接着我们要深入到做游戏的细节和游戏的一些可能会扩展的设计去进一步加工:03
批判每一个细节找到“隐藏属性”和解决“隐藏问题”
现在我们再来对于每一个元素的玩法进行一次更深入的思考和批判,这些思考与批判跟上一步的“补充”不太一样,更多需要从玩法设计的角度去分析和判断,而不再是从“常理”的角度看问题,我们依然针对每一个环节去分析:产品:产品是否存在季节性的产品?比如我们有个水果店,里面的一些水果是季节性的,比如草莓只有春末夏初才有。如果需要,那么产品属性下就应该有一个live属性,来表达当前产品是否激活中,而对于“大楼产出”这件事的运算又会有不同的算法,因为会