Comprensión y uso simples del modo de grupo de objetos (implementado en unidad)

Uno, el concepto de grupo de objetos

El modelo de grupo de objetos no es un modelo de diseño único para el desarrollo de juegos. Sus ideas de diseño son las mismas que las de los grupos de conexiones de bases de datos y los grupos de subprocesos en otros desarrollos.

La idea central no es eliminarlo directamente después de su uso, sino volver a ponerlo en la piscina y sacarlo cuando sea necesario. La aparición del modo de grupo de objetos optimiza principalmente dos puntos:

1. Evite que los objetos se creen y eliminen con frecuencia, lo que da como resultado una fluctuación de la memoria y GC frecuente (recolección de basura)

2. El costo de inicialización del objeto es alto

 

Pero debido a que los objetos del desarrollo de software tradicional suelen ser de peso ligero a medio, la sobrecarga de asignar / liberar objetos es insignificante, por lo que hay relativamente pocas aplicaciones de grupos de objetos ingenuos en el desarrollo de software tradicional. Generalmente, los objetos específicos están optimizados para ②, como el grupo de conexiones de la base de datos y el grupo de subprocesos, lo que resuelve la reutilización y el número de conexiones al mismo tiempo. Pero en el proceso de desarrollo del juego, debido a que muchos objetos del juego se crean y eliminan con frecuencia, y los objetos del juego contienen muchos objetos, incluso la tecnología de grupo de objetos simple tiene más escenarios de aplicación.

Dos, operaciones de grupo de objetos

La siguiente es una introducción textual a las operaciones básicas del grupo de objetos.

Préstamo: En términos sencillos, es obtener un objeto del grupo, si es la primera vez que se obtiene un objeto, el grupo debe inicializarse. Si no hay más objetos en el grupo, cree uno

Devolución: En términos sencillos, el elemento se usó originalmente para ser eliminado, pero después de que se aplica el grupo de objetos, el objeto se devuelve al grupo, siempre que el número en el grupo no sea mayor que el número máximo preestablecido (para evitar demasiada memoria por explosión), si el número en el grupo es mayor que el número máximo preestablecido, elimínelo directamente

Calentamiento: Significa precargar una cierta cantidad de objetos. Personalmente, creo que esta es una de las partes más esenciales del grupo de objetos. Si no realiza el precalentamiento, la primera vez que cree un objeto, involucrará directamente la inicialización. Es fácil entender que los jugadores prefieren esperar 1 segundo más en la interfaz de carga, en lugar de detenerse durante 0.1 segundos en el juego. Especialmente en juegos competitivos, los jugadores querrán aplastar la computadora (risas). Así que creo que si no realiza la optimización del grupo de objetos de calentamiento, solo la mitad estará lista.

Reducción: es casi como calentar al revés. Al regresar, dijo que si excede el umbral establecido, no se devolverá al grupo, sino que se eliminará directamente, pero de hecho, la eliminación también puede traer costos de tiempo. para que podamos eliminarlo primero, y luego eliminar y reducir el grupo de memoria cada vez que pasas por la interfaz de carga en el medio del juego. Si tiene miedo de que la memoria se rompa antes de cargar la interfaz, puede establecer un umbral adicional que debe eliminarse, y su efecto es el mismo que el escrito anteriormente al regresar. (No hice esta función en mi DEMO)

Restablecer: Cada objeto recién creado debe ser el mismo que el recién creado cuando es "nuevo", y obviamente no puede tener el estado del último utilizado, por lo que cada vez que el objeto sale del grupo, debe tratarse con efectos posteriores. Coloque el reinicio. En unidad, el contenido de la inicialización manual del objeto se escribe en el OnEnable () del objeto, incluida la fuerza para despejar el cuerpo rígido, etc. La diferencia entre OnEnable () y Start () es que Start () es sólo el primero cuando el objeto se habilita por primera vez. Frame run, OnEnable se ejecutará cada vez que el objeto se vuelva a habilitar.

Tres experimentos específicos

El siguiente es un pequeño experimento de demostración que hice yo mismo. Debido a que es solo un pequeño experimento de demostración, no es muy robusto. También lo expresé en los comentarios, así que si se trata de integridad y solidez, no se queje demasiado. . Pero si hay un problema de principios en mi código que conduce a una optimización importante, por favor, indíquelo y aprenda.

A continuación se muestra el script de ObjectPool.Las funciones están diseñadas para que se parezcan más a las instancias y destrucciones nativas de Unity.

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

public class ObjectPool : MonoBehaviour
{
    //自身单例
    public static ObjectPool me;

    //池的存储
    //TODO 此处value使用自定义封装类型而不是单纯的queue更健全
    private Dictionary<string, Queue<GameObject>> pool;

    //每个池中最大数量
    //TODO 应该每个池设置每个池单独的数量
    private int maxCount = int.MaxValue;
    public int MaxCount
    {
        get { return maxCount; }
        set
        {
            maxCount = Mathf.Clamp(value, 0, int.MaxValue);
        }
    }

    //初始化
    void Awake()
    {
        me = this;
        pool = new Dictionary<string, Queue<GameObject>>();

    }

