Unity性能优化之内存管理的优化(内存管理性能增强之预制池)四

预制池系统
首先需要知道预制池系统需要做什么:

  1. 如果已经存在已回收的版本,应该重新生成第一个可用的对象
  2. 如果不存在已回收的版本,应该从预制体中实例化新的GameObject
  3. 在上述两种情况下, 都应该在附加到GameObject上的所有IPoolableComponent接口上调用Spawned方法
  4. 必须接受请求,以回收固定GameObject
  5. 如果对象由对象池系统管理,他应该禁用,并在附加到GameObject上的所有IPoolableComponent接口类上调用Despawned方法
  6. 如果对象没有由对象池系统管理,应该报错
    以上的需求简单明了,但是如果希望使解决方案的性能更好,则需要进行一些改进
    首先,对于主要的入口来说,典型的单例是一种非常好的选择,因为单例可以做到在任何地方,全局访问;
    生成的对象的主要任务包括接受一个预制引用,指出是否有回收的GameObject从相同的引用中实例化,为此,对象池系统需要为任何给定的预制体跟踪两种不同类型的列表,一个是已经激活的(实例化的),一个使未激活的(回收的)共两个对象列表,这种数据信息,最好抽象到一个类去管理,将其命名为PrefabPool;
    另一方面,由于生成的Gameobject需要给定一个预制体,我们将通过一个数据结构能够快速的将预制体映射到管理他们的PrefabPool,同时,由于回收对象需要给定一个Game Object,我们将通过另一个数据结构把已经生成的Gameobject映射到最初生成他们的PrefabPool,这样看来我们需要一对字典,分别用来管理生成与回收

在这里插入图片描述
接下来需要定义在生成对象发生了什么
在这里插入图片描述
接下来需要定义回收对象的时候需要做什么
在这里插入图片描述
现在已经有了可以自动处理多个预制池的系统,剩下的工作就是如何定义这个池子的行为,如前所述,Prefab类应该维护两个数据结构,一个用于在给定的Prefab中实例化活动(派生)对象,另一个用于非活动(回收的)对象;
从技术上来讲PrefabPoolingSystem1类已经维护了一个由PrefabPool管理的prefab的映射,所以实际上可以节省一点内存,因此这两个数据结构是PrefabPool需要跟踪的成员变量,但是由于每个GameObejct还必须要维护所有IPoolableComponent引用的列表,以便对他们调用Sapwned和Despawned方法,获取这些引用可能是在运行时执行的一个昂贵操作,所以最好将数据缓存在一个简单的数据结构中;如下
在这里插入图片描述
现在可以定义PrefabPool类的成员数据了
在这里插入图片描述
生成对象
接下来定义在池系统容器中生成GameObject意味着什么,在某个时刻,PrefabPool会收到一个请求,从给定的预制体中利用给定位置旋转,生成GameObject,首先应该要检查是否有该预制体的非激活实例,如果有,就可以将下一个可用对象移出队列,并重新生成它,如果没有,就需要使用GameObject.Instantiate从预制体中实例化新的GameObject,此时应该创建PoolablePrefabData对象,来保存GameObject引用,并获取附加在它上面,所有实现了IPoolableComponent的MonoBehaviour列表
下面的Spawn定义了这样的行为;
在这里插入图片描述
对象的回收
在这里插入图片描述
预制池测试
在这里插入图片描述
预先生成实例
在这里插入图片描述
预制池和场景加载
该系统还有一个重要的警告,由于PrefabPoolingSystem是静态类,它比场景的生命周期更长,这意味着当加载新场景时,池系统的字典尝试维护之前场景中已经池化的实例的引用,但unity在切换场景时强制销毁这些对象,而不管我们依然保有对他们的引用,因此字典将充满空引用,这将为下一个场景带来很多严重的问题,因此,应该在PrefabPoolingSystem中创建一个方法,类似于重置系统 ,并且在新的场景加载前调用,为下一个场景做好准备;
在这里插入图片描述

全部代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

