您现在的位置是:首页 >其他 >【Unity Optimize】使用对象池(Object Pooling)优化项目网站首页其他

【Unity Optimize】使用对象池(Object Pooling)优化项目

STARBLOCKSHADOW 2024-06-17 10:24:24
简介【Unity Optimize】使用对象池(Object Pooling)优化项目

1 对象池(Object Pooling)介绍

Unity中的对象池(Object Pooling)是一种用于提高游戏性能和减少资源浪费的优化方案。尤其是在需要快速创建和销毁游戏对象时,比如在一些射击游戏中发射子弹时。

对象池在游戏运行前预先创建一定数量的对象,将它们存储在一个重用字典(或者数组)中,在需要时只是激活或停用所需的游戏对象,实际上只是循环使用对象,而不是使用原生的Instantiate和Destroy方法创建和销毁对象。我们将这个存储所有激活或非激活对象的字典(或者数组)称为池子。这样可以大大减少频繁创建和销毁对象的开销,并且可以减少内存分配和垃圾回收,提高游戏的运行效率。

2 实现对象池脚本

实现一个对象池脚本ObjectPool.cs,利用序列化的游戏对象预制体来快速生成和回收大量相同类型的物体。

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 此类实现了对象池模式,用于创建和管理大量的游戏对象
/// </summary>
public class ObjectPool
{
    private readonly GameObject prefab; // 预制体对象,用来创建新的游戏对象
    private readonly int initialPoolSize; // 初始对象池大小,即预先生成的游戏对象数量
    private readonly bool canGrow; // 是否可以动态扩展对象池大小
    private readonly List<GameObject> pool = new List<GameObject>(); // 游戏对象池

    // 单例对象,用于全局访问对象池
    public static ObjectPool Instance;

    // 私有无参构造函数,避免从外部创建对象池实例
    private ObjectPool()
    {
        this.prefab = null;
        this.initialPoolSize = 0;
        this.canGrow = false;
    }

    // 构造函数,创建对象池实例,并初始化预制体对象、对象池大小、是否可扩展等参数
    public ObjectPool(GameObject prefab, int initialPoolSize, bool canGrow)
    {
        this.prefab = prefab;
        this.initialPoolSize = initialPoolSize;
        this.canGrow = canGrow;

        InitializePool(); // 初始化对象池
    }

    // 从对象池中获取未被使用的游戏对象,如果对象池已满并且可以扩展,将会创建新的游戏对象
    public GameObject GetObject()
    {
        GameObject obj = pool.Find(o => !o.activeSelf); // 查找未被激活的游戏对象
        if (obj == null && canGrow) // 如果对象池满了并且可以扩展
        {
            obj = AddObjectToPool(); // 创建新的游戏对象
        }

        if (obj != null)
        {
            obj.SetActive(true); // 激活游戏对象
        }

        return obj;
    }

    // 将游戏对象返回到对象池中
    public void ReturnObject(GameObject obj)
    {
        obj.SetActive(false); // 取消游戏对象的激活状态
    }

    // 初始化对象池,创建一定数量的游戏对象并加入到游戏对象池中
    private void InitializePool()
    {
        for (int i = 0; i < initialPoolSize; i++)
        {
            GameObject obj = GameObject.Instantiate(prefab); // 创建新的游戏对象
            obj.SetActive(false); // 初始时将游戏对象设置为未激活状态
            pool.Add(obj); // 将游戏对象加入到对象池中
        }
    }

    // 向对象池中添加新的游戏对象
    private GameObject AddObjectToPool()
    {
        GameObject obj = GameObject.Instantiate(prefab); // 创建新的游戏对象
        obj.SetActive(false); // 初始时将游戏对象设置为未激活状态
        pool.Add(obj); // 将游戏对象加入到对象池中
        return obj;
    }
}

3 使用对象池生成Cube

使用上述的对象池,编写脚本NewCube.cs,在鼠标左键点击位置生成Cube,点击鼠标右键可以销毁一个个之前生成的Cube,按键盘的X键可以销毁所有的cube。

using System.Collections.Generic;
using UnityEngine;

public class NewCube : MonoBehaviour
{
    [SerializeField]
    private GameObject cubePrefab; // Cube预制体
    [SerializeField]
    private float spawnHeight = 10f; // 在鼠标落下的位置生成Cube时使用的高度值
    [SerializeField] 
    private int initialPoolSize = 20; // 初始化对象池时的初始大小
    [SerializeField] 
    private bool canGrow = true; // 当对象池中的对象数量达到最大值时,canGrow 为 true 的话会增加对象池的大小

    private GameObject currentCube; // 当前的Cube游戏对象
    private Vector3 lastCubePosition; // 上一个Cube的位置
    private List<GameObject> cubesList = new List<GameObject>(); // 用于记录所有的Cube
    

    private void Start()
    {
        ObjectPool.Instance = new ObjectPool(cubePrefab, initialPoolSize, canGrow);
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0)) // 鼠标左键按下
        {
            SpawnCube();
        }
        else if (Input.GetMouseButtonDown(1)) // 鼠标右键按下
        {
            DeleteLastCube();
        }

        if (Input.GetKeyDown(KeyCode.X)) // X键按下
        {
            ClearCubes();
        }
    }

    //生成Cube对象
    private void SpawnCube()
    {
        Vector3 mousePos = Input.mousePosition;
        mousePos.z = spawnHeight;
        Vector3 spawnPos = Camera.main.ScreenToWorldPoint(mousePos);

        if (currentCube != null)
        {
            lastCubePosition = currentCube.transform.position;
        }

        currentCube = ObjectPool.Instance.GetObject();
        currentCube.transform.position = spawnPos;
        currentCube.SetActive(true);

        Rigidbody rb = currentCube.GetComponent<Rigidbody>();
        rb.velocity = Vector3.zero;
        rb.useGravity = true;

        lastCubePosition = currentCube.transform.position;

        cubesList.Add(currentCube); // 将当前Cube添加到列表中
    }

    // 删除上一个生成的 Cube 对象
    private void DeleteLastCube()
    {
        if (cubesList.Count > 0)
        {
            ObjectPool.Instance.ReturnObject(cubesList[cubesList.Count - 1]);
            cubesList.RemoveAt(cubesList.Count - 1); // 从列表中删除最后一个Cube
        }

        if (cubesList.Count > 0)
        {
            lastCubePosition = cubesList[cubesList.Count - 1].transform.position;
        }
        else
        {
            lastCubePosition = Vector3.zero;
        }
    }

    // 销毁所有的Cube对象
    private void ClearCubes()
    {
        int childCount = cubesList.Count;

        for (int i = childCount - 1; i >= 0; i--)
        {
            GameObject obj = cubesList[i].gameObject;
            ObjectPool.Instance.ReturnObject(obj);
        }

        currentCube = null;
    }
}

4 效果展示

首先,在Unity中在初始位置(0,0,0)处新建一个平面,适当调大其Scale的X值和Z值;接着,新建一个Cube物体,为其添加上Rigidbody组件(重力效果)并将其做成预制体;在场景中新建一个空物体,挂载NewCube脚本后,将做好的Cube预制体拖拽至其CubePrefab参数处。

在这里插入图片描述

视频演示:

5 Unity资源商店的对象池插件

Unity资源商店中已封装好的对象池插件:Lean Pool
也可以直接使用Unity内置的对象池API,使用以下命名空间引入:

using UnityEngine.Pool;
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。