UE4官方蓝图优化总结
FestEurope 2019 蓝图深入探讨|Blueprint In-depth 官方视频
UE4官方蓝图优化总结

----------------------------------------------------------

蓝图的消耗主要是在节点之间,蓝图连线触发的消耗是一致的,但节点运行的消耗是通过C++ ,节点不同就有所不同. (意思应该是简化节点连接的数量可以降低蓝图消耗)
----------------------------------------------------------

与循环逻辑有关的节点,性能开销都很大. 因为在循环节点中,会有很多连线. 根据实际需要来使用.
----------------------------------------------------------

遍历Actor的节点同样性能开销很大 , 建议使用时把遍历结果保存下来.
----------------------------------------------------------

大量使用每帧更新节点,同样开销很大. 后面会有一些方法来优化他.
----------------------------------------------------------

蓝图是单线程执行, 循环次数上限是100万 , 所有蓝图每帧指令执行次数上限是25万.
----------------------------------------------------------

蓝图本身作为预制体不会产生性能开销. 只有预制体特别复杂的时候才会. 总的来说影响不大.
----------------------------------------------------------

蓝图开销对比. 编辑器模式(红色)运行下效率不高. 但打包(蓝色)和开发模式(黄色)要低很多, 比C++高10倍左右.
----------------------------------------------------------


这些类似的节点都是每帧来更新的 , 包括输入,时间轴,更新动画,UMG的绑定.
优化建议:
1. 避免过多使用每帧更新, 最直接有效
2. 当使用每帧更新时,尽量思考是否必要. 80%-90%的情况是用不到的
3.

可以禁用蓝图每帧更新,如果不需要.
4.

定时器可以处理几乎所有的节点更新

如果场景里有很多实例,不希望在同一时间内循环,导致开销骤增,可以使用随机时间来分摊开销.
5.

尽可能使用基于事件的更新, 上图中UMG绑定会每帧更新.
6.

可以禁用蓝图每帧更新,或者修改更新间隔. 如果没有每帧更新的事件,禁不禁用没有区别.
7.

可以通过设置一些条件来启用每帧更新. 方法很简单,但效果明显.

同上 可以通过设置一些条件来修改更新频率.

有个节点Was Recently Rendered,能提供最近是否渲染.如果是false可以考虑关掉或者降低更新频率.
8. 视觉效果不一定要像逻辑处理一样每帧更新.
根据情况在适当的时候拆分逻辑


每帧更新部分,逻辑没有变,但降低了开销.
9.

简单的动画可以使用材质来实现,不必用蓝图来写. 用材质是使用GPU, 蓝图会消耗CPU. 并且比蓝图更快.

任何简单动画和淡入淡出效果或其他效果.都可以使用材质来处理.材质总是比蓝图快.
-------------------------------------------------------------
性能分析和调试
1.


Window--->Developer Tools--->Blueprint Debugger 方便查看所有监测值
2. 可视化日志

场景中可视化查看变量变化位置和记录轨迹,需要创建或者在现有蓝图里添加

设置复杂 下面是设置在Tick事件的Heath后面,记录Heath变化和场景中发生变化的位置

然后打开Window--->Developer Tools--->Visual Logger窗口
3. 分析工具
统计数据窗口

控制台输入 Stat Game

Dumpticks 命令 输出场景中所有每帧更新的Actors.

Profiler 常规分析工具.

自动测试 设置复杂

蓝图要先设置测试事件和测试内容, 然后是开始测试事件和返回值.
关卡名前缀必须FTEST_
打开Window--->Developer Tools--->Session Frontend窗口

测试完成会返回参数窗口.
-------------------------------------------------------------
内存和加载
蓝图和C++ 内存加载上完全不同 , 蓝图属于资源内容 C++就是C++
如果使用C++ 所有的C++代码都会在初始化时加载,当你启动项目时,项目会加载所有C++类,
蓝图只有在需要时才会加载.
如果C++ 引用了内容. 那么一开始就会加载这些内容.



