传统对象池&AB对象池
前序:
Q:为啥需要对象池?
A: 游戏中大量出现或销毁对象时会反复的开堆和放堆,程序与内存之间交互过于频繁导致资源的大量浪费
Q: 对象池实现原理?
A: 当子对象池没有物体的时候,它会和普通没加对象池的程序一样要内存,实例化数据,但当某个属于对象池的物体被销毁时,它不会直接回内存,而是被保存在子对象池顺序表中,当下次程序再次想用该对象时就可以直接从子对象池中拿取而不用像内存索取,像游戏中存在 特效,子弹等物体时时常会用到。
Q:什么是传统和AB对象池
A: 传统对象池,使用简单资源存放于Resources中,但当需要打包进AssetBundle中时,因为是代码弱关联的原因,会导致打不进去,所以需要使用强关联的形式来迫使Unity打包子对象池中的预制体。
Q: 使用过程中需要注意哪些地方
A: 框架使用时,请注意向下的迭代,就是说新版本的框架代码必须兼容老版本的框架代码。框架代码前面一定要加类似于JW这样的标记,由于项目一般不是一个人完成的,而框架、设计模式、算法等英文名称大多相近,如果不加以区分容易出现类重复的情况(切记!切记!)
说明:
传统对象池与AB对象池 其实现原理一毛一样,其唯一区别在于游戏数据放在AB包中,如果按照传统对象池那样通过Resources的代码加载资源,会导致数据没有被打包,所以需要挂载资源。
原理图示:
传统对象池
方法1:(代码部分很简单直接看就行)
using UnityEngine; using System; using System.Collections; using System.Collections.Generic; namespace Game { public class CSObjectPool<T> where T : class, new() { private Stack<T> m_ObjStack = new Stack<T>(); private Action<T> m_OnPop; private Action<T> m_OnPush; public CSObjectPool(Action<T> onPop, Action<T> onPush) { m_OnPop = onPop; m_OnPush = onPush; } /// <summary> /// 取出 /// </summary> public T Pop() { T obj = null; if(m_ObjStack.Count == 0) { obj = new T(); } else { obj = m_ObjStack.Pop(); } if(obj != null && m_OnPop != null) { m_OnPop(obj); } return obj; } /// <summary> /// 放回 /// </summary> public void Push(T obj) { if(obj == null) return; if(m_ObjStack.Count > 0 && ReferenceEquals(m_ObjStack.Peek(), obj)) { Debug.LogError(string.Format("CSObjectPool error. Trying to push object that is already in the pool, obj = {0}", obj)); return; } if(m_OnPush != null) { m_OnPush(obj); } m_ObjStack.Push(obj); } } }
方式2:( 由于这个方法实现过于复杂已经被我放弃了!)
使用方法:
- 挂载ObjectPool (场景中)
- 讲预制体(prefab)放入Resources资源中
- 然后就可以直接单例要数据!
- 销毁就直接调 ObjectPool.Instance.Unspawn(XXXX);
代码:
/*************************************** 作者: 蒋伟 版本: v1.0 最后修改时间: 2016-12-22 电话: 15928517727 功能&使用方法: 可重用接口 * * void OnSpawn -------- 当生成时调用 * void OnUnspawn ------ 当回收时调用 ***************************************/ using UnityEngine; using System.Collections; public interface IReusable { //当生成时调用(初始化) void OnSpawn(); //当回收时调用(销毁) void OnUnspawn(); }
/*************************************** 作者: 蒋伟 版本: v1.3 最后修改时间: 2016/12/26 电话: 15928517727 功能&使用方法: * 自定义顺序表, 注意存放在该表中的所有 * 物体必须可比较,如果为自定义的类需要继 * 自IComparable接口,并public实现接口汇总CompareTo(Object obj) * 接口! * * 存在方法: * <0> ----------- MyArrayList<T>() -------- 无参构造 * <1> ----------- Size() ------------------ 得到使用空间 * <2> ----------- Expansion()(私有)------ 扩容 * <3> ----------- Add(T data) ------------- 添加数据 * <4> ----------- (r)T At(int index) ------ 得到数据 * <5> ----------- Clear() ----------------- 清空数据 * <6> ----------- (r)T this[int index] ---- 得到/设置 数据【索引器】 * <7> ----------- SortSmallToBig() -------- 排序(从小到大)(冒泡排序) * <8> ----------- ConsoleShow() ----------- 显示(控制台) Unity程序别用 * <9> ----------- DebugShow() ------------- 显示(Debug.Log) Unity调试使用 * <10> ---------- Contains(T) ------------- bool类型返回是否包含这个东西 ***************************************/ using UnityEngine; using System.Collections; using System; namespace MyList { //泛型顺序表 (IComparable) 可以比较的 //由于部分数据类型(GameObject)不支持数据比较, //然而它还不知羞耻的加了Sealed属性 public class MyArrayList<T> /*where T : IComparable*/ { //容量 int capacity; //使用量 int size; //堆 --- 指针 T[] obj; //构造 public MyArrayList() { //内存容量 (初始4 自动扩容) capacity = 4; //空间使用量归0 size = 0; //开堆 obj = new T[capacity]; } //属性 --- 返回使用空间 public int Size { //只写返回 get { return size; } } //方法 --- 扩容 void Expansion() { //容量 == 使用量 if (size == capacity) { //容量扩大一倍 capacity *= 2; //数据开堆 T[] nt = new T[capacity]; //复制数据 for (int i = 0; i < size; ++i) { nt[i] = obj[i]; } //修改数据结构指针指向 obj = nt; } } //方法 --- 数据添加 public void Add(T data) { //扩容 Expansion(); //放入数据 obj[size++] = data; } //方法 --- 得到数据[下标索引] public T At(int index) { //参数检查 if (index < 0 || index >= size) return default(T); else return obj[index]; } //方法 --- 清空 public void Clear() { //使用空间修改为0就可以了 size = 0; } //索引器a public T this[int index] { set { //设置数据 在 范围内 if (index >= 0 && index < size) { obj[index] = value; } } get { if (index >= 0 && index < size) return obj[index]; else return default(T); } } //声明委托 //case 1 -------------- Left > Right //case 0 -------------- Left == Right //case -1 -------------- Left < Right public delegate int CompareTo(T _objLeft, T _objRight); //方法 --- 排序(冒泡) public void SortSmallToBig(CompareTo CTF) { //冒泡排序 for (int i = 0; i < size; ++i) { for (int j = size - 1; j > i; --j) { if (CTF(obj[i], obj[j]) > 0) { T temp = obj[i]; obj[i] = obj[j]; obj[j] = temp; } } } } //遍历显示(C#控制台显示) public void ConsoleShow() { //遍历 for (int i = 0; i < size; ++i) { //这个显示方式比 Console.WriteLine(obj[i]); } } //遍历显示(Unity.Debug.Log(name)) public void DebugShow() { //遍历 for (int i = 0; i < size; ++i) { //这个显示方式比 Debug.Log(obj[i].ToString()); } } //遍历查找 public bool Contains(T t, CompareTo CTF) { //遍历 for (int i = 0; i < size; ++i) { if (CTF(t, obj[i]) == 0) { return true; } } //否则return false return false; } } }
/*************************************** 作者: 蒋伟 版本: v1.1 最后修改时间: 2018-06-05 电话: 15928517727 功能&使用方法: 传统对象池 1.挂载ObjectPool (场景中) 2.讲预制体(prefab)放入Resources资源中 3.然后就可以直接单例要数据了! ****************************************/ using UnityEngine; using System.Collections; using System.Collections.Generic; public class ObjectPool : Singleton<ObjectPool> { //子对象池存储 Dictionary<string, SubPool> m_pools; //初始化 public override void Awake() { base.Awake(); Init(); } private void Init() { m_pools = new Dictionary<string, SubPool>(); } //取对象 绝对路径 public GameObject Spawn(string _name) { //返回的子对象池 SubPool pool = null; //如果存在该子对象池 if (m_pools.ContainsKey(_name)) { pool = m_pools[_name]; } else pool = CreateNewSubPool(_name); //返回一个新对象 return pool.Spawn(); } //回收对象 public void Unspawn(GameObject _obj) { SubPool pool = null; //遍历查找所有子池子 foreach (SubPool p in m_pools.Values) { //找到了 if (p.Contains(_obj)) { pool = p; break; } } //调用回收方法 if (pool!= null) pool.Unspawn(_obj); } //回收所有对象 public void UnspawnAll() { foreach (SubPool p in m_pools.Values) p.UnspawnAll(); } //创建新的子对象池 private SubPool CreateNewSubPool(string _name) { //预设路径 string path = ""; path = _name; //加载预设 GameObject prefab = Resources.Load<GameObject>(path); SubPool np = new SubPool(prefab); //添加到池列表 m_pools.Add(_name, np); //返回子对象池 return np; } }
/*************************************** 作者: 蒋伟 版本: v1.0 最后修改时间: 2016-12-21 电话: 15928517727 功能&使用方法: 可被回收的类(抽象类) * ***************************************/ using UnityEngine; using System.Collections; public abstract class ReusableObject : MonoBehaviour, IReusable { //pS:纯虚类不能有本体 但如果用virtual 修饰则不会有这个问题 public abstract void OnSpawn(); public abstract void OnUnspawn(); }
/*************************************** 作者: 蒋伟 版本: v1.0 最后修改时间: 2016-12-22 电话: 15928517727 功能&使用方法: 单例 * 这个单例还需要理解 ***************************************/ using UnityEngine; using System.Collections; public class Singleton<T> : MonoBehaviour where T : MonoBehaviour { //指针 protected static T m_instance = null; //属性 public static T Instance { get { return m_instance; } } //偷懒的单例方式 在 Awake里面进行初始化 public virtual void Awake() { m_instance = this as T; } }
/*************************************** 作者: 蒋伟 版本: v1.0 最后修改时间: 2016-12-22 电话: 15928517727 功能&使用方法:子对象池 * * -------------------------------------------- * GameObject m_prefab ------------- 预设 * MyArrayList<GameObject> m_objects - 存储物体用链表 * -------------------------------------------- * Name ------------------------------- 得到子对象池名字(属性) * SubPool(GameObject prefab)-------- 构造 * Spawn()--------------------------- 喂!对象池!给我一个对象!(单身狗之咆哮~) * Unspawn(GameObject obj) ------------ 回收一个对象 * UnspawnAll() --------------------- 回收所有对象s * Contains(GameObject obj) --------- 是否包含对象 ***************************************/ using UnityEngine; using System.Collections; using MyList; public class SubPool { //预设 GameObject m_prefab; //存储物体用链表(构造来) MyArrayList<GameObject> m_objects; //子对象池名字 public string Name { get { return m_prefab.name; } } //构造 public SubPool(GameObject prefab) { //赋值 this.m_prefab = prefab; //开堆 m_objects = new MyArrayList<GameObject>(); } //取对象 池子中则拿出 无则新建 public GameObject Spawn() { GameObject obj = null; for (int i = 0; i < m_objects.Size; ++i) { //池子中存在,拿出 if (!m_objects[i].activeSelf) { obj = m_objects[i]; break; } } //如果对象池没有了! if (obj == null) { obj = GameObject.Instantiate<GameObject>(m_prefab); m_objects.Add(obj); } //设置为启用 obj.SetActive(true); //调用怪物的初始化函数 并且是预设体子树下所有组件的初始化 //(厉害了我的哥!),参2保证不投射错误 obj.SendMessage("OnSpawn", SendMessageOptions.DontRequireReceiver); return obj; } //回收对象 public void Unspawn(GameObject obj) { //对象存在于子对象池 if (Contains(obj)) { obj.SendMessage("OnUnspawn", SendMessageOptions.DontRequireReceiver); obj.SetActive(false); } } //回收该池子的所有对象 public void UnspawnAll() { for (int i = 0; i < m_objects.Size; ++i) { if (m_objects[i].activeSelf) { m_objects[i].SendMessage("OnUnspawn", SendMessageOptions.DontRequireReceiver); m_objects[i].SetActive(false); } } } //是否包含对象 public bool Contains(GameObject go) { bool temp = false; //判断物体是否存在于顺序表中 for (int i = 0; i < m_objects.Size; ++i) { if (m_objects[i] == go) { temp = true; break; } } return temp; } }
使用代码:
/*************************************** Editor: Tason Version: v1.0 Last Edit Date: 2018-XX-XX Tel: 328791554@qq.com Function Doc: 在屏幕左上角为初始点依次生成图像 ***************************************/ using UnityEngine; using System.Collections; using UnityEngine.UI; public class Test : MonoBehaviour { //需要生成的物体地址 public string spritePath; //物体生成在哪个物体下面 如果为空则生成在当前挂载下面 public Transform m_parent; //当前物体的生成下标 private int m_xIndex; private int m_yIndex; //初始屏幕长宽 private int m_screenW; private int m_screenH; //生成对象的长宽 private float m_spriteW; private float m_spriteH; private void Awake() { Init(); } //初始化 private void Init() { //安全校验 if (m_parent == null) m_parent = transform; //初始化参数 GameObject obj = Resources.Load<GameObject>(spritePath); if (obj == null) { Debug.Log("生成物体不存在!!"); return; } else { m_spriteW = obj.GetComponent<RectTransform>().sizeDelta.x; m_spriteH = obj.GetComponent<RectTransform>().sizeDelta.y; } m_screenW = Screen.width; m_screenH = Screen.height; m_xIndex = m_yIndex = 0; //初始测试 Debug.Log("Sw:" + m_spriteW + " Sh:" + m_spriteH); Debug.Log("Screenw:" + m_screenW + " Screenh:" + m_screenH); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { GameObject obj = ObjectPool.Instance.Spawn("sprites") as GameObject; obj.transform.SetParent(m_parent); obj.GetComponent<RectTransform>().anchoredPosition = new Vector3(m_xIndex++ * m_spriteW - m_screenW / 2, -1 * m_yIndex * m_spriteH + m_screenH / 2, 0); if ((m_xIndex + 1) * m_spriteW >= m_screenW) { m_xIndex = 0; m_yIndex++; } } } }
AB对象池
本来以为很麻烦,结果发现只用加一个脚本就可以了,ObjectPool(ABObjectPool) 都是负责生成物体的,SubPool负责管理GameObject的List,代码如下 (亲测AssetBundle打包可行!!!撒花!)
/*************************************** 作者: 蒋伟 版本: v0.0.1 最后修改时间: 2018-06-05 电话: 15928517727 功能&使用方法: 强关联对象池 1.挂载ABObjectPool (场景中) 2.给ABObjectPool 添加键值对!! 3.然后就可以直接单例要数据了! ****************************************/ using UnityEngine; using System.Collections; using System.Collections.Generic; public class ABObjectPool : Singleton<ABObjectPool> { //子对象池存储 Dictionary<string, SubPool> m_pools; [System.Serializable] public class KeyValueStruct { public string m_name; public GameObject m_prefab; } public KeyValueStruct[] m_keyValues; //初始化 public override void Awake() { base.Awake(); Init(); } private void Init() { m_pools = new Dictionary<string, SubPool>(); } //取对象 绝对路径 public GameObject Spawn(string _name) { //返回的子对象池 SubPool pool = null; //如果存在该子对象池 if (m_pools.ContainsKey(_name)) pool = m_pools[_name]; else pool = CreateNewSubPool(_name); //返回一个新对象 if (pool != null) return pool.Spawn(); return null; } //回收对象 public void Unspawn(GameObject _obj) { SubPool pool = null; //遍历查找所有子池子 foreach (SubPool p in m_pools.Values) { //找到了 if (p.Contains(_obj)) { pool = p; break; } } //调用回收方法 if (pool!= null) pool.Unspawn(_obj); } //回收所有对象 public void UnspawnAll() { foreach (SubPool p in m_pools.Values) p.UnspawnAll(); } //创建新的子对象池 private SubPool CreateNewSubPool(string _name) { //预设路径 string path = _name; //加载预设 GameObject prefab = null; for (int i = 0; i < m_keyValues.Length; ++i) { if (_name.Equals(m_keyValues[i].m_name)) { prefab = m_keyValues[i].m_prefab; break; } } if (prefab == null) { Debug.Log("对象池数据丢失!!!!!"); return null; } SubPool np = new SubPool(prefab); //添加到池列表 m_pools.Add(_name, np); //返回子对象池 return np; } }