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中的信息提示比较多时,具有滚动条和超出自动隐藏的功能,就是通过对象池技术实现,从而提高性能和效率

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

分析

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

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

声明和构造函数

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;

泛型池对象的初始化

池对象初始化创建

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

池对象的获取和销毁

池对象的获取

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

池对象的销毁

对象的销毁其实是回收,这里对象最简单的一步就是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);
        }
    }
}

时间: 2024-08-02 22:34:41

Unity3D 基于预设(Prefab)的泛型对象池实现的相关文章

探索对象池技术

对象池技术是一种常见的对象缓存手段.’对象’意味着池中的内容是一种结构化实体,这也就是一般意义上面向对象中的对象模型:’池’(或动词池化)意味着将有生命周期的对象缓存到’池子’中进行管理,即用即取.缓存的目的大多是为了提升性能,对象池技术的目的也即如此.所以,对象池技术的本质简单来说就是:将具有生命周期的结构化对象缓存到带有一定管理功能的容器中,以提高对象的访问性能. 处理网络连接是对象池使用最多的场景.比如一些RPC框架的NettyChannel缓存(如motan),以及数据库连接池的Conn

大数据技术之_27_电商平台数据分析项目_02_预备知识 + Scala + Spark Core + Spark SQL + Spark Streaming + Java 对象池

第0章 预备知识0.1 Scala0.1.1 Scala 操作符0.1.2 拉链操作0.2 Spark Core0.2.1 Spark RDD 持久化0.2.2 Spark 共享变量0.3 Spark SQL0.3.1 RDD.DataFrame 与 DataSet0.3.2 DataSet 与 RDD 互操作0.3.3 RDD.DataFrame 与 DataSet 之间的转换0.3.4 用户自定义聚合函数(UDAF)0.3.5 开窗函数0.4 Spark Streaming0.4.1 Dst

《Unity3D》通过对象池模式,管理场景中的元素

池管理类有啥用? 在游戏场景中,我们有时候会需要复用一些游戏物体,比如常见的子弹.子弹碰撞类,某些情况下,怪物也可以使用池管理,UI部分比如:血条.文字等等 这些元素共同的特性是:存在固定生命周期,使用比较频繁,场景中大量使用. 所以,我们就通过池管理思路,在游戏初始化的时候,生成一个初始的池,存放我们要复用的元素, 当要用到时,从池中取出:生命周期结束,放回到池中. 代码 这个池的参数有两个:1池中存放的元素 2 池的初始容量(如果池不够了,则会按照这个容量进行扩展) 代码如下 using S

delphi新语法之泛型实现的对象池模板

现在的DELPHI因为支持泛型的语法,所以也能支持模板编程了. // 标准模板 unit UntPools; interface uses Classes, SysUtils, UntThreadTimer; type { 这是一个对像池, 可以池化所有 TObject 对像 } { 用法: 在一个全局的地方定义 var Pooler: TObjectPool; 用到的地方 obj := Pooler.LockObject as Txxx; try finally Pooler.UnlockOb

游戏开发设计模式之对象池模式(unity3d 示例实现)

前篇:游戏开发设计模式之命令模式(unity3d 示例实现) 博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 原理:从一个固定的池中重用对象,来提升性能和内存的使用,而不是一个一个的分配内存在释放它们.当你需要创造大量重复的对象,而且经常使用这些对象,你就要考虑使用对象池了,因为反复创建销毁就是一个内存反复分配与释放的过程,很容易产生内存碎片.在主机和移动端与PC相比内存稀缺,我们都希望游戏能够更加稳定,而不能有效的管理内

Unity3D中对象池的实现

在Unity中常常会遇到需要重复创建,销毁某些物体的情况,比如fps类游戏中的子弹,rpg类游戏中的小怪等等,如果直接使用Instantiate和Destroy的话,会浪费系统的资源,而使用对象池则能够节省下这些浪费. 这里使用一个重复利用子弹的进行发射的简单场景来演示对象池. 首先需要一个在场景中创建一个Cube,充当子弹. 然后在Assets目录下创建Resources文件夹,在Cube上添加上刚体,取消重力后,将Cube拖入Resources文件夹内作为一个预设,在场景中删除这个Cube.

[译]Unity3D内存管理——对象池(Object Pool)

从一个简单的对象池类开始说起 对象池背后的理念其实是非常简单的.我们将对象存储在一个池子中,当需要时在再次使用,而不是每次都实例化一个新的对象.池的最重要的特性,也就是对象池设计模式的本质是允许我们获取一个“新的”对象而不管它真的是一个新的对象还是循环使用的对象.该模式可以用以下简单的几行代码实现: public class ObjectPool<T> where T : class, new() { private Stack<T> m_objectStack = new Sta

unity3d对象池的使用

说对象池之前首先来看看单例类和单例脚本的区别.这里有介绍 http://blog.csdn.net/lzhq1982/article/details/12649281 使用对象池的好处是不用每次都创建对象.销毁在创建,比如子弹的发射,当创建好的子弹.可以在使用后保存到对象池里面.当用的时候.直接从对象池中取即可 这里随便举个列子 鼠标左键单击发射蓝色球.右键发射红色球 Main Camera挂载Fire.cs脚本 GameObject挂载单列脚本ObjectPools.cs Fire.cs代码

Unity3d对象池

Singleton.cs 12345678910111213 using UnityEngine;/// <summary>/// 单例模版类/// </summary>public class Singleton<T> where T : new() { private static readonly T instance = new T(); public static T Instance{ get{ return instance; } }} MonoSingl