对象池模式(Object Pool Mode),是创建模式的一种。本来的五个怎么变成六个了。2333,它和享元模式非常相似,只不过享元模式一个对象可以被多个对象引用,对象池模式一个对象只能被一个对象引用。它是Unity脚本开发中非常常用的模式,对于反复创建销毁的游戏物体使用对象池模式可以大幅提升游戏性能。比如子弹,怪物等等。它和有限状态机一样都有代码框架。这里我也整理了两种对象池代码框架的写法。
目录
1. 意图:
我们将对象存储在一个池中,当需要时再次使用,而不是每次都实例化一个新对象。池的最重要特性。也就是对象池设计模式的本质是我们获取一个‘新的’对象,而不管它真的是一个新的对象还是循环使用的对象。
2. 如何使用:
对象池的特征:
1.对象池中有一定数量已经创建好的对象。
2.对象池向用户提供获取对象的接口,当用户需要新的对象时,便可通过调用此接口获取新的对象。如果对象池中有事先创建好的对象时,就直接返回给用户,如果没有了,对象池还可以创建新的对象加入其中,然后返回给用户。
3.对象池向用户提供归还对象的接口,当用户不再使用某对象,便可以通过此接口把对象归还给对象池。
优点:复用池中对象,没有分配内存和创建堆中对象的开销,没有释放内存和销毁堆中对象的开销,进而减少垃圾收集器的负担,避免内存抖动,不必重复初始化对象状态。
缺点:水平有限,以后补。
何时使用:
1.频繁创建销毁某对象。
2.创建对象比较昂贵。
注意事项:注意并发环境下的使用,多个线程可能同时需要获取池中的对象,进而需要在堆数据结构上进行同步或者因为锁竞争而产生阻塞,这种开销要比创建销毁对象的开销高数百倍。
3. 第一种对象池代码框架:
只有一个游戏对象的对象池Single pool
***************************定义*********************************
private List<GameObject> bulletPool = new List<GameObject>();//对象池
public GameObject PrefabRay;//克隆预设
***************************方法*****************************
//对象池入口取物品
public GameObject GetBullet()
{
GameObject o;
if (bulletPool != null && bulletPool.Count > 0)
{
o = bulletPool[0];
bulletPool.RemoveAt(0);
o.SetActive(true);
}
else
{
//第一次没东西生成子弹
o = GameObject.Instantiate(PrefabRay);
}
return o;
}
//对象池出口隐藏物体回收
public void RecyleBullet(GameObject o)
{
if (bulletPool != null)
{
bulletPool.Add(o);
o.SetActive(false);
}
}
多种游戏对象的组合对象池
***************************定义*****************************
private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();
public GameObject PrefabRay1;//克隆预设1
public GameObject PrefabRay2;//克隆预设2
***************************方法*****************************
//创建入口
public GameObject GetBullets(string name)
{
GameObject o;
if (pool != null && pool.ContainsKey(name) && pool[name] != null && pool[name].Count > 0)
{
o = pool[name][0];
pool[name].RemoveAt(0);
o.SetActive(true);
}
else
{
//这样有多少子弹类型预设就写多少
if (name == "bullect1")
{
o = GameObject.Instantiate(PrefabRay1);
}
else if(name == "bullect2")
{
o = GameObject.Instantiate(PrefabRay2);
}
}
return o;
}
//删除入口
public void Recyles(string name, GameObject o)
{
if (pool != null)
{
if (!pool.ContainsKey(name))
{
pool[name] = new List<GameObject>();
}
pool[name].Add(o);
o.SetActive(false);
}
}
// 创建:GameObject go = GetBullets("bullect1");删除:Recyles("bullect1", go);
4. 第二种对象池代码框架:
套入框架的对象池代码:
单例模板
using UnityEngine;
using System.Text;
using System.Collections.Generic;
//单例模版
//对泛型T进行约束,泛型T也必须是MonoBehaviour的子类
public abstract class Singleton<T> : MonoBehaviour
where T : MonoBehaviour
{
private static T m_instance = null;
public static T Instance
{
get { return m_instance; }
}
//为了可以让子类继承下去,我们把保护级别设为保护的,并变成一个虚方法,因为子类也有可能用。
protected virtual void Awake()
{
m_instance = this as T;
}
}
public class SubPool
{
Transform m_parent;
//预设
GameObject m_prefab;
//集合
List<GameObject> m_objects=new List<GameObject>();
//名字标识
public string Name
{
get{ return m_prefab.name;}
}
//构造
public SubPool(Transform parent,GameObject prefab)
{
this.m_parent = parent;
this.m_prefab = prefab;
}
//取对象
public GameObject Spawn()
{
// Debug.LogWarning(m_objects.Count);
GameObject go = null;
foreach(GameObject obj in m_objects)
{
if(!obj.activeSelf)
{
go = obj;
break;
}
}
if(go==null)
{
go = GameObject.Instantiate<GameObject> (m_prefab);
go.transform.parent = m_parent;
m_objects.Add (go);
}
go.SetActive (true);
go.SendMessage ("OnSpawn",SendMessageOptions.DontRequireReceiver);
return go;
}
//回收对象
public void Unspawn(GameObject go)
{
if(Contains(go))
{
go.SendMessage ("OnUnspawn", SendMessageOptions.DontRequireReceiver);
go.SetActive (false);
}
}
//回收该池子的所有对象
public void UnSpawnAll()
{
foreach(GameObject item in m_objects)
{
if(item.activeSelf)
{
Unspawn (item);
}
}
}
//是否包含对象
public bool Contains(GameObject go)
{
return m_objects.Contains (go);
}
}
//让对象池继承单例模版,他就立马变成了单例的组建
public class ObjectPool :Singleton<ObjectPool>
{
public string ResourceDir = "";
Dictionary<string,SubPool> m_pools = new Dictionary<string,SubPool> ();
//取对象
public GameObject Spawn(string prefabName,string dirPath)
{
if (!m_pools.ContainsKey (prefabName))
{
RegisterNew (prefabName, dirPath);
}
SubPool pool=m_pools[prefabName];
return pool.Spawn ();
}
//回收对象
public void Unspawn(GameObject go)
{
SubPool pool = null;
foreach(SubPool p in m_pools.Values)
{
if(p.Contains(go))
{
pool = p;
break;
}
}
pool.Unspawn (go);
}
//回收所有对象
public void UnSpawnAll()
{
foreach(SubPool p in m_pools.Values)
{
p.UnSpawnAll ();
}
}
//创建新子池子
void RegisterNew(string prefabName, string dirPath)
{
//预设路径
string Path="";
if (string.IsNullOrEmpty (ResourceDir))
{
Path = dirPath;
}
else
{
Path = ResourceDir + "/" + dirPath;
}
//加载预设
GameObject prefab=Resources.Load<GameObject>(Path);
//创建子对象池
SubPool pool=new SubPool(transform,prefab);
m_pools.Add (pool.Name,pool);
}
}
对象池接口
public interface IReusable
{
//当取出时调用
void OnSpawn ();
//当回收时调用
void OnUnspawn ();
}
要使用对象池物体的抽象类
//每个游戏对象的功能都是不一样的。都需要实例化,我们给他加上抽象类关键字
public abstract class ReusbleObject:MonoBehaviour,IReusable
{
//表示abstract子类继承父类的时候,一定要实现这几个方法
public abstract void OnSpawn ();
public abstract void OnUnspawn ();
}
具体的对象
public abstract class Role : ReusbleObject,IReusable
{
public event Action<int, int> HpChanged;//血量变化
public event Action<Role> Dead;//死亡
int m_Hp;
int m_MaxHp;
//血量要到0再说,不能让他直接死亡,所以这里要保护起来
protected virtual void Die(Role role)
{
}
public override void OnSpawn()
{
//在起点
//当事件发生的时候要和死亡挂钩,
//每次从池子里拿出来,这个dead和die事件挂钩,
//当他死的时候,子类可以在这里写些东西,比如做些记录啊
//积分都可以在这里实现
this.Dead += Die;
}
public override void OnUnspawn()
{
//在终点
//送到池子里这些脏数据都要给他洗干净
//首先字段会变脏,事件也会变脏
//因为在用的时候首先是事件会加监听器的,
//就是事件会和监听事件挂钩,一对多关系
//字段归零,事件清空
Hp = 0;
MaxHp = 0;
while (HpChanged != null)
{
HpChanged -= HpChanged;
}
while (Dead!=null)
{
Dead -= Dead;
}
}
}
使用具体的对象
Role m_Role = null;
public void SpawnRole(Vector3 Vecpos)
{
GameObject go = Game.Instance.ObjectPool.Spawn("Role","Roles/Role");
m_Role = go.GetComponent<Role>();
m_Role.Dead += Role_Dead;
m_Role.HpChanged += Role_HpChanged;
m_Role.transform.position = Vecpos;
}