蓝图之间的互相引用都会导致加载所有相关的内容 , 如果不加以控制,就会失控.

Size Map会显示加载内容

计划并组织引用的管理方式. 务必考虑这点.
避免蓝图引用和巨量的大型资源, 试着把蓝图拆分成父类蓝图和子类蓝图
引用建议:
创建层级清晰的结构,明确有关资源引用位置的规定
1. 所有关键类都定义在C++中
2. 确保有一个蓝图继承自C++
3. 另一个蓝图以合理的方式继承自父类蓝图
4. 内容最好在第三步中引用

看起来像这样
-------------------------------------------------------------
库 (函数库,宏库,任何类型的蓝图库)
如果使用库中的任何一个,整个库都会被加载,请注意这一点
如果库中的函数,用到了引用, 这个引用也会被加载
(下图 不要在函数中引用外部资源, 应该作为传入.)



游戏模式也能帮助区分引用

动态资源加载 , 异步加载资源(Async Load Asset) 然后引用网格体

如果你有很多需要软引用的部分,可以使用Maps来存储,通过关键字查找,方便配置和管理.
这是一种资源映射.

在编辑器和最终打包出来的加载时间是完全不一样的.
Engine.ini 可以禁用”加载时重新编译” (如果蓝图损坏,编辑器可能在加载时因为重新编译崩溃.)
-------------------------------------------------------------
垃圾回收/GC
在游戏中销毁某个对象时,他不会被立即移除,而是被隐藏, 只是被简单标记为不应该存在,实际上并没有从游戏中删除,为了删除他,我们要遍历场景中所有对象,然后明确这个对象应该消失,但我们不能总这样做, 因为开销很大. 这时候就要用到垃圾回收.每隔一段时间,引擎就遍历场景中所有对象,然后删除. 这个就是垃圾回收.

由于要遍历对象, 所以开销有点大 ,通常没有必要特别关注他. 只需要了解垃圾回收有一个统计信息.

可以修改垃圾回收的间隔时间, 默认61秒 ,游戏不建议修改. 如果是电影,根据内存情况可以修改成1小时.

集群,所有蓝图中都有这个设置
(然而,集群和GC通常是复杂的,启用集群并不是一个好主意.
最好的办法是,如果你要处理大量生成和摧毁的话,最好找C++程序员来处理.)
-------------------------------------------------------------
编译
1. 编译时间对于运行时执行没有任何影响
2. 通常编译时间长就预示着涉及很多转换和引用,内容不够优化,内存占用很有可能超过应有的限度
3.
编译时间通常受三个方面的影响
(1) 节点数量
(2) 转换和引用, 导致引用的其他蓝图也编译
(3) 循环引用
4.

蓝图编译有上限 64KB , 编译会提示失败信息. 正常情况是不会达到上限的, 除非犯了某些错误.
5.
使用宏不会对编译产生帮助
但是函数会带来巨大的变化
可以依赖函数来降低编译时间 ,有助于降低编译压力,还能减少编译大小. 相对于宏更好
6.
(1) 将功能拆分成不同的蓝图Actor. 例如 子类,子actor ,组件
(2) 将逻辑移到C++
(3) 实际情况很复杂, 什么时候该迁移, 迁移多少等等.
-------------------------------------------------------------
转换|Cast To

转换会同时影响编译,内存和引用
当你将某个Actor(蓝图) 转换成第二个Actor(蓝图)时, 当编译第一个时,除非转换前后没有变化,否则会一同编译第二个Actor(蓝图), 可以缓存结果,但如果你改变了两者,编译这个或者那个, 就会出现连锁反应.
例如:


看起来简单的蓝图, 但转换目标是一个十分复杂的蓝图. 空变量转换失败, 打印一段字符串,但却需要编译30s . 仅仅因为cast to的目标复杂. 所以转换成功与否并不重要,只要参转换与就会有影响
转换是指蓝图之间的转换, 广义上引用也属于这种转换.

