Unity框架之对象池GameObjectPool
对象池的核心思想
将需要频繁创建销毁的游戏对象缓存起来,将创建销毁行为替换成显示和隐藏,大大提高游戏运行效率。
典型的以空间换时间的思想
对象池的使用流程
对象池的设计
根据上图对象池的使用流程分析对象池的特点
- 每个类型的对象应该有属于自己的独立的池子(子弹复用子弹对象,不能获取子弹却返回一个敌人对象)
- 考虑用字典的键值对 (名称 , 池)–(key,value)的形式缓存每个独立的池子
- 每个独立的池子中保存着若干个游戏对象供使用
- 池:
List<GameObject> 用列表存储个数不固定的游戏对象 - 需要提供一个获取游戏对象的方法和回收游戏对象的方法
- 全局唯一且经常使用,考虑使用单例模式
通用的对象池框架
对象池的数据结构
根据设计时的分析,应该用(key,List)结构的字典
private Dictionary<string, List<GameObject>> objCache;
protected override void Init()
{
base.Init();
objCache = new Dictionary<string, List<GameObject>>();
}
关键方法:获取游戏对象
public GameObject CreateObject(string key,GameObject prefab,Vector3 pos , Quaternion rotate)
{
GameObject go;
go = FindUsableObject(key);
if(go == null)
{
go = AddObject(key, prefab);
}
UseObject(go,pos,rotate);
return go;
}
private GameObject FindUsableObject(string key)
{
if (objCache.ContainsKey(key))
{
return objCache[key].Find(go => !go.activeInHierarchy);
}
else return null;
}
private GameObject AddObject(string key,GameObject prefab)
{
GameObject go = Instantiate(prefab);
if (!objCache.ContainsKey(key)) objCache.Add(key, new List<GameObject>());
objCache[key].Add(go);
return go;
}
private void UseObject(GameObject go,Vector3 pos,Quaternion rotate)
{
go.transform.position = pos;
go.transform.rotation = rotate;
go.SetActive(true);
foreach (var item in go.GetComponents<IResetable>())
item.onReset();
}
关键方法:回收游戏物体
这里提供三种回收游戏物体的方法
- 回收单个游戏对象(支持延迟回收)
- 回收一类游戏对象
- 回收全部的游戏对象
public void CollectObject(GameObject go,float delay=0)
{
StartCoroutine(CollectObjectDelay(go,delay));
}
private IEnumerator CollectObjectDelay(GameObject go,float delay)
{
yield return new WaitForSeconds(delay);
go.SetActive(false);
}
public void Clear(string key)
{
if(objCache.ContainsKey(key))
{
foreach (GameObject obj in objCache[key])
Destroy(obj);
objCache.Remove(key);
}
}
public void ClearAll()
{
foreach(var item in new List<string>(objCache.Keys))
{
Clear(item);
}
}
对象池的拓展
使用对象池最容易的易错点也是难点就是 游戏对象的复用
如果对象每次使用都仅需要修改位置和旋转,则当前版本的对象池已经可以完美做到。
但如果一些对象每次生成后都要注册一些事件,动态生成一些属性等等,每次复用都要注销上一次的这些引用,替换成当前所需要的新事件,新引用。
- 一种解决方案是在此对象的每个脚本的OnEnable和OnDisable中添加需要复用重置的属性逻辑,但这样做会造成紧耦合,并且不利于解决一些较为复杂的属性重置复用。
- 此通用对象池框架提供一个IResetable接口,每次获取对象时,都会查询对象身上是否有实现此接口的脚本,然后调用此接口的onReset()方法,这样当一个较为复杂的对象复用时,就可以在相应脚本实现此接口,在接口中的OnReset方法中完成属性的重置。
public interface IResetable
{
void onReset();
}
对象池的完整源码
本对象池用到了Mono脚本的单例模式,有关单例模式较为简单,读者可以自行查阅了解,笔者在此也贴上源码供参考使用
MonoSingleton.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Common
{
public class MonoSingleton<T> : MonoBehaviour where T:MonoSingleton<T>
{
private static T instance;
public static T Instance
{
get
{
if (instance != null) return instance;
instance = FindObjectOfType<T>();
if (instance == null)
{
new GameObject("Singleton of "+typeof(T)).AddComponent<T>();
}
else instance.Init();
return instance;
}
}
private void Awake()
{
instance = this as T;
Init();
}
protected virtual void Init()
{
}
}
}
GameObjectPool.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Common
{
public interface IResetable
{
void onReset();
}
public class GameObjectPool : MonoSingleton<GameObjectPool>
{
private Dictionary<string, List<GameObject>> objCache;
protected override void Init()
{
base.Init();
objCache = new Dictionary<string, List<GameObject>>();
}
public GameObject CreateObject(string key,GameObject prefab,Vector3 pos , Quaternion rotate)
{
GameObject go;
go = FindUsableObject(key);
if(go == null)
{
go = AddObject(key, prefab);
}
UseObject(go,pos,rotate);
return go;
}
private GameObject FindUsableObject(string key)
{
if (objCache.ContainsKey(key))
{
return objCache[key].Find(go => !go.activeInHierarchy);
}
else return null;
}
private GameObject AddObject(string key,GameObject prefab)
{
GameObject go = Instantiate(prefab);
if (!objCache.ContainsKey(key)) objCache.Add(key, new List<GameObject>());
objCache[key].Add(go);
return go;
}
private void UseObject(GameObject go,Vector3 pos,Quaternion rotate)
{
go.transform.position = pos;
go.transform.rotation = rotate;
go.SetActive(true);
foreach (var item in go.GetComponents<IResetable>())
item.onReset();
}
public void CollectObject(GameObject go,float delay=0)
{
StartCoroutine(CollectObjectDelay(go,delay));
}
private IEnumerator CollectObjectDelay(GameObject go,float delay)
{
yield return new WaitForSeconds(delay);
go.SetActive(false);
}
public void Clear(string key)
{
if(objCache.ContainsKey(key))
{
foreach (GameObject obj in objCache[key])
Destroy(obj);
objCache.Remove(key);
}
}
public void ClearAll()
{
foreach(var item in new List<string>(objCache.Keys))
{
Clear(item);
}
}
}
}
|