Unity中简易对象池 / 缓存池技术

缓存池: 通过重复利用已经创建的对象,避免频繁的创建和销毁, 减少系统的内存分配和垃圾回收所带来的开销.

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和敌人等.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值