任何引用都会产生类似的效果, 举例,你想获取他的类型(Get Class)是否是特定的类, 这个转换会耗时30s
转换建议:
1.

玩家可能已经加载到内存,不会对内存造成压力.但会稍微降低编译时间.
2.

如果转换的对象很简单,也不会有什么问题
3. 如果有个蓝图只包含变量, 不包含其他内容.转换和引用他不会有太大问题
4.

如果将核心类转换到特定类, 例如将玩家类转换到交通生成类,通常就会产生问题,因为只要玩家类出现,这个交通生成类就需要编译. 所以不要这样做. 除非你的游戏始终有一个交通生成类. 但这里假设没有.
5.

如果你在核心类之间来回引用,从内存角度来看,可能没有区别,但会增加编译时间, 问题不大,但仍然要小心,有可能会变成这样. (下图)


一般可以这样简化理解, 让偶尔出现的依赖性功能 ,引用始终存在的核心功能, 但反过来不行, 这是普遍做法.
6.

不断将内容转移到C++中或者采用其他技巧最小化引用的影响.
使用C++来桥接类. 经常这样做 ,方法很简单. 很多示例都这样做, 即使不是程序员也可以操作. 有助于你使用蓝图. ( 用C++代码获取一个蓝图里的变量信息,直接在另一个蓝图里像节点一样直接创建使用, 而不必要Cast To 来转换获得.)

函数也可以这样操作.
7.

当你进行转换时,请试着先转换成父类. 蓝图始终能转换成父类. 你可以用一种方法让他触发子类中的特定功能,但你只引用父类.(子类继承事件)
例如 父类中只有变量和事件,子类中事件后实现功能, 其他蓝图只用转换或引用父类事件即可直接触发子类功能.


-------------------------------------------------------------
接口/ Interface

最早用于蓝图通信的方法,比Cast To转换还要早.
从不引用特定的类,只获取你查看的Actor,具体是什么Actor要视情况而定, 可以是任何东西.
即使Actor 没有实现接口, 他也会忽略这点, 所以你甚至不需要检查这样做是否正确.


你也可以检查一下, 是否实现了接口 ,相比使用”获取类”方法,你可以检查是否实现了接口,如果实现了, 就接着完成其他操作 .避免了硬引用.
-------------------------------------------------------------
标签系统

使用标签系统或者使用”游戏技能系统”插件提供的标签系统, 为场景中的对象绑定了很多标签,以便确认当前我们在处理什么内容. 而不必直接引用这些内容.
例如, 这是玩家可以拾取的物品,为他绑定标签, 这样我们可以随时判断出是否是玩家可以拾取的物品.无需获取类才能得出结论,只需检查标签.你只需指定好标签, 然后在蓝图中查找检查Actor的标签,获取标签数组,遍历他.
最佳实践往往是混用这些方法,所以没有绝对的完美方案. 不是说有些方法完全不要用, 而是权衡各种之间的利弊, 取长补短,深思熟虑,选择合适的方法.
-------------------------------------------------------------
竞态条件

竞态条件是指两个或多个类相互依赖, 但他们可能会暂时失效. 因为你不知道哪个会先运行

请经常使用 ”IsValid” 确保对象存在.
-------------------------------------------------------------
C++

