Unity优化篇:对象池的创建与使用。(简单且实用)

1.对象池是什么?

对象池是一种Unity经常用到的内存管理服务,它的作用在于可以减少创建每个对象的系统开销。

2.为什么要使用对象池?

在Unity游戏开发的过程中经常会创建一些新的对象,如果数量较少还可以接受,如果创建的新对象数量庞大,那么对内存而言是一个极大的隐患。例如射击游戏当中,每发射一颗子弹,都要创建一个新的子弹对象,那么子弹是数量庞大,可想而知一场游戏当中会创建多少这样的新对象,那么如果这些子弹创建之后都对游戏起着关键且持续性的作用也无可厚非,问题是子弹发射完成之后,几秒之后就不再拥有任何的意义,一般会将它自动的隐藏,也就是我们所说的SetActive(false),因此大量的非活跃对象出现在游戏场景当中。

3.怎么创建并使用对象池?

对象池背后的理念其实是非常简单的。我们将对象存储在一个池子中,当需要时在再次使用,而不是每次都实例化一个新的对象。池的最重要的特性,也就是对象池设计模式的本质是允许我们获取一个“新的”对象而不管它真的是一个新的对象还是循环使用的对象。

我们需要两个字典,一个动态存储和去除当前所需要的游戏物体,另一个当记事本,记录那些游戏物体被记录过,那些没有,并且据此来判定是否需要创建新的对象池。

下面是对象池的代码,(参考https://www.cnblogs.com/yugejuhao/p/7677497.html)

创建对象池:

   我修改了一下GetObj函数,让它能在指定位置生成游戏物体。

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

public class ObjectPool
{
    public const string stoneExplosion = "done_explosion_asteroid";
    public const string enemyExplosion = "done_explosion_enemy";
    public const string playerExplosion= "done_explosion_player";
    public const string redStone = "Asteroid_Lrg_B_01";
    public const string boss1 = "Boss1";
    public const string shuijing = "Crystal_Lrg_A_01";
    public const string blackStone = "Done_Asteroid 02";
    public const string playerAttack = "Done_Bolt";
    public const string enemyAttack = "Done_Bolt-Enemy";
    public const string player = "Done_Player";
    public const string purpleEnemy = "Done_PurpleEnemy";
    public const string redEnemy = "Done_RedEnemy";

    /// 
    /// 对象池
    /// 
    private Dictionary> pool;

    /// 
    /// 预设体
    /// 
    private Dictionary prefabs;
    #region 单例
    private static ObjectPool instance;
    private ObjectPool()
    {
        pool = new Dictionary>();
        prefabs = new Dictionary();
    }
    public static ObjectPool GetInstance()
    {
        if (instance == null)
        {
            instance = new ObjectPool();
        }
        return instance;
    }
    #endregion


    /// 
    /// 从对象池中获取对象
    /// 
    /// 
    /// 
    public GameObject GetObj(string objName,Vector3 position,Quaternion quaternion)
    {
        //结果对象
        GameObject result = null;
        //判断是否有该名字的对象池
        if (pool.ContainsKey(objName))
        {
            //对象池里有对象
            if (pool[objName].Count > 0)
            {
                //获取结果
                result = pool[objName][0];
                //激活对象
                result.transform.position = position;
                result.transform.rotation = quaternion;
                result.SetActive(true);
                //从池中移除该对象
                pool[objName].Remove(result);
                //返回结果
                return result;
            }
        }
        //如果没有该名字的对象池或者该名字对象池没有对象

        GameObject prefab = null;
        //如果已经加载过该预设体
        if (prefabs.ContainsKey(objName))
        {
            prefab = prefabs[objName];
        }
        else     //如果没有加载过该预设体
        {
            //加载预设体
            prefab = Resources.Load("Prefabs/" + objName);
            //更新字典
            prefabs.Add(objName, prefab);
        }

        //生成
        result = Object.Instantiate(prefab);
        result.transform.position = position;
        result.transform.rotation = quaternion;
        //改名(去除 Clone)
        result.name = objName;
        //返回
        return result;
    }

    /// 
    /// 回收对象到对象池
    /// 
    /// 
    public void RecycleObj(GameObject obj)
    {
        //设置为非激活
        obj.SetActive(false);
        //判断是否有该对象的对象池
        if (pool.ContainsKey(obj.name))
        {
            //放置到该对象池
            pool[obj.name].Add(obj);
        }
        else
        {
            //创建该类型的池子,并将对象放入
            pool.Add(obj.name, new List() { obj });
        }

    }

}



 

使用对象池:

           把用到Instantiate的地方替换为GetObj,

           把用到Destroy的地方替换为RecycleObj,

注意点:

1.由于对象池是采用将游戏物体的状态设置为true和false来实现目的,所以有些游戏物体挂载的脚本的Start()函数和Awake()函数需要根据情况来更改一下。(可以考虑一下OnEnable,OnDisable

2.正如这位大神所说https://www.cnblogs.com/mezero/p/3955130.html

  • 很多类型的对象被重新使用前,在某些情况下,需要被reset。至少,所有的成员变量都要设置成初始值。这可以在池中实现而不需要用户处理。何时和如何重置需要考虑以下两个方面:
    • 重置是立即的(例如,在存储对象时即重置)还是延迟的(例如,在对象被重新使用后重置)。
    • 重置是被池管理(例如,对于被放入池中的对象来说是透明的)还是声明池对象的类。
  • 创建管理所有类型池的ObjectPool。
  • 某些类型的资源是很珍贵的(如数据库连接),池需要显示上限并提供一个针对分配对象失败的安全措施;
  • 当池中对象很多却很少使用时,或许需要收缩的功能(不管是自动的还是强制的)。
  • 最后,池可以被多个线程共享,因此需要实现为线程安全的。
  • 那么其中那些是必需的呢?你的答案或许和我的不一样,但请允许我阐述我的观点:

  • 重置是必需的。但是正如你将在下面看的那样,我并没有强制到底是在池中还是被管理类中处理重置逻辑。你可能两种都需要,之后的代码中我将向你展示各自两个版本。
  • Unity强制限制多线程。你可以在主线程中定义工作者线程,但只有主线程可以调用Unity API。以我的经验看来,我们并不需要将池实现为支持多线程。
  • 仅个人而言,我并不介意每次为一个类型申明一个新的池。可选的方案是采用单例模式:创建一个新的对象池并放置于存储池的字典中,该字典放置在一个静态变量中。为了安全使用,你需要将将你的对象池实现为支持多线程。但就我看到的对象池而言没有一个是100%安全的。
  • 在本篇文章中我重点处理内存。其它类型资源池也是很重要的,但超出本篇文章的范围。这很大程度上减少了以下的需求:
    • 不需要一个作限制用的最大值。如果你的游戏使用太多的资源,你已经陷入麻烦了,对象池也救不了你。
    •  我们也可以假设没有其它进程等待你尽快释放内存。这就意味着重置可以是延迟的,也不需要提供收缩功能。

总结:本篇所说的对象池是非常简单且基础的。适用于游戏物体频繁生成与销毁项目。如果有复杂的需求,这个方法肯定不适用。仅供参考。

你可能感兴趣的:(Unity相关)