在网易游戏的第三年——Jerish的2021总结

Posted Jerish_C

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在网易游戏的第三年——Jerish的2021总结相关的知识,希望对你有一定的参考价值。

这是【游戏开发那些事】第53篇原创

时光飞逝,2021年匆匆走过,不知不觉我的年终总结已经写到了第4篇。老规矩,先祝大家新年快乐、事事顺心。动笔前本想换一个题目,但思来想去还是直接复用了以往的标题做成一个“总结”系列,既可以见证自己在漫长岁月中的成长,也可以给大家一个纵向的参考和指引。

坦白讲,2021是我工作后成长与收获最大的一年,同时也是最为焦虑的一年。迅速成长得益于技术功底的不断扎实,对学习效率的正向反馈越来越强烈。但思考的深入、视野开阔之后对自身局限性的感知、工作强度的增大等也实实在在地增加了身体以及精神上的疲惫。

如果用一句话来总结2021,那就是 “拓宽行业视野,广泛涉猎知识” 。2020年我用了接近一半的业余时间研究游戏的网络同步并撰写了《细谈网络同步在游戏历史中的发展变化》系列文章,算是在细分领域深耕的一年。而今年我主要把精力用于自身对行业技术领域的拓展以及知识体系的查漏补缺。其实作为一名客户端,工作上有不少时间用于写逻辑和查Bug,这会在一定程度上导致自身的知识体系不够完善,迁移其他领域的能力比较弱,正如我在知乎上自嘲的 “不怎么懂服务器,对渲染也不算熟” 。估计不少游戏客户端开发的朋友都面临着类似的问题吧。

>>网络同步PDF部分目录截图

学习

因此,在巩固基础的同时(比如反复阅读《深入理解计算机系统》),我也开始制定计划快速丰富自己在服务器、客户端、引擎等各个方面的知识。针对服务器,首先是巩固Socekt编程。虽然之前有碎片化地学习过,但也属于一知半解,不够透彻。年初的时候,通过对书籍《TCP/IP网络编程》(unix网络编程可能更好,但是比较厚)以及博客、Linux内核源码等资料相对系统地学习,算是真正捋清了阻塞/非阻塞、同步/异步(注意:异步IO和异步不是一个概念)、IO多路复用、Reactor/Proactor等概念和原理。随后又通过《网络是怎样连接的》(网络通信科普),重新捡了捡之前遗忘的计算机网络基础知识。

年中的时候我的公众号(“游戏开发那些事”)曾转载过《一篇文章读懂守望先锋外挂的技术原理》一文。其实转载前是打算好好研究一下汇编、逆向、反外挂等相关技术的,也在大佬的推荐下下载了《网络游戏安全揭密》《游戏外挂攻防艺术》《黑客反汇编揭秘》等电子书。但看了几章之后,由于时间安排以及计划优先级的问题,就暂时放弃了。不过通过简单的学习也大概了解了常见外挂和反外挂的基本原理(加壳/脱壳、加密/解密、调试/反调试),理解了“外挂无穷无尽,无法根除”的缘由:

  • 游戏只是一个应用,他本身无法跨越自身领域/权限去解决一个不受它控制的问题。

  • 任何技术都是双刃剑,如果它能被广泛用于反外挂,也就一定能用于外挂的制作上,根除外挂可能就是一个永远无解的伪命题。

此外,推荐大家有空可以多了解一些汇编知识(不限于某个具体的岗位),这可以让我们在调试某些疑难杂症时有更多的思路。

>>几种常见外挂的原理示意图

对逆向学习暂时搁置,是因为我将更多的时间和精力花在了啃“渲染”这块硬骨头上。渲染相关的入门教程可以说是非常之多了,不过我还是首先推荐闫老师的视频——《Games101》(B站播放量已达107万)。看完视频大概花了我一个月左右的业余时间,再加上对其他辅助资料的学习,渲染领域的常见概念和原理基本就都熟悉了。说实话,如果网课都能达到这个水平(Games103 / 201/ 202/ 203都已经发布了,可以慢慢追),我觉得很多知识/技术的入门可以不用一页一页地翻书了,文档和书籍用于查漏补缺就好。另外如果理论学完了想用DirectX实践一把,可以关注一下X_Jun大佬的网站,源码和各种细节讲解都很清晰。https://www.cnblogs.com/X-Jun/p/9028764.html

