【UE5】通用对象池系统设计和实现(一)

对象池的好处

众所周知,对象池是游戏开发中很常见的一种设计模式,它的好处颇多,比如内存复用、减少内存碎片等等。在处理大量可重复利用的游戏对象时,可以极大的提高游戏性能。最常见的比如子弹之类的物体。在UE中,如Niagara等都带有专门的对象池系统来优化其性能。但其余的如生成、销毁Actor时,却没有一个通用的对象池系统来进行管理。这次我们就来实现一个对蓝图友好、使用方便且易拓展的UE通用对象池系统。

最终实现功能

我们实现的对象池使用起来非常方便,首先第一步,在想要的位置创建一个对象池数据文件:

创建对象池数据文件

创建完,打开对象池数据文件,可以看见以下配置项:

对象池数据文件配置项

  • PoolName:该对象池的唯一辨识符,类型为一个FName,该变量会自动命名为创建时的文件名,一般情况下建议做好文件命名规范,而不去改动这个参数。
  • ObjectType:对象池的对象类型,可选择为Actor对象池或UObject对象池,可自行拓展更多类型,不过一般为Actor对象池居多。
  • Pool Actor Classes:要池化的Actor,可直接把Actor传入此Array,即可将此对象池化。当ObjectType不同时,该选项会变成对应Type类型的Array。
  • PoolMode:对象池的运作模式,可设置为栈模式或者队列模式,队列模式可以使池对象取用更加均匀,不同模式按需选用,且可轻松自行拓展更多运作模式。
  • Collction Check:回收对象时是否进行安全检查,检查待回收对象是否已在池中。默认为开启状态,关闭可以提升些许性能,按实际情况更改,推荐保持默认即可。
  • Default Capacity:设置池的初始大小,根据对象的使用频率适当调整。
  • Max Size:设置对象池的最大容量,同样根据对象的使用频率适当调整。

配置完池的基本参数后,将需要池化的对象传入Array中

如图:
配置池化Actor
池化的Actor如果需要接收从池中取出和回收时的事件,还可以直接添加一个Pooltem组件,以此可监听对应事件:
池行为事件

配置好一个池数据文件后,把它直接添加进项目设置->ObjectPool->ObjectPoolSystem中的Object Pool Data Array即可

添加进项目设置

使用起来也很简单,下面是在蓝图中使用对象池系统的例子

获取对象:
获取对象
回收对象:
回收对象
从上面的简单例子可以看出,我们的对象池系统使用起来非常方便,获取池化对象基本上分为以下三步:

  1. 获取ObjectPoolSubsystem;
  2. 从ObjectPoolSubsystem获取Pool对象,可通过PoolName或者资产引用进行获取,推荐使用PoolName,性能更好;
  3. 从Pool对象中取出指定类型的对象,使用该对象的类引用来进行查找,该方法返回从池中取出的实例化对象。

而回收对象同样很简单,同样分为三步:

  1. 获取ObjectPoolSubsystem;
  2. 从ObjectPoolSubsystem获取Pool对象;
  3. 传入待回收对象的示例和其对应的类引用,即可完成回收。

蓝图中所开放的函数,在C++中同样可用,所以你依旧可以通过在C++中编写代码来使用对象池系统。

对象池系统框架设计

简单讲完实现的效果,我们回过头来看看如何从头构筑这样一套系统。首先,我们需要思考一个问题,一个通用对象池系统,他肯定需要一个全局管理的单例类,而这个类的的实例,应该放在引擎的哪一个位置好?换在Unity,我们也许会写一个单例的Manager类,然后把他设置为DontDestroyOnLoad,从而让他在整个游戏的生命周期中以单例对象的形式进行管理。而在UE中,由于UE有一套完备的Gameplay框架,我们不得不考虑,我们的单例类应该运行在框架中的哪一个层级,才能完美的解决我们的需求?我们所需要的,是在整个游戏的生命周期中以一个单例来管理我们的对象池。而在UE的Gameplay架构里,最符合这一特征的无疑是我们的GameInstance(对UE的Gameplay架构不太熟悉的可以看看大钊的InsideUE系列,很有帮助)。确定是在GameInstance后,如果是以前,接下来头疼的就是如何管理我们单例类的生命周期。但现在UE有了Subsystem框架(还不了解的同样可以看看大钊的博客),这让我们可以轻松实现我们自己的单例类,而不用去头疼单例类的自动实例化和释放等问题。

而有了用于管理所有对象池的ObjectPoolSubsystem,接下来的思路就简单多了,先简单上个图:

对象池系统框架图
结合上图,我们从底往上走,最底的是我们想要池化的普通Actor,对于每个池化Actor,它都有对应的UObjectPool对象,负责池化和管理对应Actor。再往上走,管理我们UObjectPool的则是UPool对象,每一个ObjectPoolData都对应了一个UPool对象,而每个UPool都会读取对应ObjectPoolData中的数据,并为Data中指定的每一个待池化的Actor创建出对应的UObjectPool。继续往上走,就来到了我们的UObjectPoolSubsystem,我们在之前也说过,它需要管理所有的对象池,所以理所当然的,UObjectPoolSubsystem将持有我们所有UPool对象的引用。这样一来,只要我们能够获取到UObjectPoolSubsystem,我们就可以获取到所有的UObjectPool。

结尾

本节我们讨论了对象池系统框架的基础部分。下一节,除了讲框架中PoolAgent的设计,我们还将讨论对象池的核心类——UObjectPool的设计与实现。

猜你喜欢

转载自blog.csdn.net/weixin_51924051/article/details/134322480