通过使用PoolManager对象池和VFXManger构建可复用粒子系统 C#Unity

本文介绍了PoolManager,一种用于减少内存消耗的对象池机制,尤其适用于粒子系统等频繁创建和销毁的对象。文章还讨论了内存溢出和内存泄露的概念,并展示了如何通过实例ID、Prefab和订阅者模式来管理和使用PoolManager。
摘要由CSDN通过智能技术生成

前文

为何使用PoolManager?

PoolManager,中文名称,对象池。避免了在运行中反复创建GameObject和进行垃圾处理所造成的内存损耗,尤其是Particle这样的粒子系统,使用PoolManager可以有效的减少内存损耗,同时便于对Clone对象进行统一管理           

对象池(模式)一种创建式设计模式
概念:  

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer, 但给它存了long才能存下的数,那就是内存溢出。

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。 

若游戏中大量判定出现的粒子系统,且粒子效果在完成释放前不会销毁,大量的粒子Object同时运行时占用了大量内存,就会导致内存溢出,程序会报错,xxxx memory Allocated。

PoolManager
    一个构建可重复使用GameObject的对象池,采用Queue来实现不同GameObjectPrefab的内部排序,在PoolManager中构建对象池,指的是为每个Prefab
    构建一个专属的Pool Pool的Size 由Engine输入的SerializeField Private 提供,足以构建准确且可复用的对象管理池。Pool 的架构如下:
    

[System.Serializable]
    public struct Pool
    {
        public int poolSize;
        public GameObject prefab;
    }


    
   程序构建原理:
        使用GetInstanceID。GetInstaceID是gameobject的已架构函数,使用instanceID可以确保复制出来的ID完全可靠。一个Prefab具备唯一且独特的
        InstanceID,可以使用InstanceID检测GameObjet路径来源且确保使用完全一样的GameObject,为对象池的内容物提升安保性。

        构建PoolDictionary 使用InstanceID作为Key,Queue<GameObject>作为value,如下:
         

​
  private Dictionary<int,Queue<GameObject>> poolDictionary=new Dictionary<int, Queue<GameObject>>();

​


        这样就完成了顶层架构。

        ReUseGameObject,CreatePool在数据结构层级上进行操作。


VFXManger
   控制粒子的Manger 通过订阅者模式来调用ParticleGameObject(粒子生成器)

   先写订阅者模式:   ///此处为一个外置的EventHandlerClass
   可以直接在外部调用(类和函数的可访问区域均为public static)

   public static EventHandler
   {
        public static event Action ParticleUse;

        public static void CallParticleUse
        {   
            //确定此处为非空
            if(ParticleUse!=null)
            {
                //使用ParticleUse
                ParticleUse();
            }
        }
   }

   然后,在VFX中使用固有函数,将event需要使用的函数添加入其中
   

private void OnEnable()
   {
        Eventhanlder.ParticleUse+=目的函数;
   }
   private void OnDisabled()
   {
        EventHandler.ParticleUse-=目的函数;
   }


    最后在需要调用ParticleGameObject的地方使用EventHandler.CallParticleUse()来调用VFXmanger中添加的粒子效果。

下方为完整代码

单例模式 泛型

当某个类继承于单例SingletonMonoBehaviour时,可以直接调用Instance获得唯一的StaticT,可直接调用
   

using UnityEngine;

