对象池的用法和意义
为什么要用对象池,它有什么用呢?它一般用在哪些方面呢?比如,你要制作一个射击类游戏,射击肯定要经常发射子弹吧,如果你不使用对象池,那么你每发射一颗子弹就要实例化一颗子弹,之后还要Destroy销毁掉,不断的实例化和销毁,是非常消耗性能的,小游戏还好,如果是比较大点的游戏,很容易造成卡顿现象,这样玩家肯定就不乐意玩了。又比如跑酷游戏,游戏中会反复的生成金币和障碍物和道具,如果也是反复的生成销毁的话,性能非常差。所以就引入了对象池。
对象池的实现
无非就是,游戏物体在用的时候就设为显示的,即SetActive(true),用不到的时候就SetActive(false),当要使用的false的游戏物体用完了,就再实例化出来,没用完的时候,将false的游戏物体设为true即可。
当游戏中有许多的物品需要重复的实例化和销毁时,如金币、小车、栅栏、磁铁等,这个时候我们可以先建一个子池子,不同类型的东西分别在不同的子池子里,最后由一个大池子统一管理。
子池子的实现
在实现子池子之前,先写个接口,C#中接口可以多继承,而类只能单继承,所以使用接口,接口名一般由大写的I开头。代码如下:
using System.Collections;
using System.Collections.Generic;
public interface IReusable
{
//取出的时候调用
void OnSpawn();
//回收的时候调用
void OnUnSpawn();
}
接口写完后,再写个类,让这个类同时继承MonoBehaviour和 IReusable。
之后在对象池中只需要继承这个新写的类就可以了,相当于同时继承了MonoBehaviour和 IReusable。新的类代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class ReusableObject : MonoBehaviour, IReusable
{
public abstract void OnSpawn();
public abstract void OnUnSpawn();
}
为什么要先写这个接口和这个类呢,因为下面的子池子中会用到。
就是go.SendMessage(“OnSpawn”,SendMessageOptions.DontRequireReceiver);
和go.SendMessage(“OnUnSpawn”,SendMessageOptions.DontRequireReceiver);
将游戏物体设为true时,发送消息让该游戏物体执行OnSpawn()的方法,可以在OnSpawn中设置生成的游戏物体的位置和大小之类的。
将游戏物体设为false时,发送消息让该游戏物体执行OnUnSpawn()的方法,可以在OnUnSpawn中将变换过的游戏物体恢复成原来的模样,如果没有变换过,那就在OnUnSpawn中留空就好了。
子池子的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SubPool
{
//这个是负责保存子池子中的游戏物体
private List<GameObject> m_objects = new List<GameObject>();
private GameObject m_prefab;
public string Name { get { return m_prefab.name; } }//每个子池子都有一个名字标识
private Transform m_parent;
public SubPool(Transform parent,GameObject go)
{
m_parent = parent;
m_prefab = go;
}
/// <summary>
/// 取出物体
/// </summary>
/// <returns></returns>
public GameObject Spawn()
{
GameObject go = null;
foreach (var obj in m_objects)
{
if(!obj.activeSelf) //!obj.activeSelf代表游戏物体是false的;
{
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;
}
/// <summary>
/// 回收一个游戏物体
/// </summary>
/// <param name="go"></param>
public void UnSpawn(GameObject go)
{
if(Contain(go))
{
go.SendMessage("OnUnSpawn", SendMessageOptions.DontRequireReceiver);
go.SetActive(false);
}
}
/// <summary>
/// 回收所有游戏物体
/// </summary>
public void UnSpawnAll()
{
foreach (var obj in m_objects)
{
if(obj.activeSelf)
{
UnSpawn(obj);
}
}
}
/// <summary>
/// 判断是否属于list里面,,因为m_objects是私有的,所以在大池子中访问不到,无法判断小池子中是否包含有传进来的游戏物体,所以这里提供一个供外部访问的方法。
/// </summary>
/// <returns></returns>
public bool Contain(GameObject go)
{
return m_objects.Contains(go);
}
}
小池子建完了以后,就需要建一个大池子来统一管理这些小池子了。
大池子的代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoSingleton<ObjectPool>
{
/// <summary>
/// 资源目录,就是需要实例化的那些游戏物体所在的路径
/// </summary>
public string ResourceDir = "";
//游戏物体的名字对应相应的子池子
private Dictionary<string, SubPool> m_pools = new Dictionary<string, SubPool>();
public GameObject Spawn(string name,Transform trans)
{
SubPool pool = null;
if(!m_pools.ContainsKey(name))
{
RegisteNew(name,trans);
}
pool = m_pools[name];
return pool.Spawn();
}
/// <summary>
/// 新建一个池子
/// </summary>
private void RegisteNew(string names,Transform trans)
{
string path = ResourceDir + "/" + names;
GameObject go = Resources.Load<GameObject>(path);
SubPool pool = new SubPool(trans, go);
m_pools.Add(pool.Name, pool);
}
/// <summary>
/// 清除所有
/// </summary>
public void Clear()
{
m_pools.Clear();
}
/// <summary>
/// 回收物体
/// </summary>
/// <param name="go"></param>
public void UnSpawn(GameObject go)
{
SubPool pool = null;
foreach (var p in m_pools.Values)
{
if(p.Contain(go))
{
pool = p;
break;
}
}
pool.UnSpawn(go);
}
//回收所有物体
public void UnSpawnAll()
{
foreach (var p in m_pools.Values)
{
p.UnSpawnAll();
}
}
}
大池子继承自 MonoSingleton< ObjectPool>,就是将自身变成一个单例,单例的内容请移步https://blog.csdn.net/weixin_43839583/article/details/103405296
因为大池子只有一个,并且需要频繁的使用,所以使用到单例。
对象池的使用就到这里了,性能非常的高效。
我对象池已经建好了,那么我的对象在哪啊,国家怎么还没给我分配对象。
2020.7.23更新:
自己重新写了对象池,将多个池子整合成一个,以方便使用。代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPool : MonoSingleton<ObjectPool>
{
[Header("需要生成的预制体")]
public GameObject[] prefabs;
[Header("生成的预制体的父物体")]
public Transform[] parentTrans;
[Header("生成预制体的数量")]
public int num = 100;
//每个预制体对应生成的游戏物体的列表
private List<GameObject>[] goList;
//每个预制体的名字对应一个生成的预制体列表
private Dictionary<string, List<GameObject>> poolDic = new Dictionary<string, List<GameObject>>();
protected override void Awake()
{
base.Awake();
goList = new List<GameObject>[num];
for (int i = 0; i < num; i++)
{
goList[i] = new List<GameObject>();
}
for (int i = 0; i < prefabs.Length; i++)
{
for (int j = 0; j < num; j++)
{
GameObject go = Instantiate<GameObject>(prefabs[i]);
go.transform.parent = parentTrans[i];
go.SetActive(false);
goList[i].Add(go);
}
poolDic.Add(prefabs[i].name, goList[i]);
}
}
/// <summary>
/// 根据预制体名字来生成游戏物体
/// </summary>
/// <param name="prefabName">预制体名字</param>
/// <returns></returns>
public GameObject SpawnGo(string prefabName)
{
//查找当前预制体名字对应的下标
int index = 0;
for (int i = 0; i < prefabs.Length; i++)
{
if (prefabs[i].name == prefabName)
index = i;
}
if (poolDic.ContainsKey(prefabName))//如果字典中本来就存有这个预制体
{
for (int i = 0; i < poolDic[prefabName].Count; i++)//遍历该预制体所对应的列表
{
if(poolDic[prefabName][i].activeSelf==false)
{
poolDic[prefabName][i].SetActive(true);
return poolDic[prefabName][i];
}
}
GameObject go = Instantiate<GameObject>(prefabs[index]);
go.transform.parent = parentTrans[index];
poolDic[prefabName].Add(go);
return go;
}
else//字典中不含这个预制体时
{
poolDic.Add(prefabName, goList[index]);
GameObject go = Instantiate<GameObject>(prefabs[index]);
go.transform.parent = parentTrans[index];
goList[index].Add(go);
return go;
}
}
/// <summary>
/// 回收一个游戏物体
/// </summary>
/// <param name="go"></param>
public void RecycleGo(GameObject go)
{
//查找当前预制体名字对应的下标
int index = 0;
for (int i = 0; i < prefabs.Length; i++)
{
if (prefabs[i].name == go.name)
index = i;
}
if(goList[index].Contains(go))
{
go.SetActive(false);
}
}
/// <summary>
/// 回收所有游戏物体
/// </summary>
public void RecycleAllGo()
{
for (int i = 0; i < prefabs.Length; i++)
{
for (int j = 0; j < goList[i].Count; j++)
{
if (goList[i][j].activeSelf)
goList[i][j].SetActive(false);
}
}
}
}
2020.12.06新增新方法
(我觉得这样写更好)
代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class ObjectPool<T>
{
private Func<T> func;
private List<T> pool = new List<T>();
public ObjectPool(Func<T> func,int count)
{
this.func = func;
InstanceObject(count);
}
/// <summary>
/// 从对象池里取对象
/// </summary>
/// <returns></returns>
public T GetObject()
{
int i = pool.Count;
if(i-->0)
{
T t = pool[i];
pool.RemoveAt(i);
return t;
}
InstanceObject(3);
return GetObject();
}
/// <summary>
/// 添加对象到对象池
/// </summary>
/// <param name="t"></param>
public void AddObject(T t)
{
pool.Add(t);
}
/// <summary>
/// 实例化对象
/// </summary>
/// <param name="count"></param>
private void InstanceObject(int count)
{
for (int i = 0; i < count; i++)
{
pool.Add(func());
}
}
}
用法如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public GameObject ballPrefab;
private ObjectPool<Ball> ballPool;
void Start()
{
ballPool = new ObjectPool<Ball>(InstanceBallFunc, 100);
}
private Ball InstanceBallFunc()
{
GameObject ball = Instantiate(ballPrefab, transform.Find("ballPool"));
ball.SetActive(false);
Ball ballScript = ball.AddComponent<Ball>();
return ballScript;
}
}