public static class PrefabPoolingSystem1
{
    static Dictionary<GameObject, PrefabPool> _prefabTopoolMap = new Dictionary<GameObject, PrefabPool>();
    static Dictionary<GameObject, PrefabPool> _goToPoolMap = new Dictionary<GameObject, PrefabPool>();


    public static GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation)
    {
        if(!_prefabTopoolMap.ContainsKey(prefab)) //如果缓存中没有包含要生成的对象
        {
            _prefabTopoolMap.Add(prefab, new PrefabPool());
        }
        PrefabPool pool = _prefabTopoolMap[prefab];//获得对象所在的预制池
        GameObject go = pool.Spawn(prefab, position, rotation);//使用预制池生成预制
        _goToPoolMap.Add(go, pool);//将生成的预制体与预制池保存
        return go;
    }

    public  static GameObject Spawn(GameObject prefab) 
    {
        return Spawn(prefab, Vector3.zero, Quaternion.identity);
    }

    public static bool Despawn(GameObject prefab)
    {
        if(!_goToPoolMap.ContainsKey(prefab))
        {
            Debug.LogError(string.Format("Object {0} not managed by pool system!", prefab.name));
            return false;
        }
        PrefabPool pool = _goToPoolMap[prefab];
        if(pool.Despawn(prefab))
        {
            _goToPoolMap.Remove(prefab);
            return true;
        }
        return false;
    }
    public static void Prespawn(GameObject prefab, int numToSpawn)
    {
        List<GameObject> spawnedObjects = new List<GameObject>();

        for (int i = 0; i < numToSpawn; i++)
        {
            spawnedObjects.Add(Spawn(prefab));
        }

        for (int i = 0; i < numToSpawn; i++)
        {
            Despawn(spawnedObjects[i]);
        }

        spawnedObjects.Clear();
    }


    public static void Reset()
    {
        _prefabTopoolMap.Clear();
        _goToPoolMap.Clear();
    }
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

public struct PoolablePrefabData1
{
    public GameObject go;
    public IPoolableComponent[] poolableComponents;
}
class PrefabPool1
{
    Dictionary<GameObject, PoolablePrefabData1> _activeList = new Dictionary<GameObject, PoolablePrefabData1>();
    Queue<PoolablePrefabData1> _inactiveList = new Queue<PoolablePrefabData1>();

    public GameObject Spawn(GameObject prefab,Vector3 position,Quaternion rotation)
    {
        PoolablePrefabData1 data;
        if(_inactiveList.Count > 0)//如果存在已经回收的对象
        {
            data = _inactiveList.Dequeue();
        }
        else //如果不存在已经回收的对象
        {
            //那么就需要新实例化一个对象
            GameObject newGo = GameObject.Instantiate(prefab, position, rotation);//实例化
            data = new PoolablePrefabData1();
            data.go = newGo;
            data.poolableComponents = newGo.GetComponents<IPoolableComponent>();//需要获取所有IPoolableComponent引用的列表,以便调用Spawned和Despawned
        }
        //设置实例化物体的位置
        data.go.SetActive(true);
        data.go.transform.position = position;
        data.go.transform.rotation = rotation;

        for(int i = 0;i<data.poolableComponents.Length; i++)
        {
            data.poolableComponents[i].Spawned();//所有的实现了IPoolableComponent接口的都需要调用Spawned
        }
        _activeList.Add(prefab, data);

        return data.go;
    }

    public bool Despawn(GameObject objToDespawn)
    {
        if(!_activeList.ContainsKey(objToDespawn))//如果将要回收的对象没有在显示列表中,代表出错啦
        {
            Debug.LogError("This Object is not managed by this object pool!");
            return false;
        }

        PoolablePrefabData1 data = _activeList[objToDespawn];
        for(int i = 0;i <data.poolableComponents.Length;i++)//所有的实现了IPoolableComponent接口的都需要调用DesSpawned
        {
            data.poolableComponents[i].Despawned();
        }

        data.go.SetActive(false);
        _activeList.Remove(objToDespawn); //移除
        _inactiveList.Enqueue(data);//添加
        return true;
    }
}

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