public class SingletonMonoBehaviour<T> : MonoBehaviour where T:MonoBehaviour 
{   
    //static 确保了这个的这个函数只有这一个内存空间,可见Test函数及其输出
    private static T instance;
    public static T Instance
    {
        get{
            return instance;
        }
    }
   
//使用虚函数protected virtual 便于继承子类重写函数Awake
    protected virtual void Awake() 
    {
        if(instance==null)
        {
            instance=this as T;
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

订阅者模式

using UnityEngine;
using System.Collections.Generic;
using System;

public static class EventHandler 
{
    public static event Action<GameObject,Vector3,Quaternion> ParticleUse;

    public static void CallParticleUse(GameObject particleGameObject,Vector3 VFXPosition,Quaternion VFXQuternion) 
    {
        if(ParticleUse!=null) 
        {
            ParticleUse(particleGameObject,VFXPosition,VFXQuternion);
        }
    }     
}

Test

用于说明单例泛型的原理

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{   
    public static int TestValue;
    private void Awake()
    {
        if(this.gameObject.name=="Test1")
        {
            TestValue=1;
        }
    }
    private void Start() 
    {
        if(this.gameObject.name=="Test2")
        {
            Debug.Log("Test2Debug"+"    "+TestValue);
        }
    }
}

构建两个TestGameObject并挂载Test

ConSole输出结果如下,可见,在一个类中static变量属于一个唯一的内存空间所有基于此类构建的对象共享同一个static param

PoolManager

用于存储Pool,管理pool和生成PoolDictionary

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PoolManger : SingletonMonoBehaviour<PoolManger>
{
    [System.Serializable]
    public struct Pool 
    {
        public int PoolSize;
        public GameObject PoolPrefab;
    }
    
    [SerializeField]
    private List<Pool> poolList=new List<Pool>();
    [SerializeField]
    private Transform poolParentTransform;
    private Dictionary<int,Queue<GameObject>> poolDicitonary=new Dictionary<int, Queue<GameObject>>();

    private void Start() 
    {
        CreatePoolDictionary();
    }
    
    //通过外部导入的List<Pool>来构建poolDictionary//
    private void CreatePoolDictionary()
    {
        foreach(Pool pool in poolList)
        {
            if(pool.PoolSize!=0&&pool.PoolPrefab!=null)
            {
                int poolKey=pool.PoolPrefab.GetInstanceID();

                if(!poolDicitonary.ContainsKey(poolKey))
                {      
                    InaugurateNewPlaceInPoolDictionary(poolKey,pool);
                }
            }
        }
    }
    
    //为每个 poolkey 开辟专属Place 
    private void InaugurateNewPlaceInPoolDictionary(int poolKey,Pool pool)
    {   
        poolDicitonary.Add(poolKey,new Queue<GameObject>());
        
        GameObject Anchor=new GameObject(pool.PoolPrefab.name+"Anchor");
        //Make the newAnchor under the ParentTransform
        Anchor.transform.SetParent(poolParentTransform);

        for(int i=1;i<=pool.PoolSize;i++)
        {
            GameObject newPoolGameObject=Instantiate(pool.PoolPrefab,poolParentTransform.position,Quaternion.identity,Anchor.transform);
            newPoolGameObject.SetActive(false);
            poolDicitonary[poolKey].Enqueue(newPoolGameObject);
        }
    }

    public GameObject ReUsePoolGameObject(GameObject poolPrefab,Vector3 usePosition,Quaternion useQuaternion)
    {   

        int poolKey=poolPrefab.GetInstanceID();
        if(poolDicitonary.ContainsKey(poolKey))
        {
            Queue<GameObject> exactQueue=poolDicitonary[poolKey];

            GameObject poolTop=exactQueue.Dequeue();
            if(poolTop.activeSelf==true)
                poolTop.SetActive(false);
            ResetPoolObject(poolTop,usePosition,useQuaternion);
            exactQueue.Enqueue(poolTop);
            return poolTop;
        }
        else
        {
            Debug.Log("there is no value of this key");
            return null;
        }
    }

    private void ResetPoolObject(GameObject poolTop,Vector3 usePosition,Quaternion useQuaternion)
    {
        poolTop.transform.position=usePosition;
        poolTop.transform.rotation=useQuaternion;
        poolTop.SetActive(true);
    }
    
}

VFXManager

用于委托挂载,并且利用异步协程将VFXSetFalse

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VFXManger : SingletonMonoBehaviour<VFXManger>
{
    [SerializeField] GameObject ToReuseVFXGameObjet;
    [SerializeField] private WaitForSeconds temporaryWaitSeconds;
    private bool Judge=true;

    override protected void Awake() 
    {
        base.Awake();

        temporaryWaitSeconds=new WaitForSeconds(2f);
    }
    private IEnumerator DisableTemporaryVFX(GameObject temporaryVFX,WaitForSeconds secondsToWait)
    {
        yield return secondsToWait;
        temporaryVFX.SetActive(false);
    }

    public void DisPlayVFX(GameObject VFXGameObject,Vector3 VFXPosition,Quaternion VFXQuternion)
    {
        if(Judge)
        {   
            GameObject temporaryVFX;
            temporaryVFX=PoolManger.Instance.ReUsePoolGameObject(ToReuseVFXGameObjet,VFXPosition,VFXQuternion);
            StartCoroutine(DisableTemporaryVFX(temporaryVFX,temporaryWaitSeconds));
        }
    }

    private void OnEnable() 
    {
        EventHandler.ParticleUse+=DisPlayVFX;    
    }
    
    private void OnDisable() 
    {
        EventHandler.ParticleUse-=DisPlayVFX;    
    }
}

PlayerTest

用于使用委托
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerTest : MonoBehaviour
{   
    [SerializeField] private GameObject ReUsePoolPrefab=null;
    private void Update() 
    {
        if(Input.GetMouseButtonDown(1))
        {
            Vector3 mouseScreenPosition=Input.mousePosition;
            Vector3 mouseWorldPosition=Camera.main.ScreenToWorldPoint(new Vector3(mouseScreenPosition.x,mouseScreenPosition.y,-Camera.main.transform.position.z));
            // PoolManger.Instance.ReUsePoolGameObject(ReUsePoolPrefab,mouseWorldPosition,Quaternion.identity);
            EventHandler.CallParticleUse(ReUsePoolPrefab,mouseWorldPosition,Quaternion.identity);
        }
    }
}

  效果如下:

         

康娜爆破

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值