虚幻引擎(UE4) C++与蓝图之性能比较

当为游戏设计技术方案的时候,一个最主要的问题就是什么内容需要放在蓝图实现,什么内容要放在c++中实现。本文旨在讨论这些问题,并给出一个参考的建议。

游戏逻辑和数据

广义的说,游戏内容可以分为Logic(逻辑)Data(数据)Logic是游戏的指令和结构,Data被用于逻辑并描绘了游戏具体干了些什么。有时候这种区分是显而易见的,例如c++在屏幕上绘制一个角色是基于逻辑的,而角色的物理外观是基于数据的。但是在实际练习中,各种各样的LogicData混杂在一起是非常复杂的,理解这其中的区别和做出正确的选择至关重要。

在虚幻4中你有几种方式来实现Logic

  • C++ :变量和函数定义在c++代码里,并实现最基础的逻辑。
  • 蓝图:游戏逻辑实现也可以放在蓝图的事件图表(Event Graph)中,或者被图表调用的函数中。添加额外变量。
  • 自定义系统:许多系统和游戏带有轻量级的微型语言,用来描述游戏逻辑。虚幻材质编辑器、AI行为树都是属于游戏逻辑的自定义系统。

Data你可以使用更多的方式来实现:

  • C++:类的构造函数中设置默认值并支持数据继承。当然数据也可以强制写死在函数中,但是这不利于扩展和Data追踪。

  • Config Files:INI文件和控制台变量支持声明在C++构造函数中的数据,也能快速直接地查询。

  • 蓝图:蓝图的默认工作方式和C++的构造函数类似,提供数据继承。数据也能安全地放在函数的局部变量中或者写在常量中。

  • Data Assets:对于无法实例化且不需要数据继承的对象,独立的Data Assets比蓝图默认设置更易于使用。

  • Table:Data可以在运行时(Runtime)被作为Data Tables(数据表)、Curve(曲线)导入。

  • 自定义数据储存系统:通常在开发中,开发团队都会有一套属于自己的数据储存系统。

通常,派生类中的属性会覆盖父类的属性,基类向派生类的转换是不安全的,所以基类很难从派生类中访问和使用变量。举例说明,在C++类中访问一个蓝图中的变量是很难的,这种获取方式也是不推荐的。为了避免这样的问题,绝大多数情况,你应该将函数和变量定义在基类(C++类),让C++直接从基类访问变量或者函数。对于游戏逻辑来说,将基础逻辑实现在基类是最好的做法,或者你可以将主要基础逻辑函数留在基类,扩展的,可变化的逻辑写在派生类中来实现。

Data数据规则更复杂,系统特定,因为有更多的可能性,更深入的继承更常见。你需要在他们定义的地方提供默认值。任何一个派生对象都可以重写默认值。

C++ vs 蓝图

从上面的列表你会注意到,不管是c++还是蓝图都可以用于LogicData。介于这种原因,大多数游戏系统都是采用其中一种来实现的。以为每个开发团队的情况不同,所有没有绝对正确的选择方式,但是这里给出了一些建议,可以帮助你决定是否使用C++或者蓝图。

C++的优势:

  • 执行效率更高:通常C++逻辑比蓝图逻辑更快,原因如下。
  • Explicit Design(明确):C++更容易控制是否暴露你想控制的变量或者函数。所以你可以采用protect/private保护指定的变量/函数并构建一个正式的API给你的类。这这样做会避免创建一个过大的难以理解的蓝图
  • 访问更容易:函数和变量定义在C++中更容易被其他的系统访问,可以在不同系统之间完美的传递信息。除此之外,C++比蓝图拥有更多引擎功能。
  • 控制更多的数据:在加载和保存数据时,C++具有更具体的功能。这允许您以非常自定义的方式处理版本更改和序列化。
  • 网络复制:蓝图中提供的网络复制支持是很直观的,也是专门为小型游戏设计的。如果你希望更加正确的控制网络带宽和时机,你还是需要用C++。
  • 对数学库支持更好:在蓝图中做一些复杂的数学计算是很困难的,而且很慢的。所以考虑使用C++来处理复杂的数学运算。
  • 更容易合并和对比差异:C++代码和数据储存格式为文本文件,这在多人开发和多分枝开发中显得格外友好。

蓝图的优势:

  • 创建更快:对于大多数人来说,创建一个蓝图并添加一个变量或者函数比在C++中快得多。
  • 更快迭代:蓝图更容易修改和预览,而C++修改还需要重新编译。
  • 流动性更好:将C++中的“游戏流程”可视化是很复杂的,因此在蓝图中通常是更好的。延迟和异步刷新的节点比使用C++委托要容易得多。
  • 灵活编辑:对于设计和美术人员,蓝图更加友好,都可以进行修改编辑。而C++几乎只有程序员才能修改。
  • 数据使用更紧密:将数据储存在蓝图中更加简单和安全,蓝图适合于数据和逻辑紧密的类。

性能方面

