欢迎光临散文网 会员登陆 & 注册

UE4官方蓝图优化总结

2020-01-20 14:43 作者:死亡的小手  | 我要投稿

FestEurope 2019 蓝图深入探讨|Blueprint In-depth 官方视频

UE4官方蓝图优化总结

 

总览

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

蓝图主要消耗

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

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

循环节点

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

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

遍历节点

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

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

每帧更新节点

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

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

循环上限

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

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

复杂的蓝图Actor

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

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

开销对比

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

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

每帧运行节点


每帧运行节点

    这些类似的节点都是每帧来更新的 , 包括输入,时间轴,更新动画,UMG的绑定.


优化建议:

1. 避免过多使用每帧更新, 最直接有效

2. 当使用每帧更新时,尽量思考是否必要. 80%-90%的情况是用不到的

3. 

关闭tick位置

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

4. 

定时器更新

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

优化定时器更新

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

5. 

UMG绑定更新VS事件更新

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

 6. 

禁用蓝图每帧更新

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

7. 

根据距离开启每帧更新

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

根据距离越远更新频率越低

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

根据是否渲染设置更新频率或者关闭

    有个节点Was Recently Rendered,能提供最近是否渲染.如果是false可以考虑关掉或者降低更新频率.

8. 视觉效果不一定要像逻辑处理一样每帧更新.

       根据情况在适当的时候拆分逻辑

使用定时器更新碰撞体积和设置actor位置的计算


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

9. 

图中物体在旋转

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

true是使用蓝图传参数来改变材质参数实现变色, false直接使用材质来改变颜色

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


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


性能分析和调试

1.  

蓝图调试


Blueprint Debugger

    Window--->Developer Tools--->Blueprint Debugger 方便查看所有监测值

2. 可视化日志

可视化日志

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

开启可视化日志

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

      然后打开Window--->Developer Tools--->Visual Logger窗口

3. 分析工具

    统计数据窗口

统计数据窗口

    控制台输入 Stat Game

Dumpticks命令效果

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

Profiler

    Profiler 常规分析工具.

自动测试

    自动测试  设置复杂

自动测试事件设置

    蓝图要先设置测试事件和测试内容, 然后是开始测试事件和返回值.

    关卡名前缀必须FTEST_

    打开Window--->Developer Tools--->Session Frontend窗口

 

测试窗口

    测试完成会返回参数窗口.

 

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


内存和加载

    蓝图和C++ 内存加载上完全不同 , 蓝图属于资源内容 C++就是C++

    如果使用C++ 所有的C++代码都会在初始化时加载,当你启动项目时,项目会加载所有C++类,

    蓝图只有在需要时才会加载.

 

    如果C++ 引用了内容. 那么一开始就会加载这些内容.

 

场景引用蓝图例子

 

引用查看器和搜索深度


转换

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

 

Size Map打开位置和显示窗口

    Size Map会显示加载内容

 

计划引用

    计划并组织引用的管理方式. 务必考虑这点.

    避免蓝图引用和巨量的大型资源, 试着把蓝图拆分成父类蓝图和子类蓝图

 

引用建议:

    创建层级清晰的结构,明确有关资源引用位置的规定

1. 所有关键类都定义在C++中

2. 确保有一个蓝图继承自C++

3. 另一个蓝图以合理的方式继承自父类蓝图

4. 内容最好在第三步中引用

引用关系合理的层级结构

     看起来像这样

 

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

 

库 (函数库,宏库,任何类型的蓝图库)

    如果使用库中的任何一个,整个库都会被加载,请注意这一点

    如果库中的函数,用到了引用, 这个引用也会被加载

    (下图  不要在函数中引用外部资源, 应该作为传入.)

函数错误引用


函数正确的引用


分游戏模式

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

 

动态资源加载

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

 

Maps资源映射

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

这是一种资源映射.

 

Engine.ini可禁用蓝图重新编译

     在编辑器和最终打包出来的加载时间是完全不一样的.

    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++来桥接类. 经常这样做 ,方法很简单. 很多示例都这样做, 即使不是程序员也可以操作. 有助于你使用蓝图. ( 用C++代码获取一个蓝图里的变量信息,直接在另一个蓝图里像节点一样直接创建使用, 而不必要Cast To 来转换获得.)

 

函数操作相关代码

    函数也可以这样操作. 

7. 

子类继承事件

    当你进行转换时,请试着先转换成父类. 蓝图始终能转换成父类. 你可以用一种方法让他触发子类中的特定功能,但你只引用父类.(子类继承事件)

 

    例如 父类中只有变量和事件,子类中事件后实现功能, 其他蓝图只用转换或引用父类事件即可直接触发子类功能.

父类事件,子类实现


只需引用父类事件

 

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

 

接口/ Interface

接口

    最早用于蓝图通信的方法,比Cast To转换还要早.

    从不引用特定的类,只获取你查看的Actor,具体是什么Actor要视情况而定, 可以是任何东西.

    即使Actor 没有实现接口, 他也会忽略这点, 所以你甚至不需要检查这样做是否正确.

 