public class PrefabPoolingTestInput : MonoBehaviour {
    [SerializeField] GameObject _orcPrefab;
    [SerializeField] GameObject _trollPrefab;
    [SerializeField] GameObject _ogrePrefab;
    [SerializeField] GameObject _dragonPrefab;

    List<GameObject> _orcs = new List<GameObject>();
    List<GameObject> _trolls = new List<GameObject>();
    List<GameObject> _ogres = new List<GameObject>();
    List<GameObject> _dragons = new List<GameObject>();

    void Start() {
        PrefabPoolingSystem1.Prespawn(_orcPrefab, 11);
        PrefabPoolingSystem1.Prespawn(_trollPrefab, 8);
        PrefabPoolingSystem1.Prespawn(_ogrePrefab, 5);
        PrefabPoolingSystem1.Prespawn(_dragonPrefab, 1);
    }

    void Update() {
        if (Input.GetKeyDown(KeyCode.Alpha1)) { SpawnObject(_orcPrefab, _orcs); }
        if (Input.GetKeyDown(KeyCode.Alpha2)) { SpawnObject(_trollPrefab, _trolls); }
        if (Input.GetKeyDown(KeyCode.Alpha3)) { SpawnObject(_ogrePrefab, _ogres); }
        if (Input.GetKeyDown(KeyCode.Alpha4)) { SpawnObject(_dragonPrefab, _dragons); }
        if (Input.GetKeyDown(KeyCode.Q)) { DespawnRandomObject(_orcs); }
        if (Input.GetKeyDown(KeyCode.W)) { DespawnRandomObject(_trolls); }
        if (Input.GetKeyDown(KeyCode.E)) { DespawnRandomObject(_ogres); }
        if (Input.GetKeyDown(KeyCode.R)) { DespawnRandomObject(_dragons); }
    }

    void SpawnObject(GameObject prefab, List<GameObject> list) {
        GameObject obj = PrefabPoolingSystem1.Spawn(prefab,5.0f * Random.insideUnitSphere,Quaternion.identity);
        list.Add(obj);
    }

    void DespawnRandomObject(List<GameObject> list) {
        if (list.Count == 0) {
            // Nothing to despawn
            return;
        }

        int i = Random.Range(0, list.Count);
        PrefabPoolingSystem1.Despawn(list[i]);
        list.RemoveAt(i);
    }
}
public interface IPoolableComponent {
    void Spawned();
    void Despawned();
}

预制池系统总结:
这个池系统为GameObject和预制的运行时内存分配问题提供了一个很好的解决方案,但是需要注意以下事项:

  1. 需要小心再重新生成的物体中正确的重置重要数据(刚体速度等)
  2. 必须确保不会预先生成太少或者太多的实例
  3. 应该小心IPoolableComponent中的Spawned和Despawned方法的执行顺序,不能假定他们以特定的顺序执行
  4. 在加载新场景的时候,必须要调用PrefabPoolingSystem1的Reset,以清除可能不再存爱的空引用对象

如果希望扩展这个系统,可以从下面的几个方面来做:

  1. 在GameObject初始化之后添加到GameObject上的IPoolableComponent不会触发他们的Spawned()或Despawned方法,因为值在GameObject首次初始化收集该列表,为了修复此问题,可以PrefabPool,在每次调用Spawned和Despawned时,获取IPoolableComponent引用,但是代价时生成和回收期间有额外的开销;
  2. 添加到预制根下的子节点的任何IPoolableComponent不会被同继,为了修复此问题,可以修改prefabPool来使用GetComponentsInchildren,代价是有额外的开销;
  3. 可以实现一种方法,让IPoolableComponent在获取期间设置优先级,并直接控制他们的Spawn和Despawned的方法的执行顺序;
  4. 这个系统不会将自己设置为DontDestroyOnLoad的预制实例友好的交互,明智的做法可能时在每个Spawn调用中添加一个布尔值,以判断加载新场景的时候,是否应该持久化;
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值