前言
之前做游戏的时候用过对象池,适用于频繁使用回收的对象,可以防止对象反复生成和销毁,减少gc,之前用于人物移动的残影上。
GF中有两个池子,之前一直不知道有什么区别,直到看了传送门,没错又是群主大佬的文章,才知道引用池和对象池原理一样,但是适用的对象不一样,一般引用池用于存储C#类型的对象,对象池用于Unity相关的GameObj对象。
引用池
打开GF源码,可以看到关于ReferencePool有这四个文件。先分别介绍这四个类和他们的功能,最后拿StarForce中的一个例子来看看~
IReference
IReference接口,所有要被引用池存储的类型都要继承自该接口,其中有一个Clear方法,会在被放回池子的时候调用,一般用于该引用对象的数据初始化。
ReferenceCollection
我们的游戏中一般有多个引用池,每个引用池存放不同的对象,一个引用池则存放相同类型的对象,一个ReferenceCollection则代表一种类型的引用池
用一个队列在存储对象。
其余的属性分别为存放的对象类型,被取出的数量,获取次数,释放次数,加入对象的次数和移除对象的次数。
获取对象的两种方法,一种通过泛型,另一种直接获取。
有则取之,无则实例化
将对象放回池子,调用Clear(),并且检查该对象是否已经被放回防止重复操作。
Add就直接new并且放入count个对象进队列,remove和removeAll也是类似。
ReferencePool
静态类
因为外部不能直接拿到引用池,所以是在该类中通过一个字典来存储所有类型引用池,外部就通过它来间接获取
强制类型检查,必须该对象是Class,继承自IReference
可以看到这两个Acquire的区别,分别调用的是ReferenceCollection的Acquire,一个是使用泛型,一个不使用。
使用泛型的就不需要强制类型检查,因为有where约束,如果传入错误的类型直接编译报错非常安全,总的来说用泛型比较好。
ReferenceInfo
引用池信息,结构体类型
在调用此方法时会返回一个数组,记录所有引用池的信息
使用
这是某个使用引用池的地方,可以看到它继承自IReference,并且实现了Clear()方法。
重点是这个Create()方法,调用Acquire< T >后会创建实例,因为Create传入的参数都不同,所以没有在IReference中写上。
当Release后再次Create就可以直接从引用池中取出而不需要重新创建
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using GameFramework;
using UnityEngine;
namespace UnityGameFramework.Runtime
{
internal sealed class AttachEntityInfo : IReference
{
private Transform m_ParentTransform;
private object m_UserData;
public AttachEntityInfo()
{
m_ParentTransform = null;
m_UserData = null;
}
public Transform ParentTransform
{
get
{
return m_ParentTransform;
}
}
public object UserData
{
get
{
return m_UserData;
}
}
public static AttachEntityInfo Create(Transform parentTransform, object userData)
{
//一开始没有就创建一个,放回池子里循环利用
AttachEntityInfo attachEntityInfo = ReferencePool.Acquire<AttachEntityInfo>();
attachEntityInfo.m_ParentTransform = parentTransform;
attachEntityInfo.m_UserData = userData;
return attachEntityInfo;
}
public void Clear()
{
m_ParentTransform = null;
m_UserData = null;
}
}
}
对象池
相比于引用池,对象池更加复杂,如果让我自己写对象池的话能写成引用池那样的已经很满足了。
ObjectBase
对象基类,要通过对象池操作的类需要继承自ObjectBase,ObjectBase本身继承自IReference(内有Clear方法)
此时我们需要考虑,如果是Unity自带的GameObject对象不能继承自ObjectBase怎么办,继续看代码
其中的m_Target字段才是我们真正需要操作的对象
可以通过Initialize函数来初始化对象。所以我们如果要用到对象池,则需要重写一个类继承自ObjectBase,内部封装的m_Target才是真正要操作的对象
其余属性分别为名称,是否加锁,优先级,对象上次使用时间
子类可能需要重写这两个方法,这两个方法会在Object中Spawn和Unspawn被调用,我们也是通过ObjectPool来获取对象
Object
说实话这个操作我是看懵了,嵌套的有点复杂~
我们先看一下ObjectPool的代码
上面那个一对多的字典应该就是池子了,然而它存储的是Object< T>类型
在看到Object,继承自IReference,且T需要继承自ObjectBase
破案了,这个就是我们存储的对象,准确的是它里面封装的m_Target才是我们真正存储的对象。
Object是ObjectManager的内部类,我们需要通过ObjectManager来拿到它
Object的很多属性和字段和ObjectBase一样,且已经编写了自己这层的Spawn和UnSpawn逻辑,内部调用的ObjectBase的Spawn和Unspawn需要我们自己重写
ObjectPoolBase
接下来我们要看的是ObjectPool
可以看到它继承自一个抽象类,一个接口,我们先来看看ObjectPoolBase
内容不一一介绍了,大概就是些获取对象类型、释放对象池对象等操作
IObjectPool< T>
和上面的操作差不多,但是多了些针对于泛型的操作,也有Spawn和Unspawn,在其中调用Object< T>的对应函数
区别
为什么要写一个抽象类,一个泛型接口?我一开始也不知道为啥,还是看了群主大佬的文章
因为ObjectPoolBase不是泛型,所以我们在需要获取多个对象池的通用数据时,可以用ObjectPoolBase[]来操作,IObject< T>是明确类型的,保证编译时类型安全,和类型有关的Spawn,Register操作都声明在该接口中。
因为继承自这两个,所以我们在不同的情境下可以以不同的形式来持有对象池
这两处就很明显,上面是要进行和类型相关的操作的,下面是ObjectPoolComponentInspector的,是和集合有关的操作
ObjectPool
两个字典,一个一对多,一个一对一,一对多的应该就是Name对应一个对象集合,一对一的话是实际存储的对象对应一个Object< T>
从注释我们可以看到每个字段属性的含义
先调用Object< T>的Spawn,再调用ObjectBase的Onspawn,其中这个Onspawn我们可以重写
和上面的类似,但是如果数量超出限制,需要调用Release
次操作和上面的时间、回调都有关系
筛选可释放对象
最后就是调用我们传入的筛选回调函数(可以自己写)得到一个可释放列表,将其一一释放即可,不详细写了
ObjectPoolManager
传入ObjectBase的子类Type和Name作为key,对象池作为Value来获取对应的池子。
还有些创建对象池,释放对象池等操作就不详细说了…StarForce中血条相关的就是具体使用方法,大家可以自己看看~