虚幻引擎的蓝图可视化脚本十分有名,甚至可以只使用蓝图就能制作出一款游戏。但随着项目体量越来越大,蓝图里面的线可能会盘得像意面一样,代码复杂性会越来越高。我们该如何应对这种情况?这便是本文我们要分享给大家的内容。
本文出自:https://www.youtube.com/watch?v=1SH9DBlfE4I,由Valentin Galea分享。目录
4.1. 循环复杂度(Cyclomatic Complexity) 4.2. 霍尔斯特德复杂度(Halstead Complexity)五、可维护性(Maintainability Index)一、什么容易出问题
在虚幻引擎中,有两部分的复杂程度会逐渐失控,一个是蓝图的可视化界面,一个是资产之间的引用与依赖。但我们后面重点放在蓝图上,依赖链是蓝图编程中会导致的一部分问题。蓝图作为可视化脚本编辑工具,如果逻辑很糟糕,用了很多Cast和GetAllActors之类的节点,就会导致游戏性能表现不佳。如果蓝图排版非常乱,就很难读懂逻辑的流动,会变得很难理解,导致维护与更新困难。资产引用如果不稍加注意,就有可能一个引用另一个,一个依赖另一个,导致这条依赖链越来越长,越来越难以梳理。看看这坨意面怪,血压是不是上来了?在虚幻引擎中节点之间的连接,各种线条很容易形成意面怪物。就算您自己写了一段逻辑,过了几个月回来再翻看那段逻辑,您会怀疑这段到底是不是您自己写的。甚至还有一个网站tumblr.com/blueprintsfromhell,上面大多数都是用户贴出来的意面怪物。如果您不想盘出意面怪物,就得继续留意以下的内容了。二、依赖链
依赖链条可能会导致一些加载与内存方面的问题,特别是当您的某个类有一长串依赖链的话,就需要非常长的时间去加载这些依赖的资产,占用较大的运行时内存。如果我们在蓝图中使用Cast,就会将这两个类连接在一起,这是一种硬引用,相当于你在加载这个蓝图资产的同时需要加载另一个它引用的蓝图或者什么资产。如果我们频繁在各种蓝图类中用Cast,然后去检查尺寸贴图,就会看到地狱绘图!加载一个资产就占用了两个G的内存(或者磁盘空间)。我们应该尽早地发现这个问题,并且与团队中的其他成员进行沟通与指导。通过各种实际的方法,比如自定义的数据检验、蓝图规范、培训成员等等措施来预防这种问题的产生。三、数据验证
我们可以启用数据验证插件并且可以在编辑器中右键并执行资产数据验证操作。在版本控制中,以Perforce为例,当我们准备提交某个修改时,会首先运行资产数据验证。假如数据验证出现了问题,就会强制用户重新审查资产并进行修复,而不会直接提交。我们可以编写自己的数据验证类,下图是C++中的例子(该类的父类为UEditorValidatorBase),并且我们创建项目自己的验证器基类的时候,编辑器在启动时会自动发现这些C++或者蓝图的验证器,如果新建了一个验证器,就需要重启编辑器。所以不用担心要不要手动注册。我们可以利用Data Validation帮助我们一步一步验证我们的资产。比如烘焙的时候出现警告就是不合规、蓝图中使用了Cast或者GetAllActors这种节点的不合规、检查损坏的引用、检查是否有很长的依赖链、检查蓝图是否过于复杂等等。这都是可以通过自定义数据验证类去实现的。一个检查蓝图节点的案例,我们可以在ValidateNode函数中写入我们自己的检查实现。结果如下。利用数据验证工具弹出错误提醒可以帮助我们在团队中及时规范成员编写蓝图脚本时的习惯。四、复杂性
4.1. 循环复杂度(Cyclomatic Complexity)
我们可以用数字来代表一段代码逻辑的复杂度,这里我们将会利用循环复杂度的计算方式来给我们的C++逻辑和蓝图逻辑打一个复杂度的分。如果对这个计算方式感兴趣可以去搜索相关资料,简单来说这个方法可以用来评估流程图的复杂程度。在C++中是这么评价一段代码的复杂度的,如果有if作为流程的开关,就可以记作一个复杂度。蓝图是一种图表形式的可视化脚本工具,所以可以通过流程分支直接数出复杂度。大致的意思就是,有多少种执行情况就有多复杂。但是这种复杂度计算方式很奇怪,我只有3个节点,复杂度就有可能是6了,感觉不太合理。那我们就只数连接了的流程分支,如下图所示。关于如何优化蓝图的循环复杂度指标,我们可以尝试把一些判断逻辑简化,使用尽可能少的流程分支去实现功能。4.2. 霍尔斯特德复杂度(Halstead Complexity)
除了循环复杂度测量,我们还可以使用霍尔斯特德复杂度测量方式验证我们蓝图或者C++逻辑的复杂度。下面是计算公式,感兴趣的小伙伴可以去搜索相关的数学知识。简单来说就是数我们的操作数和运算符,根据公式计算出对应的参数,比如体积、难度、工作效率等,用来评估一段程序的可维护性。举一个C语言例子,数出运算符(Operators)种类数量n1与操作数(Operands)种类数量n2,然后数出运算符使用次数N1和操作符使用次数N2。然后根据公式计算就好了。而放在蓝图里就这么数运算符与操作数,操作数是那些引用对象、布尔值等等,运算符是那些事件、Cast节点、Branch、执行等等。一个计算例子,这里有3种节点,用了4次,这些是运算符。其中有2个操作数,用了5次。然后代入参数计算。当工作量(Effort)越大,难度(Difficulty)越高,体积(Volume)越大,就说明该段逻辑越复杂,越难以读懂与拓展。如果想降低霍尔斯特德复杂度,我们可以把一些逻辑折叠成节点,或者折叠成函数与宏。上面提到的计算方法是为了让我们知道这么做是能够优化我们逻辑的复杂度的。五、可维护性(Maintainability Index)
可维护性指数是一个用来评估软件系统代码的可维护性参数,这个指数是用霍尔斯特德体积、循环复杂度、代码行数和注释条数进行加权得来的。这是种静态分析,Visual Studio也使用这个来计算可维护性指数。我们会通过这个指数对其进行定性,是否高度可维护,还是极难维护等等。VS里的可维护性指数是修改过的,使分数归一百化(也就是结果会位于1到100之间)所以会有所不同。下图举出了原始公式、VS公式与讲者团队使用的定性标准。接下来是以上提到的参数整合成的图表,用来探讨它们之间的一些关系。比如我们可以在图表中看出,当体积下降时,可维护性指数提升;构造函数是空的,所以可维护指数停滞不前;如果一部分蓝图被很好地做注释和文档了,它的可维护指数也会很高;当图表是空的时候,它是需要被写的,所以可维护指数是最高的。到目前为止,我们有了一些复杂性与可维护性的指标,可以通过一些注释与文档来降低复杂性和提升可维护性。但如果太过于专注于指标,人们就会找出指标的漏洞,并做出一些很奇怪的逻辑,比如在一个地方写上两个Delay和一个注释,就是为了提高可维护性指标。讲者团队的指标,可以作为一个参考,大家可以根据自己团队与项目的情况制定对应的指标。在生成这些数据报告的时候,还可以考虑使用Perforce Age参数作为一个可维护性指标,因为我们可以看到哪些蓝图经常被修改,哪些蓝图修改得很少,这代表着这个蓝图是否经常维护、容易维护。但是我们也要编写相关的文档告诉我们的团队该如何针对这些指标来做出对应的优化工作。团队内成员第一次看到这些奇怪的数据,可能会不懂这些数据背后有什么含义,我该根据这些数据做些什么。我们还可以通过Horde进行蓝图复杂性分析,并且输出一些数据。六、一些例子
开幕雷击,第一个是Control Rig中的蓝图节点,他的可维护指数只有3.5,并且节点数量达到了惊人的一千多个,注释比例也只是基本够看的程度,它的整个体积有4.7w,说明这个图表十分巨大且复杂(循环复杂性为零是Bug)。这个蓝图属于在接受范围之内,当我们的蓝图过于复杂了,不妨可以参考这个蓝图的整理方式以及计算出来的参数。这是个优秀例子,就算不看数据,这也是一个赏心悦目的蓝图图表。可维护性高、节点不多不少、注释也丰富易懂、规模也不大。七、小结
我们讨论了数据验证,这是工作流中很重要的过程,它能帮助我们在主线开发中更稳定安全地迭代,并且提交前的强制验证还能保证我们的提交都是没有问题的。我们还讨论了导致这个问题的几个重大因素:依赖链、复杂性和问题验证。最后我们还详细讨论了复杂度和可维护性的问题,保证我们的蓝图图表更简洁更易拓展。需要注意的是,关于蓝图的复杂性与可维护性分析可以作为一个方案参考,具体需要根据团队与项目决定是否使用这种方案规范蓝图写法。结语
以上便是一些对于优化蓝图复杂性与可拓展性的一些知识与技巧,希望能对您有所帮助。祝创作顺利,工作愉快!扫描下方二维码,关注后点击菜单栏按钮“更多内容”并选择“联系我们”获得更多虚幻引擎的授权合作方式和技术支持。
长按屏幕选择“识别二维码”关注虚幻引擎
“虚幻引擎”微信公众账号是Epic Games旗下Unreal Engine的中文官方微信频道,在这里我们与大家一起分享关于虚幻引擎的开发经验与最新活动。