缓存池: 通过重复利用已经创建的对象,避免频繁的创建和销毁, 减少系统的内存分配和垃圾回收所带来的开销.
C#所需知识点: C#中Dictionary、Stack知识点. C#泛型单例、Resources资源初始化及管理.
优点: 需要 手动创建 ➔ 提前分配 ➔ 重复复用 ➔ 避免频繁new/destroy Objs.
缺点: 与垃圾回收机制相比, 需要自己管理池子(下文会讲解GC 垃圾回收机制).
附上DeepSeek 及其他博主所介绍的对象池机制如下图.
上图是常见的游戏开发中需要创建的单例, 以及如何管理的关系.
我们通过一个案例来讲解对象池技术的实现.
在这个案例游戏中, 游戏中需要管理音效和粒子的频繁创建和销毁, 或者留存在Hierarchy面板中.
每一次场景中特定的Objs被销毁后, 应该发出声音和生成粒子, 并且自己销毁. 若不采用对象池的技术, 那么Hierarchy面板中一直会在特定的Obj销毁后, Instanced Particle 和 Sound. 会越来越多.
1. 首先,场景中的Test / Quad 为 游戏中粒子的Prefab, Test / ExplosionPart 为游戏中的Audio Source.
2. 然后, 我们讨论下整个 Prefab的生成过程(需要由四个代码组成) :
3.1 BaseManager 泛型单例 ( 需要藏在Assets中)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.Reflection;
public class BaseManager<PoolMgr> where PoolMgr : class//,new()
{
private static PoolMgr instance;
public static PoolMgr Instance
{
get
{
if (instance == null)
{
//利用反射得到无参私有的构造函数 来用于对象的实例化
Type type = typeof(PoolMgr);
ConstructorInfo info = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
null,
Type.EmptyTypes,
null);
instance = info.Invoke(null) as PoolMgr;
}
return instance;
}
}
}
上面代码中的<PoolMgr>也可以换成<T> .
3.2 对象池单例 (对象池管理代码,也需要藏在Assets中)
采用Dictionary(字典) 和Stack(栈) 的方式用来管理复用的对象(声音、特效)GetObj方法中,采用Pop ( ) 的方式 在栈中取出一个元素, 然后在PushObj方法中,采用Push ( ) 的方式 在抽屉中放入一个元素.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolMgr : BaseManager<PoolMgr>
{
private Dictionary<string, Stack<GameObject>> poolDic = new Dictionary<string, Stack<GameObject>>();
private PoolMgr() { }
/// <summary>
/// 缓存池(对象池)模块 管理器
/// </summary>
/// <param name="name"></param>抽屉容器的名字</returns>
/// <returns></returns>
public void GetObj(string name, Action<GameObject> callback)
{
GameObject obj = null;
//有抽屉 并且 抽屉里 有对象 才直接去拿
if (poolDic.ContainsKey(name) && poolDic[name].Count > 0)
{
//弹出栈中的对象 直接返回给外部使用
obj = poolDic[name].Pop();
//激活对象 再返回
obj.SetActive(true);
}
//否则,应该去创造
else
{
//没有的时候 通过资源加载 去实例化出一个GameObject
obj = GameObject.Instantiate(Resources.Load<GameObject>(name));
obj.name = name;
}
callback?.Invoke(obj);
}
/// <summary>
/// 往缓存池放入对象
/// </summary>
/// <param name="name">抽屉 (对象)的名字</param>
/// <param name="obj">希望放入的对象</param>
public void PushObj(GameObject obj)
{
if (obj == null) return;
obj.SetActive(false);
// 如果不存在该键,先创建新Stack
if (!poolDic.ContainsKey(obj.name))
{
poolDic.Add(obj.name, new Stack<GameObject>());
}
poolDic[obj.name].Push(obj);
}
/// <summary>
/// 用于清除整个柜子中的数据
/// 使用场景 主要是 切场景时
/// </summary>
public void ClearPool()
{
poolDic.Clear();
}
}
3.3 在需要碰撞的物体上挂上脚本,一旦物体被销毁, 在物体被销毁的地方实例化声音和粒子
using System.Collections;
using System.Collections.Generic;
using Unity.FPS.Game;
using UnityEngine;
public class selfDestruction : MonoBehaviour
{
// Start is called before the first frame update
public LayerMask destroyableLayer;
public LayerMask defaultLayer;
public float rayDistance;
public bool destroySelf = false;
public bool destroyCollider = false;
public bool startDetection = false;
public bool isUsed;
public bool isEnemyUsed;
public GameObject enemyObj;
public GameObject playerObj;
private AudioSource audioSource;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (!startDetection)
{
return;
}
}
public void ClearUseStatus()
{
isEnemyUsed = false;
isUsed = false;
}
private void OnCollisionEnter(Collision collision)
{
if (isUsed)
{
if (collision.transform.gameObject != playerObj.gameObject)
{
if (collision.gameObject.layer == 6 || collision.gameObject.layer == 7)
{
if (destroyCollider)
{
InitParticle();
Destroy(collision.transform.gameObject, 0.1f);
}
}
if (collision.gameObject.layer == 0)
{
if (destroySelf)
{
Debug.Log("gameObject:" + collision.transform.name);
InitParticle();
Destroy(gameObject, 0.01f);
}
}
}
return;
}
if (isEnemyUsed)
{
if (collision.transform.gameObject != enemyObj.gameObject)
{
if (collision.gameObject.layer == 0)
{
if (destroySelf)
{
Debug.Log("gameObject:"+collision.transform.name);
InitParticle();
Destroy(gameObject,0.01f);
}
}
}
}
}
private void InitParticle()
{
//从池子里拿 音效对象
PoolMgr.Instance.GetObj("Test/ExplosionPart", (GameObject soundObj) =>
{
soundObj.transform.position = transform.position;
//soundObj.transform.SetParent(null, true); prefab有子物体的情况下加上
//确保有AudioSource组件
AudioSource audioSource = soundObj.GetComponent<AudioSource>();
if (audioSource == null)
audioSource = soundObj.AddComponent<AudioSource>();
});
//从池子里拿 粒子对象
PoolMgr.Instance.GetObj("Test/Quad", (GameObject newParticle) =>
{
newParticle.transform.position = transform.position;
//newParticle.transform.SetParent(null, true); prefab有子物体的情况下加上
});
}
}
3.4 同样, 实例化后的粒子和Audio Source需要被延迟销毁
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class DelayRemove : MonoBehaviour
{
private void OnEnable()
{
Invoke("RemoveMe", 1f);
}
private void RemoveMe()
{
PoolMgr.Instance.PushObj(this.gameObject);
}
}
最后, 该对象池可以扩展UI和敌人等.