    /// <summary>
    /// 从池中获取物体
    /// </summary>
    /// <param name="go">需要取得的物体</param>
    /// <param name="position"></param>
    /// <param name="rotation"></param>
    /// <returns></returns>
    public GameObject GetObject(GameObject go,Vector3 position,Quaternion rotation)
    {
        //如果未初始化过 初始化池
        if(!pool.ContainsKey(go.name))
        {
            pool.Add(go.name, new Queue<GameObject>());
        }
        //如果池空了就创建新物体
        if(pool[go.name].Count == 0)
        {
            GameObject newObject = Instantiate(go, position, rotation);
            newObject.name = go.name;/*
            确认名字一样,防止系统加一个(clone),或序号累加之类的
            实际上为了更健全可以给每一个物体加一个key,防止对象的name一样但实际上不同
             */

            return newObject;
        }
        //从池中获取物体
        GameObject nextObject=pool[go.name].Dequeue();
        nextObject.SetActive(true);//要先启动再设置属性,否则可能会被OnEnable重置
        nextObject.transform.position = position;
        nextObject.transform.rotation = rotation;
        return nextObject;
    }

    /// <summary>
    /// 把物体放回池里
    /// </summary>
    /// <param name="go">需要放回队列的物品</param>
    /// <param name="t">延迟执行的时间</param>
    /// TODO 应该做个检查put的gameobject的池有没有创建过池
    public void PutObject(GameObject go,float t)
    {
        if (pool[go.name].Count >= MaxCount)
            Destroy(go,t);
        else
            StartCoroutine(ExecutePut(go,t));
    }

    private IEnumerator ExecutePut(GameObject go, float t)
    {
        yield return new WaitForSeconds(t);
        go.SetActive(false);
        pool[go.name].Enqueue(go);
    }

    /// <summary>
    /// 物体预热/预加载
    /// </summary>
    /// <param name="go">需要预热的物体</param>
    /// <param name="number">需要预热的数量</param>
    /// TODO 既然有预热用空间换时间 应该要做一个清理用时间换空间的功能
    public void Preload(GameObject go,int number)
    {
        if (!pool.ContainsKey(go.name))
        {
            pool.Add(go.name, new Queue<GameObject>());
        }
        for (int i = 0; i < number; i++)
        {
            GameObject newObject = Instantiate(go);
            newObject.name = go.name;//确认名字一样,防止系统加一个(clone),或序号累加之类的
            newObject.SetActive(false);
            pool[go.name].Enqueue(newObject);
        }
    }

}

Luego está el script de prueba GameManager

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

public class GameManager : MonoBehaviour
{

    public GameObject testObject;
    // Start is called before the first frame update
    void Start()
    {
        //预热
       ObjectPool.me.Preload(testObject, 500);
    }

    //无对象池测试
    public void TestOfNotOP()
    {
        StartCoroutine(CreateOfNotOP());
    }


    private IEnumerator CreateOfNotOP()
    {
        //统计500帧所用时间
        float t = 0.0f;
        //每一帧生成一个对象,定时2秒后自动消除
        for (int i = 0; i < 500; i++)
        {
            int x = Random.Range(-30, 30);
            int y = Random.Range(-30, 30);
            int z = Random.Range(-30, 30);
            GameObject newObject=Instantiate(testObject, new Vector3(x, y, z),Quaternion.identity);
            Destroy(newObject, 2.0f);

            yield return null;
            t += Time.deltaTime;
        }
        Debug.Log("无对象池500帧使用秒数:"+t);
    }

    //使用对象池测试
    public void TestOfOP()
    {

        StartCoroutine(CreateOfOP());
    }

    private IEnumerator CreateOfOP()
    {
        //统计500帧所用时间
        float t = 0.0f;
        //每一帧生成一个对象,定时2秒后自动消除
        for (int i = 0; i < 500; i++)
        {
            int x = Random.Range(-30, 30);
            int y = Random.Range(-30, 30);
            int z = Random.Range(-30, 30);
            GameObject newObject = ObjectPool.me.GetObject(testObject, new Vector3(x, y, z), Quaternion.identity);
            ObjectPool.me.PutObject(newObject, 2.0f);
            yield return null;
            t += Time.deltaTime;
        }
        Debug.Log("使用对象池500帧使用秒数:"+t);
    }
}

Hice dos botones en un Canvas, y luego hice un GameObject vacío usado como GameManager, ObjectPool enlazado (script de grupo de objetos) y GameManager (script de prueba), dejé que los eventos OnClick de los dos botones escucharan TestOfNotOP respectivamente () y TestOfOP ()

Finalmente, use un efecto de partículas descargado de la tienda de recursos para probar

(Vea mi estación B para un video de prueba detallado)

Se puede ver que reproducir 500 fotogramas (se genera un efecto de partículas por fotograma) es de aproximadamente 9,8 a 9,9 segundos si no se usa el grupo de objetos, y reproducir 500 fotogramas después de aplicar el grupo de objetos es de aproximadamente 9,4 a 9,5 segundos. Todavía hay resultados optimizados. Si es la versión anterior de unity, puede estar más optimizada. Esto fue hace mucho tiempo, Yusong también se quejó de http://www.xuanyusong.com/archives/2925 Ahora la inicialización de efectos de partículas de unidad Debería estar optimizado. En general, se deben medir todas las técnicas de optimización, los costos de optimización y los resultados.

Supongo que te gusta

Origin blog.csdn.net/sun124608666/article/details/112602188
Recomendado
Clasificación