zoukankan      html  css  js  c++  java
  • Unity3D 基于预设(Prefab)的泛型对象池实现

    背景

    在研究Inventory Pro插件的时候,发现老外实现的一个泛型对象池,觉得设计的小巧实用,不敢私藏,特此共享出来。

    以前也看过很多博友关于对象池的总结分享,但是世界这么大,这么复杂到底什么样的对象池才是好的呢,我们发现通用的对象池未必适应所有的环境,比如基于UI的局部(从某个Scene,到某个Dialog)对象池,范围不同,需要的对象池就有不同的要求。本文就是介绍一种基于预设(Prefab)的局部UI对象池。

    通用信息提示窗口的实现http://www.manew.com/thread-94647-1-1.html

    声明:本文首发于蛮牛,次发博客园,本人原创。

    原文链接1http://www.manew.com/thread-94650-1-1.html,原文链接2

    演示

    左下角信息提示窗口NoticeUI中的信息提示比较多时,具有滚动条和超出自动隐藏的功能,就是通过对象池技术实现,从而提高性能和效率

    u1

    下面代码是在NoticeUI中 设置对象池的相关代码

    code1

    分析

    老规矩先上类图,不过其实没有什么好分析的,就是对象池的标准接口,取Get和回收这里是Destroy。还有出现两个类一个是泛型的,一个是非泛型的,至于为什么是结构体而不是类,这块我也没有深究。

    InventoryPool

    下面我们仔细分析下这个UI对象池的类,首先看下类的

    声明和构造函数

    class1

    1、类注释写的很清楚,告诉你这个对象池支持的对象是GameObjects,限制了必须是Unity3D的类型,该类的功能仅为了提高速度。

    它并不完善,建议使用小规模的数据。明白了这点很重要,也就是我在背景里说的每种场景使用的对象池是不一样的,一定不能混用和滥用。

    2、泛型结构体的声明,这里我们T限定了类型是UnityEngine.Component也就是Unity3D的组件基类,所以为什么注释里面说了只适合GameObjects了,剩下的就是new()关键字约束,确保类是可以new的。IPoolableObject是面向接口的一种实现约束(框架层考虑就是不一样),实际是强制要求在代码级别做好对象池类的管理。

    3、构造函数是用了缺省值StatSize64很贴心,下面三句初始化代码,创建了一个_PoolParent GameObject,并挂到了全局的组件树上,我觉得的这点特别重要,这使得在Edit下可以看见池对象的动态创建和状态,特别是让组件树特别清晰,点个赞,第三句,也就是保存了泛型对象

    poolParent = new GameObject("_PoolParent").transform;
    poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
    this.baseObject = baseObject;

     

    泛型池对象的初始化

    池对象初始化创建

    class2

    泛型池对象的初始化其实是调用了GameObject.Instantiate<T>的泛型方法重载,也就是深克隆。这也就是解释了为什么可以完成对于预设(Prefab)的对象池了,然后设置其父对象,挂接到组件树上,最后调用gameObject.SetActivie(false)让UI对象不可见,其实是一个开关。

    池对象的获取和销毁

    池对象的获取

    class3

    有了创建时候的开关,获取UI池对象就可以通过这个开关来判断对象是否已经使用了,然后如果active是false的,通过设置成true打开就完成了池对象的创建,这里createWhenNoneLeft参数默认是true,用来解决当构造函数预设池对象都被使用后如何扩展的问题,这里其实很简单了当所有预设都用完了直接再创建一个即可,多亏了c#的动态数组List<T>,不然还要像c++那样动态调整数组的大小。

    池对象的销毁

    class4

    对象的销毁其实是回收,这里对象最简单的一步就是SetActive(false)也就是把UI开关给关上,让对象不可见。其实重要的是调用IPoolObject接口的Reset函数实现,进行一些善后工作,让然也要通过 transform.SetParent()进行组件树的重置。所有对象的销毁和回收就很简单了只需要循环遍历销毁即可。

    核心源码

    using System;
    using System.Collections.Generic;
    using Devdog.InventorySystem;
    using Devdog.InventorySystem.Models;
    using UnityEngine;
    
    namespace Devdog.InventorySystem
    {
        /// <summary>
        /// Only supports GameObjects and no unique types, just to speed some things up.
        /// It's not ideal and I advice you to only use it on small collections.
        /// </summary>
        public struct InventoryPool<T> where T : UnityEngine.Component, IPoolableObject, new()
        {
            private List<T> itemPool;
            private Transform poolParent;
            private T baseObject;
    
            public InventoryPool(T baseObject, int startSize = 64)
            {
                poolParent = new GameObject("_PoolParent").transform;
                poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
                this.baseObject = baseObject;
    
                itemPool = new List<T>(startSize);
                for (int i = 0; i < startSize; i++)
                {
                    Instantiate();
                }
            }
    
    
            /// <summary>
            /// Create an object inside the pool
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="obj"></param>
            public T Instantiate()
            {
                var a = GameObject.Instantiate<T>(baseObject);
                a.transform.SetParent(poolParent);
                a.gameObject.SetActive(false); // Start disabled
    
                itemPool.Add(a);
                return a;
            }
        
    
            /// <summary>
            /// Get an object from the pool
            /// </summary>
            public T Get(bool createWhenNoneLeft = true)
            {
                foreach (var item in itemPool)
                {
                    if(item.gameObject.activeSelf == false)
                    {
                        item.gameObject.SetActive(true);
                        return item;
                    }
                }
    
                if (createWhenNoneLeft)
                {
                    Debug.Log("New object created, considering increasing the pool size if this is logged often");
                    return Instantiate();
                }
    
                return null;
            }
        
    
            /// <summary>
            /// Mark an object as inactive so it can be recycled.
            /// </summary>
            /// <param name="item"></param>
            public void Destroy(T item)
            {
                item.Reset(); // Resets the item state
                item.transform.SetParent(poolParent);
                item.gameObject.SetActive(false); // Up for reuse
            }
    
            public void DestroyAll()
            {
                foreach (var item in itemPool)
                    Destroy(item);
            }
        }
    
        /// <summary>
        /// InventoryPool only good for gameObjects
        /// </summary>
        public struct InventoryPool
        {
            private List<GameObject> itemPool;
            private Transform poolParent;
            private GameObject baseObject;
    
            public InventoryPool(GameObject baseObject, int startSize = 64)
            {
                poolParent = new GameObject("_PoolParent").transform;
                poolParent.SetParent(InventoryManager.instance.collectionObjectsParent);
                this.baseObject = baseObject;
    
                itemPool = new List<GameObject>(startSize);
                for (int i = 0; i < startSize; i++)
                {
                    Instantiate();
                }
            }
    
    
            /// <summary>
            /// Create an object inside the pool
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="obj"></param>
            public GameObject Instantiate()
            {
                GameObject a = null;
                if (baseObject != null)
                    a = GameObject.Instantiate<GameObject>(baseObject);
                else
                    a = new GameObject();
    
                a.transform.SetParent(poolParent);
                a.gameObject.SetActive(false); // Start disabled
    
                itemPool.Add(a);
                return a;
            }
    
    
            /// <summary>
            /// Get an object from the pool
            /// </summary>
            public GameObject Get(bool createWhenNoneLeft = true)
            {
                foreach (var item in itemPool)
                {
                    if (item.gameObject.activeInHierarchy == false)
                    {
                        item.gameObject.SetActive(true);
                        return item;
                    }
                }
    
                if (createWhenNoneLeft)
                {
                    Debug.Log("New object created, considering increasing the pool size if this is logged often");
                    return Instantiate();
                }
    
                return null;
            }
    
    
            /// <summary>
            /// Mark an object as inactive so it can be recycled.
            /// </summary>
            /// <param name="item"></param>
            public void Destroy(GameObject item)
            {
                item.transform.SetParent(poolParent);
                item.gameObject.SetActive(false); // Up for reuse
            }
    
    
            public void DestroyAll()
            {
                foreach (var item in itemPool)
                    Destroy(item);
            }
        }
    }
    View Code
  • 相关阅读:
    算法设计--从后向前处理
    野指针的危害究竟在哪里?
    printf参数的问题
    C++运算符详解问题详解
    java Clone 的心得记录
    拜占庭将军问题
    Java中的private protected public和default的区别
    Mybatis 学习笔记1 不整合Spring的方式使用mybatis
    Mybatis 使用maven插件mybatis-generator自动生成entities和SQL和mapper
    Mybatis 复习 Mybatis 配置 Mybatis项目结构
  • 原文地址:https://www.cnblogs.com/IlidanStormRage/p/5888146.html
Copyright © 2011-2022 走看看