首选使用C++的一个重要原因是性能问题。然而,蓝图的性能在实践中不是问题。总的来说,主要的区别在于,在蓝图中执行每个单独节点比执行一行C++代码行更慢,但是一旦执行在节点内,它就如同从C++调用一样快。举个例子,如果你的蓝图中很少的函数并调用开销大的物理跟踪函数,将这些类转换为C++类并不会显著地提高游戏性能。但是如果你的蓝图中有大量联系紧密的循环节点或者嵌套了可以扩展到上百个节点的宏,你就应该考虑将蓝图转换为C++代码。一个最有争议的影响性能的函数就是Tick函数,执行蓝图的Tick比执行C++的Tick慢得多,你应该避免Tick拥有多个实例对象的类。相反,你应该使用定时器或者委托代理让蓝图类在需要时调用。

查找影响性能的蓝图中的最好的办法就是使用Profiler Tool(工具)。要查看你的工程的性能表现,首先在合适的位置创建一个蓝图有严重性能问题的场景,然后使用Profiler Tool捕获一个配置文件,你可以深入到游戏线程中,然后展开树,直到你找到某个单独的蓝图类()。在蓝图内部,你可以看见蓝图函数的消耗时间,然后再展开它,如果它在Self中消耗了大多数时间,你将因为蓝图开销过大而丢失性能。但是如果你大多数时间消耗在其他本地事件中,那么蓝图的开销就不是问题。

Blueprint Nativization(蓝图本地化)可以减轻这种问题,但是这也有缺点:

  • 首先,它改变了你的工作流程,从而减慢项目的迭代进度。
  • 此外,它的运行时逻辑与普通蓝图不同,因此根据游戏的具体情况,您可能会看到不同的bug或游戏行为。绝大多数蓝图的特性在本地化蓝图中是支持的,但是有些模糊特性并没有得到支持。
  • 最后,性能改进不一定非要转换成C++。蓝图本土化可能无法解决所有的性能问题,但应该作为解决性能问题的潜在解决方案。

架构方面的建议

使用C++和蓝图构建蓝图的时候,随着游戏内容变多变得复杂,你将面临诸多挑战。这里给出一些建议:

  • 避免使用开销大的蓝图:无论何时,将一个蓝图BP_B转换为BP_A(或者是将其声明为其他函数或者蓝图中的变量类型),都将会在BP_A上创建一个依赖项。这种情况下,如果BP_A拥有4个巨大的Static Mesh(静态网格)和20个音效,每次你加载BP_B它将不得不加载4个巨大的Static Mesh​​和20个音效,甚至会转换失败。所以这很有必要将重要的函数和变量定义在基类​,不太重要的和不共用的函数或者变量放在​​​​派生的类型中。除此之外,你也应该尽量将大的蓝图作为子类来处理。
  • 避免蓝图循环引用:C++中的循环引用并不是什么大问题,只需要添加头文件include,使用#if_def/#elseif就能解决问题。但是在蓝图中循环引用就会使加载和编译更糟糕。如上面所说,想要解决这种问题,这种情况应该将蓝图转换为C++类或者开销更小的蓝图基类,而不是转换为开销更大的蓝图子类。
  • 避免在C++构造函数中引用资源:可能会利用一些函数(FObjectFinderFClassFinder)在C++构造函数中的引用资源,如果确实不是很有必要,尽可能的避免这样做。这种引用方式会在在项目工程启动的时候开始加载资源,这会导致加载时间过长和内存过大的问题。同时,在构造函数中被引用的资源很难删除和重命名。通常一个好的办法是,创建一些Game Data资源或者蓝图类型,然后使用一个资源管理器(assets manager)或者配置文件来加载,而不是直接在C++中引用指定的Static Mesh
  • 避免通过字符串引用资源:为了避免直接从C++加载资源的问题,可能会使用LoadObject之类的函数从磁盘上手动加载资源。然后这样引用完全没法被开发者追踪问题,所以就可能在打包好的游戏中引发问题。相反,你应该在C++中使用FSoftObjectPath或者TSoftObjectPtr类型,在INI文件和蓝图中设置它们,并按需要进行加载或者进行异步加载。
  • 谨慎使用结构体和枚举:C ++和Blueprints都可以使用C ++中定义的枚举和结构,但是用户结构/枚举不能在C ++中使用,也不能如保存游戏部分所述手动固定。因为随着时间的流逝,您可能希望将更多游戏逻辑转移到C ++,所以我们建议在C ++中实现关键的枚举和结构。基本上,如果一个或两个以上的蓝图使用某些东西,则可能应该在本机C ++中实现。
  • 考虑网络架构:游戏的特定网络架构将对构建类的方式产生重大影响。通常,构建原型时候并没有考虑网络因素,所以当你开始重构使之变得真实的时候你就需要考虑什么Actors需要网络复制什么数据。为了创造一个良好的复制数据流程,你可能需要做出更难以迭代决策。
  • 考虑异步加载:随着你的游戏不断迭代而变得越来越大越臃肿,你需要考虑将资源按需要加载,而不是游戏启动的时候加载所有的资源。一旦你理解了这一点,你就需要开始将Hard references(硬引用:从磁盘加载并引用)转变为Soft reference(软引用:从内存引用)。AssetManager(资源管理器)提供了几个方法来异步加载资源,同时公开了提供了低级别功能函数的StreamableManager 

你可能感兴趣的:(UE4,C++和蓝图,UE4,虚幻引擎,VR,C++/蓝图,性能优化)