50万玩家等着和“鬼”交朋友!这个游戏里的 NPC,可不好糊弄了丨Created with Cocos
NPC 是模拟经营游戏中不可或缺的组成部分,但现如今绝大多数 NPC 依然是“工具人”式的存在,脸谱化严重。那么,如果一个游戏里的 NPC,有各自的作息习惯,会衡量生活的“幸福指数”,经历生老病死、死后甚至可能变成“鬼怪”,将会带来怎样一种游戏体验?
即将在年前正式上线的古风模拟经营手游《解忧小村落》(以下简称《解忧》)做出了自己的尝试。游戏由古德工作室基于 Cocos Creator 研发,过去一年先后进行了三轮测试,目前在 TapTap 上已有超50万玩家预约。
《解忧小村落》TapTap 预约开启:
https://www.taptap.cn/app/214345
《解忧》描绘了一个“人鬼共存”的世界。这里有历法、时辰、天气、昼夜,玩家将在这个拟真的自然世界里,作为村落的村长,同此地的居民和鬼怪一起,经历春耕夏耘、秋收冬藏,跟随四季和天气的变化合理开展种田、畜牧、建造工作,逐步打造一个心仪的世外桃源。
《解忧》依然保留了模拟经营的经典玩法——种植收获、采集建造、生产售卖等,而游戏最具差异的,莫过于其对 NPC(居民和鬼怪)的描绘。
每一位居民都有自己的职业,玩家可以“招募”这些居民,根据职业将他们派遣到合适的岗位上工作。游戏加入了“作息管理”,居民们会在不同时辰休息、工作、吃饭、散步、出游,玩家可以调整每一位居民的作息,甚至让他们“996”打工——但是,这些安排会影响居民的“健康值”和“幸福感”,若健康值过低,居民会生病乃至死亡,若幸福感过低,则可能导致居民搬离村落。
在这个游戏里,每位居民都是独立的个体,有各自的生活与经历,随时可能触发不同的事件。反之,也会有访客来到此地引起各种波澜,比如流窜的小偷盗窃居民财富、游历四方的郎中到访等……玩家将和居民一起去经历这些事件,并用自己的方式,应对某些棘手的局面。
鬼怪图鉴
鬼怪则是《解忧》中一个重要且特别的存在。他们看上去一点也不恐怖,甚至还萌萌的,虽然喜欢游走在村子里四处捣蛋,但又有各自独特且可爱的性格。鬼怪和居民一样,都是村落里的一份子,玩家可以“收服”和“招募”自己喜欢的鬼怪一同建设村落。
可以说,《解忧》始终围绕“佛系建设人鬼共存的和谐村落”展开,无论是删繁就简的游戏内容、清新可爱的画风,或是大批有血有肉的 NPC,游戏都在努力营造一种闲适、自由、真实的体验。
这样一款游戏是怎样被创作出来的?其中又蕴藏着哪些巧思与独特之处?C 姐和《解忧小村落》技术总监万骁聊了聊——
1
鬼怪:
“路见有鬼,但不必绕道”
《解忧》立项的契机是什么?
《解忧》的研发团队一共24人,成员们主要来自阿里游戏、百田等,平时都喜欢玩模拟经营游戏。有一次聊天聊到模拟经营的“模拟性”上,感慨现在这类游戏中的 NPC 还是工具人居多。所以我们就想,有没有可能让 NPC 更“动态”或者更“真实”呢?比如,他们是否能有更加复杂的情绪表达、变化的身体状况,会生老病死,甚至在死后成为“鬼怪”(一些中国特色)。
由此衍生了《代号:百鬼》的立项——也就是今天的《解忧》。当时我们计划用1年左右的时间做出这个模拟经营+鬼怪的游戏,现在看来,团队离目标越来越近了。
游戏的“鬼怪系统”在玩法上有哪些亮点?
目前的版本中共有53只鬼怪,之后还会陆续增加。在我们的设定中,鬼怪是人类在生前遭遇不幸后,因执念太深而化身为鬼。每只鬼怪都有自己的故事,科场鬼生前是个落榜多次的书生,墓鬼是个喜欢玩躲猫猫的小女孩……这些前尘往事,将随着“鬼怪传记”的逐步解锁徐徐呈现。
鬼怪们各有各的调皮,会给村落里的建筑、居民等造成影响,比如墓鬼无聊时会把居民抓来墓场同自己玩耍、碰上野鬼会让居民迷路,水鬼会影响渔夫钓鱼等。玩家可以“收服”自己喜欢的鬼怪,通过喂养鬼怪、给鬼怪送礼等方式来提升鬼怪的等级与好感度。等级越高,鬼怪的妖力值越高,工作产生的收益也越高;提升好感度,则可以解锁该鬼怪的传记故事、前生人物等。
在达成一定条件后,玩家可以“招募”鬼怪入住村落,并根据他们生前的职业与技能,派遣他们到不同建筑里工作。由于鬼怪的“特殊身份”,和居民不同,鬼怪不会疲惫劳累,可以不停地干活 (不是。
鬼怪形象都很 Q 萌,和大家分享一下美术设计思路吧。
游戏中所有的鬼怪都是能在古籍里找到原型的。我们查阅了大量的资料,夸张和放大鬼怪的特点,让鬼怪的形象更富记忆点。以“疫鬼”的设计为例,因为是和疾病有关的鬼怪,因此底色是苍白的;手持灯笼,象征其夜行性;符咒束缚和红衣,则是借鉴民间对瘟疫恶鬼的压制手段,代表疫鬼被人们惩戒和封印。
2
风格:
“真佛系,不上班”
除了鬼怪,《解忧》中还有大量的居民,这方面又是如何设计的?
《解忧》整体美术风格为古风 Q 版,我们想在此基础上,结合游戏基调,做出和市场上其他同类型游戏有所区别的美术风格。
居民的设计围绕着“模拟”这个关键词展开,力求真实感。游戏中,不同居民有各自的职业身份,基于此我们又在年龄、性别、体型三个维度做了区别。如上图,一个农夫职业有12个模型,为的就是让玩家感受到《解忧》世界里居民的多样与真实性。
《解忧》中有大量的建筑,怎么避免因建筑种类过多而显杂乱?
结合游戏内容,我们将建筑分为士、农、工、商、民宅、装饰建筑等大类,以中国古代建筑结构为基底,设计其共同的设计语言,包括墙体结构、屋顶造型与颜色、环境植物搭配等。这样既能保证建筑种类丰富,也能让玩家一眼看出建筑所属类型,方便游戏布局。
相对于大场景以绿色为主的主色调,建筑上我们有丰富的颜色类型,这样玩家在发展自己小村落的过程中,能慢慢感受到场景色彩的变化,由乡间林野变成繁华村落,从而获得成就感。
从美术风格到游戏内容,《解忧》给人的感觉都非常轻松治愈。
是的。当下很多模拟经营类游戏都会选择布局长线运营,但实际体验后发现,大多数长线游戏都让人“肝疼”。我们希望《解忧》能让玩家体验到更加纯粹的种田快乐,而不是在游戏里“赛博上班”。
《解忧》始终围绕着“佛系”这一体验去做设计,删繁就简。游戏中没有重复的每日任务,就是想减少玩家“在游戏里打工”的感觉,取而代之的是用“订单”模式,让玩家通过自主决策发展村落。玩家只需思考这个季节种什么或发展什么产业能增大收益,派遣居民和鬼怪去各个生产建筑劳作,在关键元素一步步铺设好后,就可以进入“看海”模式,去和居民、鬼怪交个朋友,看看他们的人生轨迹,探索村庄的发展变迁,或者是享受布置造景的成就感。
目前《解忧》中商业化内容非常少,团队这方面有什么规划吗?
因为是朝着“纯粹”这个基调去的,所以我们对商业上的内容一直很谨慎。目前游戏内已经开始有考虑商业化的内容了,不出意外主要有两个方向:其一是传统基建,前期侧重资源内购,后期再逐渐推出各种风格的建筑让玩家收集;其二是如果能做到的话,我们希望鬼怪本身的形象、情感、故事是可以立起来的,并与玩家产生更深层次的情感共鸣,玩家可以在这个基础上,为自己喜爱的鬼怪花钱进行培养。
3
技术:“优化不停”
能否分享一下游戏的客户端设计方案?
场景多纹理
场景的所有单位均为 Spine,游戏内场景有 2w+ 格子的地图大小,因此我们希望能让所有场景单位都能合批,减少开销。
我们制作了8、16的不同贴图 shader 来进行不同机型适配,再根据 MAX_TEXTURE_IMAGE_UNITS,来确定使用哪一套材质。
重写 MultiSpineAssemble。下面是 web 端的修改,因为要多传一个贴图索引,所以在 vbuf 也要相应的传入数据。
因为原生平台 fillBuffers 重写了,所以原生平台改动就非常多。
原生平台有双 color 、单 color 之分,所以需要在顶点宏这里多一套宏定义。
相应顶点结构体也要在增加贴图索引进去。
最重要的来了。MeshBuffer 需要在接口处区分是多纹理还是常规。
在 Types.h 里定义我们要使用的 shader 常量 ATTRIB_NAME_TEXTURE_IDX。
在 VertexFormat 内加上对应的多纹理顶点定义。
最后,在这几个类做好多纹理跟非多纹理判断和使用。
Label char 模式优化
这部分我们参考了论坛里“乐府-堂主”的实现方法:
https://forum.cocos.org/t/label-1-bitmap/97573
https://forum.cocos.org/t/label-2-char/97766
帖子提供了核心代码,但我们对其中一处进行了必要的修改。
替换旧字体时,一定要记得把 xAdvance 赋值。因为在做横向排版的时候是 xAdvance 进行排版,若按照帖子里的逻辑,旧字体始终要比新替换的字体大,不赋值的话会有间隔。
UI 动态合批
虽然 FairyGUI 是个非常不错的 UI 编辑器,但是在 Cocos 运行时效率确实不太好,同时动态生成的节点有些冗余,我们在做完场景优化完后才意识到 UI 优化一定要重视起来。
所以我们重写了 sprite、label 相关Assembler,目的还是动态合批(多纹理)。
我们准备了4组材质(8、16共享材质和8、16备选材质)。若参与 UI 合批的 sprite 的贴图超过了能合批的最大数量,就要使用备选材质不参与合批。
我们 UI 的 label 都是使用 char 模式,所以只要给共享材质默认设置到第0位置即可。
Sprite 的判断相对复杂点,需要先判断材质中1~8/16中,是否有设置过且不一样(设置完就只能用备选材质了)。
我们写一个 MultiUIBatch 的组件挂在 UI 的根节点上,用于创建共享材质、子节点动态添加、渲染监听刷新子渲染组件的贴图索引。
每个渲染组件(sprite、label)在 onload 都会挂在一个用于检测是否要刷新贴图索引的脚本。
该脚本会发送一个全局实践,用于给 MultiUIBatch 判断是不是自己的子渲染组件。
流程示意:
因为做好了超出共享材质贴图上限的处理,所以理论上应该没什么 bug 吧。
最后就是将 FairyGUI 里使用 Label 和 Sprite 的地方,换成重写的 MultiLabel 和 MultiSprite 即可。注意在 UI 销毁的时候做好材质回收。
理论上,如果 UI 都是图集并且图集复用率高的话,1个 UI 只有1个 DC。
游戏兼顾了高品质的渲染表现和流畅的运行体验,性能优化方面,有什么经验与心得吗?
ECS 模式
大场景多单位的游戏,如 RTS、SLG 等,一定要使用 ECS 模式。
《解忧》有 2w+ 格子,除开建筑是占位格子以外,NPC 是性能压力最大的一部分。我们使用了非严格意义上的 ECS 模式,System 与 ComponentBase(继承于 cc.Component)都有子组件(ChildCompBase),子组件负责的内容各不相同,我们在实现子组件的时候,都是基于通用扩展的目的去实现的,理论上只要给某个 System 添加寻路的 ChildCompBase,它就具备寻路行为;给 ComponentBase 添加 Spine 组件,就具备显示 Spine 的播放动作行为。
Node 的时候我们采用单帧动态,根据 FPS 改变创建数量,让 Node 从对象池添加到场景时更加平滑些。
《解忧》场景驱动逻辑
上图为游戏的场景驱动逻辑,其中黄色部分是性能优化集中的地方,因为使用 ECS 模式,所以会有部分 ChildCompBase 存在 update 的频繁调用情况,我们在处理方面会根据 fps 减少调用次数,判断 sys 是否在相机内并创建。
合理使用对象池
《解忧》自己实现了一套对象池,对象池内实现了对 Node、其他 Class 对象、RenderTexture 回收:
Node 回收:针对场景物件。
其他 Class 回收:针对System、ChildCompBase、行为树。
RenderTexture 回收:针对 RenderTexture 对象。
混合模式
游戏场景的单位都是 Spine。但在美术同学的设计中,夜晚部分建筑会在 Spine 加灯光效果(Add 混合模式),这样就会打断合批。好在白天的时长比晚上要长,美术同学在白天把叠加模式的 alpha 设置为0,所以我们在 spine 取插槽 Slot 的时候,判断 color.a = 0 就用 normal。
上图是我们使用 Spine 时的测试结果。场景建筑没有什么动画,所以使用 RealTime 即可满足需求(主要内存少),而 NPC 有大量的移动、表演及其他动作,如果采用 RealTIme 模式,性能确实堪忧。
如何处理大量的 NPC 行为逻辑?
《解忧》中 NPC 数量比较多,且有很多不同的行为,比如在主场景中 NPC 有派遣、散步等常规行为,而在酒馆、客栈中,行为就更多更复杂了。为此我们自己搭建了一个可视化的 behavior3web 编辑器,方便策划大佬们理顺一个行为是否合理。
游戏后续将重点优化哪些方面?
一个是针对逻辑耗时进行重点优化。目前渲染方面的优化效果还不错,但是因为场景太多单位要运算,所以逻辑耗时比渲染耗时要长,这是目前头疼的事情。
二是同屏单位控制问题,主要需要控制同屏 NPC 数量,或者采用 RenderTexture 把相同 NPC 的画面通过 FBO“复制”到 RenderTexture,并通过特定相机去渲染。
游戏内容方面,由于团队人力比较吃紧,我们在首发版本砍掉了鬼怪小游戏玩法,还有很多特别事件玩法想要增加,这些内容我们都将在后续版本中陆续提供给玩家。
点击文末【阅读原文】即可前往 TapTap 预约/体验《解忧小村落》。Cocos 将持续打磨技术,完善引擎功能特性,推进生态建设,助力更多开发者高效实现创意想法!