补充:知乎上还有几位大佬发布了从零开始搭建引擎的系列文章(最早出现的应该是陈文礼大佬的《从零开始手敲搭建次世代游戏引擎》,已连载80多篇),如果能跟完这个系列一定会飞速成长。


>>GAMES课程列表

此外,我也花了一些时间在AI、动画、Gameplay、LockFree编程、C++模板等的学习和深入上,看了《游戏人工智能编程案例精粹》《Character Animation With Direct3D》《腾讯游戏开发精粹》《通关!游戏设计之道》,也参考了不少资料并梳理了AI寻路/逻辑框架、动画系统框架、Gameplay框架以及一些游戏开发的制作流程和规范等。

工作

Gameplay:

在前两年关于工作的总结中,我分别谈到了游戏项目中CI/CD(或者说工具链)的重要性和Gameplay/3C的复杂性,今年我想把重点放在Gameplay上,在更高的层面上讨论其框架的设计与实现。之前我将网络同步作为一个完全独立的系统讨论,但实际上,AI模块、网络同步、动画、3C、技能系统之间都有着非常紧密的联系,当对各个独立的系统有了一定的认识之后,我非常建议大家对整体框架做一个梳理,这样才能更深层次地理解Gameplay开发。

>>看似普通的移动射击就包含了AI、动画、3C、同步等多个Gameplay模块

拿守望先锋来说,2017年GDC上关于守望先锋的技术分享一共有三个,分别是网络同步、脚本系统以及回放系统。第一次看守望先锋视频的时候,我的关注点基本都在网络同步上面,但随着理解的深入和实践的增加才逐渐意识到同步框架对可视化脚本系统的高度依赖,又或者说在客户端开发中,其实Gameplay逻辑框架(3C逻辑框架)才是游戏系统的核心,这对顺利搭建其他的子系统或关联系统至关重要。举个例子,假如我问你“网络同步中的快照到底是什么或者守望先锋里面的快照都包含哪些?”只看过“'Overwatch' Gameplay Architecture and Netcode”这个分享的朋友估计是很难回答清楚的(答案在脚本系统的视频里)。因此我们在接触一个新项目时最好先捋清整个游戏的逻辑框架,然后发散地找到各个系统之间的关系,这样会帮助你更好地理解整个项目的Gameplay逻辑。

>>守望先锋的三个GDC分享

说到这里,我好像还没有讲清楚Gameplay逻辑框架到底是什么?概括来讲,大部分游戏都存在一个核心玩法,所以任何游戏都一定有一个逻辑驱动框架代表着玩家的输入行为是如何驱动游戏角色或者游戏世界发生变化的,这就是对游戏Gameplay框架的简单理解。从代码设计上来看,一般就是指状态机/行为树/流程图 + 数据 + Gameplay实现相关的各种类(如果是面向对象设计)。

接下来就展开描述一下状态机、行为树、流程图的价值和应用场景,这也是我今年最对各种Gameplay系统的学习和设计实现过程中最大的收获。这些东西对复杂的系统开发非常有意义,而且是可以复用在多个模块上的(AI、动画、技能系统等)。按照我个人的理解,游戏AI、Gameplay里面技能、角色行为的实现,本质上都是一个数据改变驱动状态切换的设计问题。面对这种问题,通常第一反应就是实现一个状态机去维护,对于不是太复杂的游戏,效果还是很不错的。然而不断增加的状态和无穷无尽的切换需要我们设置大量的条件来判断状态迁移的合理性,这非常容易使开发者被绕晕而忽略掉各种细节,并且回溯执行的过程也会变得非常麻烦,更不用说可视化调试。因此分层状态机又被引入用于分隔状态,它把各种状态进行整合和分类,从而减少状态之间的切换路径,一定程度上缓解了复杂状态关系带来的各种问题。

