预制池系统
首先需要知道预制池系统需要做什么:
- 如果已经存在已回收的版本,应该重新生成第一个可用的对象
- 如果不存在已回收的版本,应该从预制体中实例化新的GameObject
- 在上述两种情况下, 都应该在附加到GameObject上的所有IPoolableComponent接口上调用Spawned方法
- 必须接受请求,以回收固定GameObject
- 如果对象由对象池系统管理,他应该禁用,并在附加到GameObject上的所有IPoolableComponent接口类上调用Despawned方法
- 如果对象没有由对象池系统管理,应该报错
以上的需求简单明了,但是如果希望使解决方案的性能更好,则需要进行一些改进
首先,对于主要的入口来说,典型的单例是一种非常好的选择,因为单例可以做到在任何地方,全局访问;
生成的对象的主要任务包括接受一个预制引用,指出是否有回收的GameObject从相同的引用中实例化,为此,对象池系统需要为任何给定的预制体跟踪两种不同类型的列表,一个是已经激活的(实例化的),一个使未激活的(回收的)共两个对象列表,这种数据信息,最好抽象到一个类去管理,将其命名为PrefabPool;
另一方面,由于生成的Gameobject需要给定一个预制体,我们将通过一个数据结构能够快速的将预制体映射到管理他们的PrefabPool,同时,由于回收对象需要给定一个Game Object,我们将通过另一个数据结构把已经生成的Gameobject映射到最初生成他们的PrefabPool,这样看来我们需要一对字典,分别用来管理生成与回收
接下来需要定义在生成对象发生了什么
接下来需要定义回收对象的时候需要做什么
现在已经有了可以自动处理多个预制池的系统,剩下的工作就是如何定义这个池子的行为,如前所述,Prefab类应该维护两个数据结构,一个用于在给定的Prefab中实例化活动(派生)对象,另一个用于非活动(回收的)对象;
从技术上来讲PrefabPoolingSystem1类已经维护了一个由PrefabPool管理的prefab的映射,所以实际上可以节省一点内存,因此这两个数据结构是PrefabPool需要跟踪的成员变量,但是由于每个GameObejct还必须要维护所有IPoolableComponent引用的列表,以便对他们调用Sapwned和Despawned方法,获取这些引用可能是在运行时执行的一个昂贵操作,所以最好将数据缓存在一个简单的数据结构中;如下
现在可以定义PrefabPool类的成员数据了
生成对象
接下来定义在池系统容器中生成GameObject意味着什么,在某个时刻,PrefabPool会收到一个请求,从给定的预制体中利用给定位置旋转,生成GameObject,首先应该要检查是否有该预制体的非激活实例,如果有,就可以将下一个可用对象移出队列,并重新生成它,如果没有,就需要使用GameObject.Instantiate从预制体中实例化新的GameObject,此时应该创建PoolablePrefabData对象,来保存GameObject引用,并获取附加在它上面,所有实现了IPoolableComponent的MonoBehaviour列表
下面的Spawn定义了这样的行为;
对象的回收
预制池测试
预先生成实例
预制池和场景加载
该系统还有一个重要的警告,由于PrefabPoolingSystem是静态类,它比场景的生命周期更长,这意味着当加载新场景时,池系统的字典尝试维护之前场景中已经池化的实例的引用,但unity在切换场景时强制销毁这些对象,而不管我们依然保有对他们的引用,因此字典将充满空引用,这将为下一个场景带来很多严重的问题,因此,应该在PrefabPoolingSystem中创建一个方法,类似于重置系统 ,并且在新的场景加载前调用,为下一个场景做好准备;
全部代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
public static class PrefabPoolingSystem1
{
static Dictionary<GameObject, PrefabPool> _prefabTopoolMap = new Dictionary<GameObject, PrefabPool>();
static Dictionary<GameObject, PrefabPool> _goToPoolMap = new Dictionary<GameObject, PrefabPool>();
public static GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation)
{
if(!_prefabTopoolMap.ContainsKey(prefab)) //如果缓存中没有包含要生成的对象
{
_prefabTopoolMap.Add(prefab, new PrefabPool());
}
PrefabPool pool = _prefabTopoolMap[prefab];//获得对象所在的预制池
GameObject go = pool.Spawn(prefab, position, rotation);//使用预制池生成预制
_goToPoolMap.Add(go, pool);//将生成的预制体与预制池保存
return go;
}
public static GameObject Spawn(GameObject prefab)
{
return Spawn(prefab, Vector3.zero, Quaternion.identity);
}
public static bool Despawn(GameObject prefab)
{
if(!_goToPoolMap.ContainsKey(prefab))
{
Debug.LogError(string.Format("Object {0} not managed by pool system!", prefab.name));
return false;
}
PrefabPool pool = _goToPoolMap[prefab];
if(pool.Despawn(prefab))
{
_goToPoolMap.Remove(prefab);
return true;
}
return false;
}
public static void Prespawn(GameObject prefab, int numToSpawn)
{
List<GameObject> spawnedObjects = new List<GameObject>();
for (int i = 0; i < numToSpawn; i++)
{
spawnedObjects.Add(Spawn(prefab));
}
for (int i = 0; i < numToSpawn; i++)
{
Despawn(spawnedObjects[i]);
}
spawnedObjects.Clear();
}
public static void Reset()
{
_prefabTopoolMap.Clear();
_goToPoolMap.Clear();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
public struct PoolablePrefabData1
{
public GameObject go;
public IPoolableComponent[] poolableComponents;
}
class PrefabPool1
{
Dictionary<GameObject, PoolablePrefabData1> _activeList = new Dictionary<GameObject, PoolablePrefabData1>();
Queue<PoolablePrefabData1> _inactiveList = new Queue<PoolablePrefabData1>();
public GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation)
{
PoolablePrefabData1 data;
if(_inactiveList.Count > 0)//如果存在已经回收的对象
{
data = _inactiveList.Dequeue();
}
else //如果不存在已经回收的对象
{
//那么就需要新实例化一个对象
GameObject newGo = GameObject.Instantiate(prefab, position, rotation);//实例化
data = new PoolablePrefabData1();
data.go = newGo;
data.poolableComponents = newGo.GetComponents<IPoolableComponent>();//需要获取所有IPoolableComponent引用的列表,以便调用Spawned和Despawned
}
//设置实例化物体的位置
data.go.SetActive(true);
data.go.transform.position = position;
data.go.transform.rotation = rotation;
for(int i = 0;i<data.poolableComponents.Length; i++)
{
data.poolableComponents[i].Spawned();//所有的实现了IPoolableComponent接口的都需要调用Spawned
}
_activeList.Add(prefab, data);
return data.go;
}
public bool Despawn(GameObject objToDespawn)
{
if(!_activeList.ContainsKey(objToDespawn))//如果将要回收的对象没有在显示列表中,代表出错啦
{
Debug.LogError("This Object is not managed by this object pool!");
return false;
}
PoolablePrefabData1 data = _activeList[objToDespawn];
for(int i = 0;i <data.poolableComponents.Length;i++)//所有的实现了IPoolableComponent接口的都需要调用DesSpawned
{
data.poolableComponents[i].Despawned();
}
data.go.SetActive(false);
_activeList.Remove(objToDespawn); //移除
_inactiveList.Enqueue(data);//添加
return true;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PrefabPoolingTestInput : MonoBehaviour {
[SerializeField] GameObject _orcPrefab;
[SerializeField] GameObject _trollPrefab;
[SerializeField] GameObject _ogrePrefab;
[SerializeField] GameObject _dragonPrefab;
List<GameObject> _orcs = new List<GameObject>();
List<GameObject> _trolls = new List<GameObject>();
List<GameObject> _ogres = new List<GameObject>();
List<GameObject> _dragons = new List<GameObject>();
void Start() {
PrefabPoolingSystem1.Prespawn(_orcPrefab, 11);
PrefabPoolingSystem1.Prespawn(_trollPrefab, 8);
PrefabPoolingSystem1.Prespawn(_ogrePrefab, 5);
PrefabPoolingSystem1.Prespawn(_dragonPrefab, 1);
}
void Update() {
if (Input.GetKeyDown(KeyCode.Alpha1)) { SpawnObject(_orcPrefab, _orcs); }
if (Input.GetKeyDown(KeyCode.Alpha2)) { SpawnObject(_trollPrefab, _trolls); }
if (Input.GetKeyDown(KeyCode.Alpha3)) { SpawnObject(_ogrePrefab, _ogres); }
if (Input.GetKeyDown(KeyCode.Alpha4)) { SpawnObject(_dragonPrefab, _dragons); }
if (Input.GetKeyDown(KeyCode.Q)) { DespawnRandomObject(_orcs); }
if (Input.GetKeyDown(KeyCode.W)) { DespawnRandomObject(_trolls); }
if (Input.GetKeyDown(KeyCode.E)) { DespawnRandomObject(_ogres); }
if (Input.GetKeyDown(KeyCode.R)) { DespawnRandomObject(_dragons); }
}
void SpawnObject(GameObject prefab, List<GameObject> list) {
GameObject obj = PrefabPoolingSystem1.Spawn(prefab,5.0f * Random.insideUnitSphere,Quaternion.identity);
list.Add(obj);
}
void DespawnRandomObject(List<GameObject> list) {
if (list.Count == 0) {
// Nothing to despawn
return;
}
int i = Random.Range(0, list.Count);
PrefabPoolingSystem1.Despawn(list[i]);
list.RemoveAt(i);
}
}
public interface IPoolableComponent {
void Spawned();
void Despawned();
}
预制池系统总结:
这个池系统为GameObject和预制的运行时内存分配问题提供了一个很好的解决方案,但是需要注意以下事项:
- 需要小心再重新生成的物体中正确的重置重要数据(刚体速度等)
- 必须确保不会预先生成太少或者太多的实例
- 应该小心IPoolableComponent中的Spawned和Despawned方法的执行顺序,不能假定他们以特定的顺序执行
- 在加载新场景的时候,必须要调用PrefabPoolingSystem1的Reset,以清除可能不再存爱的空引用对象
如果希望扩展这个系统,可以从下面的几个方面来做:
- 在GameObject初始化之后添加到GameObject上的IPoolableComponent不会触发他们的Spawned()或Despawned方法,因为值在GameObject首次初始化收集该列表,为了修复此问题,可以PrefabPool,在每次调用Spawned和Despawned时,获取IPoolableComponent引用,但是代价时生成和回收期间有额外的开销;
- 添加到预制根下的子节点的任何IPoolableComponent不会被同继,为了修复此问题,可以修改prefabPool来使用GetComponentsInchildren,代价是有额外的开销;
- 可以实现一种方法,让IPoolableComponent在获取期间设置优先级,并直接控制他们的Spawn和Despawned的方法的执行顺序;
- 这个系统不会将自己设置为DontDestroyOnLoad的预制实例友好的交互,明智的做法可能时在每个Spawn调用中添加一个布尔值,以判断加载新场景的时候,是否应该持久化;