C++和蓝图是紧密联系在一起的. 蓝图所做的一切都依赖于C++
每个节点基本上都是C++ , 用C++扩展蓝图很容易.
通常 大型游戏项目中的比重大约80%C++ vs 20%BP , 非游戏项目可能不一样. 根据实际情况来.
建议团队中所有人都学习蓝图的使用.
即使是程序员也花点时间学习了解一下蓝图. 这将提供很大帮助, 有助于缩小团队之间的鸿沟,有助于让程序理解美术师和设计师在什么样的环境中工作,他们如何工作,蓝图如何工作. 以及虚幻引擎的工作方式,引擎有哪些类,常见流程是怎么样的. 理解美术师和设计师需要什么.哪些对他们会很有帮助.
另一方面建议美术师和设计师学习C++ ,以便能使用简单的C++技巧. 有助于理解程序员的工作.
拉近彼此的距离,这是促进团队成长的最佳方式.
C++更效率,更适合管理大型复杂系统,执行数学运算,无法用蓝图实现,或者无法用蓝图实现最优效率,蓝图是有局限性的. 你可以用蓝图做很多事情, 但有些功能实现起来可能很困难.
蓝图优势:原型更快,可视化能让团队成员参与进来加快速度,激发创意,编译更快,迭代更快,调整功能也更容易.
简单来说,他有助于理解逻辑流,更灵活,更容易被团队成员接受.
内存方面也更有优势, 因为他属于内容

C++类使用时甚至可能是空的,你可以创建它以防万一 (核心功能,重要变量)
C++类可以一直是空的
蓝图类包含简单功能或画面效果,可以包含声音和画面有关的效果.
考虑到上面提到过的内存管理技巧,任何内容引用都最好在蓝图中实现.
通常会在一个以上地方用到的功能最好由C++实现,最好是超过两个地方用到.
例如有个背包系统, 马和玩家都用到了它.
如果某个功能每帧都要使用,特别是关联了很多Actor,使用C++更合适
如果功能很复杂且容易出错.C++更合适.
保存游戏, 网络, 关键变量和枚举. C++更好.
大多数内容引用都应该放在蓝图中, 同样要看具体情况
任何简单明了,直观可见的内容. 蓝图
一次性功能. 蓝图
处于原型并需要快速迭代的内容. 最好还是留在蓝图中

上面使用C++桥接蓝图的部分代码实现

使用C++实现蓝图函数库

建议程序员把一些方法暴露给蓝图供所有人使用, 让设计师可以使用,有助于产生新的想法和让所有人参与进来. 即使最终没有使用到.
-------------------------------------------------------------
其他内容

推广“DOP”标注 表示有意断开的链接.


纯函数 没有执行引脚

生成时公开,生成Actor时,会得到那个属性. 位于SpawnActor节点内会出现这个变量的属性.

启用在”编辑器中调用” 在蓝图事件中勾选Call in Editor ,非运行状态下,可以调用事件测试.

将一个变量公开到动画 Sequencer ?
创建一个事件 SetNameOfVariable, Set开头+变量名称, 在Sequencer时间轴运行, 事件运行, Sequencer就会运行 . 这样可以在Sequencer中的变量发生变化时,将变量值打印出来.

删除未使用的变量


显示3D控件
勾选后Actor蓝图就会在视口中显示变量的3D控件(菱形标志),可以通过拖动来改变变量,不用输入就能改变变量的值,更直观.

如果你想废弃某些过时和不需要的蓝图内容,又不想删除他,因为其他内容还需要依赖他,你可以标记他,让人们不会继续使用他.
Class Settings--->Deprecate

书签

创建和显示窗口, 还能显示注释框 ,方便查找功能位置

字符串表
类似一张表格, 必须输入”key”和”source string” ,”key”负责增加条目,他会和一个字符串绑定,与之前的”Maps”映射类似,一边是字符串,一边是资源. 只是字符串换到了另一边.他对本地化这类工作很有用.
例如创建一个”key”标识符,把他标记为”主菜单1”的文本, “key”是你内部使用的信息,然后将他与实际文本绑定,如”开始”,然后你可以本地化了.

Maps类似 ,映射表示两个对象的关联关系, 你可以让某个资源始终与另一个资源关联,可以是任何类型的资源. 网格,特效,变量 等等 .能减少逻辑复杂度和管理难度.

集合Sets也类似 ,有点像一个数组, 他有一些内部逻辑, 一个对象只能添加一次 .具有唯一性.
如果是普通数组,我们要自己编写逻辑来判断是否有重复. 集合避免了这一点. 他能自动检查是否有重复的元素.

IsValid 上面提到过了

完