>>状态机与分层状态机

然而这种优化治标不治本,能不能换个角度去思考解决方案呢?在AI的设计领域中,这个更优解就是行为树。我们在数据结构中开始使用树结构的时候就是为了解决搜索的效率问题,它能将复杂度相对均匀地平铺到各个层上。就大部分游戏的复杂度而言,基本上不会有复杂到十层以上的逻辑节点(即使有也可以通过拆分子树来解决)。行为树,就是基于树结构对逻辑流进行跟踪,类似函数调用,将行为按照逻辑划分为层。相比较而言,状态机的维护类似于函数里面加的各种goto,调用堆栈很容易变得复杂混乱。

换个角度来看,其实行为树每一帧的执行流,也可以看做是一个状态,这个状态(可以用作网络同步中的快照)就是当前所有点的激活情况以及行为树当前用到的所有数据变量。原来我们在状态机里每个时刻只有一个状态,但是逻辑转换关系却非常复杂(同一帧你要考虑当前状态与其他所有状态的切换条件是否满足),而行为树把逻辑转换关系换成了逻辑流,一条逻辑流可以很容易去定位和分析调用流程(如下图我们可以很直观的看到哪些条件被满足,逻辑流在哪条子树上。最关键的,我们每一帧都可以按照逻辑流从头开始分析,不需要像状态机那样无穷无尽地跟踪上一个状态的切换原因。

>>状态机与行为树的粗略对比

总的来说,我认为AI里的行为树比状态机应用更为广泛的原因主要有以下几点:

  1. 复杂度上,行为树的逻辑流与时间无关,聚焦于当前帧可以避免复杂的回溯追踪

  2. 对开发者来说,可以将关注点从繁复的状态切换中解放出来,从而转向更直观的数据流(原来平铺到单层的转换关系被分散到不同的子树里)

  3. 从逻辑上来讲,与游戏角色解耦,不像状态机需要把所有状态都绑定在玩家角色身上

  4. 使用上,行为树的可视化调试更方便,交给策划也可以相对较好的维护

>>UE4里面的行为树

当然,行为树适合AI并不一定适合所有的Gameplay模块,比如很多MMO游戏里技能系统的实现是一个相对简单的流程,而不是一个复杂的逻辑流。常见技能的构成和释放过程如下

技能组成:

技能 = 动作 + 特效 + 技能区域 + 子弹

释放过程:

前幺时间(动画+特效)-> 技能释放(动画+特效)->发射子弹(可选)->目标位置产生技能区域(特效)

>>一个技能的流程描述(虚线内容是可选流程)

这种需求使用简单的流程图就可以完成。流程图本质上和行为树都是树形结构,两者在驱动逻辑流运行的层面上是一致的,差异主要在于行为树有着特定的驱动逻辑节点,比如Sequence、Selector,以及在叶子节点才能真正执行的Task。当然流程图也可以被设计的很复杂来满足项目的特定需求,毕竟从某种层面上说,行为树其实是流程图的子集

>>一种广泛意义上的流程图与行为树对比

提到技能设计,还要补充一下。由于技能的维度比较多且多变,非常不适合用继承来实现,知乎上有一篇回答就非常深刻地讨论了继承在游戏设计中的问题(见段尾)。针对这种问题比较合适的方案是表格化,即把所有的技能释放归一化为一个接口调用,然后使用类似上面流程图的方式逐个地去触发组合在上面的功能。此外表格关系更容易维护和拓展,做布局和公式化也很方便,策划喜欢用Excel不无道理。基于此,在面对策划需求时,程序需要尽量认清各个对象间关系频繁变化的可能性,避免不加思考就写一堆枚举值,结果被策划多变的需求搞崩

https://www.zhihu.com/question/20275578/answer/26577791 面向对象编程的弊端是什么?

前面简单谈了谈Gameplay系统设计的常见方法状态机、行为树和流程图,但实际上,我们使用Gameplay的时候,最大的难点就是如何找到一个合适的框架方案去解决目前项目复杂多变的需求

那么面对一个复杂且无从下手的系统要怎么做呢?首先可以通过查阅一些相关资料,总结分析别人的踩坑经验看是否能应用到我们的方案设计中。其次,将问题仔细拆解。举个例子,大家刚入行的时候面对的问题基本上都是“这里加个按钮返回上个菜单“ “让这个角色受伤后不能移动”等被策划拆分出来的具体问题,后来我们开始面对“设计一个Buff系统” “改善网络框架”等一些模糊的需求。其实这些都可以拆成一条条细分的需求,也许拆解过程中会出现很多的需求冲突和难以解决的问题,但随着不断的完善和调整,最后一定能梳理出一个合适的方案。

项目优化

除了上面提到的对Gameplay框架的学习与收获,我在项目优化方面也有一些新的经验和认识。伴随着功能的迭代和代码量的增加,由于代码/工具用法不正确、不规范、考虑不全面等引入各种Bug和性能问题是无法避免的。

>>UnrealInsight性能分析Timeline

那么,当一个项目进入到后期的时候,我们都可以做哪些方面的优化呢?

  • 网络通信优化

通常指流量、同步、弱网表现等(先不考虑服务器高并发架构设计),网络通信一般可以通过降低发送频率、避免重复发送、重复序列化和拷贝、频繁使用临时变量(可以使用一个全局buffer来解决)、尽量合并小的包(注意不要超过MTU),压缩后再发送等手段进行优化。

  • 内存优化

包括避免无关资源的引用和加载、避免辅助类对象直接继承自引擎的基类、避免对象频繁的创建与删除(可以使用对象池、内存池处理)、尽量使用图集合并工具处理图片、对动画资源进行压缩、对客户端与服务器的资源进行分离和加载等。

  • 发热功耗优化

机器发热/功耗较高通常是渲染时带宽占用过大导致的,因此要避免对精度要求不高的效果逐像素处理、避免不必要的渲染Pass、尽量复用现存Pass、尽量减少半透明/后处理等

  • 代码逻辑优化/体验优化(主要指各种Bug修复,这里不赘述)

  • 帧数优化(CPU、GPU)

包括避免非必要的Tick、避免频繁的对象遍历、避免频繁的GC、避免一帧执行重复的逻辑、尽快销毁无用的对象(这样可以加快遍历速度)、同步改为异步(比如资源加载)、UI DrawCall合并、场景对象合批/Instance、使用Hash代替字符串匹配、脚本蓝图Tick移植到C++等。

关于帧数优化,再补充一些。通常项目帧率的下降有两种情况,一种是由于代码逻辑不合理或不规范,另一种是单纯的性能的问题。比如使用Tick来代替Timer就是代码逻辑问题,而引擎GC导致的卡顿就是需要优化的性能问题。一般来说,导致帧率下降的原因是很难直接定位到的,所以一定要借助工具来分析,比如虚幻自带的UnrealInsight、截帧用的RenderDoc、Mac自带的XCode、高通的Snapdragon profiler以及Intel的Vtune等。对着采集结果,一帧一帧地打标签和分析,总能抠出点细节,尤其是那些瞬时产生的调用高峰。然而对于那些隐藏在每一帧里的消耗,经验就显得尤为重要,经验丰富的程序对某个地方的逻辑用时应该是多少毫秒一般是很敏感的。还有一些问题比较隐晦,这时就必须通过添加更细致的标签来分析。比如很多代码会在一帧里面被调用N次(UE引擎本身就有不少这种代码),但实际上正确的做法是在这一帧的最后执行一次该逻辑就可以了。(ECS框架就可以很好地避免这个问题

>>RenderDoc截帧示意图

总的来说,优化的前提是理解功能和需求,另外还需要敏锐的观察力、思考能力和丰富的经验,才能很快发现项目中那些容易被忽略的细节。其实所谓优化,本身就是一个相对的概念,理论上是无穷无尽的,因此最好要有明确的优化目标。其次项目在不同的阶段面临的情况和需要解决的核心问题都不同(比如前期应该集中精力于框架搭建上),过早优化是万恶之源。

自媒体

在整理公众号时发现今年的更新比去年还要少,实在惭愧,因为今年确实把更多的精力用在了工作以及个人学习上面,输出不够。不过有几篇原创的阅读量还是不错的,包括

也转载了不少大佬的文章如

目前公众号有52篇原创,涉及到游戏科普、游戏开发、C++、面试总结、对行业的思考和总结等,朋友们还有哪些想让我分享的,可以在评论区里留言或者私信我,我会尽力把公众号一直运营下去的。

心态

坦白来说,这一年的成长和收获其实已经很多了,那我的焦虑从何而来呢?首先,今年已经是我正式工作的第五年了。参照行业标准,我应该已经是一个经验丰富的资深工程师了。但实事求是地说,我仍然只在某些非常细分的技术领域有一定的见解,很难说有什么一技之长或不可替代性。

其次,知识是会遗忘的,年初学的内容到年底可能就忘记了一大半,觉得知识好像没有学进去或者没有学透(事实确实如此,毕竟很多内容只是浅尝辄止...),所以我总想不断地花时间去巩固,但业余时间又是有限的,就很容易陷入矛盾和焦虑之中。我也一直想通过记笔记,画图,对知识进行梳理和关联的方法去解决,但实际上,实践才能出真知,除了保持持续学习的习惯,还是要投入更多的时间到动手操作上去。

我们常说实践是检验真理的唯一标准,根本原因在于书籍中很少涉及细枝末节的点,而这些点在实际操作时又不得不面对,所以建议大家学完理论后尽量去尝试动手实现。

另外还有一件事不得不提,就是在2021年底毛大离开了我们,也彻底地离开了游戏行业。这件事对不少业内朋友来说都是不小的打击,虽说抑郁症可能是导致最终悲剧的直接原因,但积劳成疾并非危言耸听,身体健康必须要摆在最重要的位置。而且身处这个行业,需要我们终身保持学习的习惯,很多优秀的人更是对自己要求极为严格,很容易因为工作或生活上的一些不顺陷入悲观的情绪之中。实际上,认清自己的实力,客观面对眼前的问题是非常重要的,我就是通过这种心态上的调整,才一定程度上改善了自己焦虑的状态。

未来的一年,希望工作上能继续突破,生活上保持健康的身心状态,多看几本书的同时也能多通关几款游戏吧,毕竟今年只通关了《伊苏8》,《双人成行》也才玩了一半。

最后再次祝大家新的一年,健康快乐,事事顺心~

 往期文章推荐

- 点在看或分享支持一下吧 -

游戏开发技术系列【想做游戏开发,我应该会点啥?】

虚幻引擎技术系列【使用虚幻引擎4年,我想再谈谈他的网络架构】

游戏科普系列【盘点游戏中那些“欺骗玩家眼睛的开发技巧”】

C++面试系列【史上最全的C++/游戏开发面试经验总结】

我是Jerish,网易游戏工程师,5年从业经验。该公众号会定期输出技术干货和游戏科普的文章,关注我回复关键字可以获取游戏开发、操作系统、面试、C++、游戏设计等相关书籍和参考资料。

以上是关于在网易游戏的第三年——Jerish的2021总结的主要内容,如果未能解决你的问题,请参考以下文章

在网易游戏的第三年——Jerish的2021总结

在网易游戏的第二年——Jerish的2020总结

在网易游戏的第四年——Jerish的2022总结

在网易游戏的第四年——Jerish的2022总结

我和编程在一起的第三年:Android总结

[年中总结]写在毕业的第三年