图例


检查接口实现

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

 

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

 

 

标签系统

标签

    使用标签系统或者使用”游戏技能系统”插件提供的标签系统, 为场景中的对象绑定了很多标签,以便确认当前我们在处理什么内容. 而不必直接引用这些内容.

    例如, 这是玩家可以拾取的物品,为他绑定标签, 这样我们可以随时判断出是否是玩家可以拾取的物品.无需获取类才能得出结论,只需检查标签.你只需指定好标签, 然后在蓝图中查找检查Actor的标签,获取标签数组,遍历他.

 

    最佳实践往往是混用这些方法,所以没有绝对的完美方案. 不是说有些方法完全不要用, 而是权衡各种之间的利弊, 取长补短,深思熟虑,选择合适的方法.

 

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

 

竞态条件

竞态条件

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

 

IsValid

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

 

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

 

C++

C++&BP

    C++和蓝图是紧密联系在一起的. 蓝图所做的一切都依赖于C++

    每个节点基本上都是C++ , 用C++扩展蓝图很容易.

 

    通常 大型游戏项目中的比重大约80%C++ vs 20%BP , 非游戏项目可能不一样. 根据实际情况来.

 

     建议团队中所有人都学习蓝图的使用.  

    即使是程序员也花点时间学习了解一下蓝图. 这将提供很大帮助, 有助于缩小团队之间的鸿沟,有助于让程序理解美术师和设计师在什么样的环境中工作,他们如何工作,蓝图如何工作. 以及虚幻引擎的工作方式,引擎有哪些类,常见流程是怎么样的. 理解美术师和设计师需要什么.哪些对他们会很有帮助.

    另一方面建议美术师和设计师学习C++ ,以便能使用简单的C++技巧. 有助于理解程序员的工作.

    拉近彼此的距离,这是促进团队成长的最佳方式.

 

    C++更效率,更适合管理大型复杂系统,执行数学运算,无法用蓝图实现,或者无法用蓝图实现最优效率,蓝图是有局限性的. 你可以用蓝图做很多事情, 但有些功能实现起来可能很困难.

 

    蓝图优势:原型更快,可视化能让团队成员参与进来加快速度,激发创意,编译更快,迭代更快,调整功能也更容易.

    简单来说,他有助于理解逻辑流,更灵活,更容易被团队成员接受.

    内存方面也更有优势, 因为他属于内容

 

最合理的架构

    C++类使用时甚至可能是空的,你可以创建它以防万一  (核心功能,重要变量)

    C++类可以一直是空的

    蓝图类包含简单功能或画面效果,可以包含声音和画面有关的效果.

    考虑到上面提到过的内存管理技巧,任何内容引用都最好在蓝图中实现.

 

     通常会在一个以上地方用到的功能最好由C++实现,最好是超过两个地方用到.

    例如有个背包系统, 马和玩家都用到了它.

    如果某个功能每帧都要使用,特别是关联了很多Actor,使用C++更合适

    如果功能很复杂且容易出错.C++更合适.

    保存游戏, 网络, 关键变量和枚举. C++更好.

 

     大多数内容引用都应该放在蓝图中, 同样要看具体情况

    任何简单明了,直观可见的内容. 蓝图

    一次性功能. 蓝图

    处于原型并需要快速迭代的内容. 最好还是留在蓝图中

 

C++桥接蓝图

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

 

C++实现蓝图函数库

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

 

暴露给蓝图

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

 

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

 

其他内容

DOP

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

 

纯函数开启
纯函数

    纯函数 没有执行引脚

 

生成时公开

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

 

编辑器中调用

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

 

变量公开到Sequencer

    将一个变量公开到动画 Sequencer ?

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

 

删除未使用的变量

    删除未使用的变量

 

 

显示3D控件

     显示3D控件

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

 

废弃标记

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

    Class Settings--->Deprecate

 

 书签

     书签

创建和显示窗口

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

 

 

字符串表

    字符串表

    类似一张表格, 必须输入”key”和”source string” ,”key”负责增加条目,他会和一个字符串绑定,与之前的”Maps”映射类似,一边是字符串,一边是资源. 只是字符串换到了另一边.他对本地化这类工作很有用.

    例如创建一个”key”标识符,把他标记为”主菜单1”的文本, “key”是你内部使用的信息,然后将他与实际文本绑定,如”开始”,然后你可以本地化了.

 

Maps

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

 

Sets

    集合Sets也类似 ,有点像一个数组, 他有一些内部逻辑, 一个对象只能添加一次 .具有唯一性.

    如果是普通数组,我们要自己编写逻辑来判断是否有重复. 集合避免了这一点. 他能自动检查是否有重复的元素.

 

IsValid

    IsValid 上面提到过了

 





UE4官方蓝图优化总结的评论 (共 条)

分享到微博请遵守国家法律