[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool

这篇具有很好参考价值的文章主要介绍了[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

学习目标:

  大家都知道在一些游戏中常常要创建大量的游戏对象,如果这些对象长期占用一些内存而没有触发垃圾回收机制(以下简称GC)或者过于频繁的触发GC就会导致游戏的帧数暴跌,在移动设备直接造成卡死的现象,那引用对象池的概念,能让这些游戏对象在刚开始的时候就被初始实例化而不会在游戏中频繁生成也不用触发垃圾回收机制,相当于对性能极大的提升,这些都是Unity非常经典的模式,那么在Unity2021.2以后的版本Unity终于自己创了一个新的命名空间UnityEngine.Pool不用玩家再自己造轮子了,下面跟着B站一位大佬Up学习了如何引用该命名空间,

这里贴个连接:

【Unity 2021】对象池API | 对象池模式 | Unity提高教程 | Object Pool_哔哩哔哩_bilibili

Github:GitHub - https://github.com/AtCloudStudio/UnityObjectPoolTutorial

官方的API:Unity - Scripting API: ObjectPool<T0> 


学习内容:

 进入官网的API可以看到这个命名空间包含了多个数据结构的对象池,

[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool

这里就用最常用的ObjectPool<T>吧,

然后选择一个2021.2之后的版本,我用的是长期支持的LTS3.8[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool

创建一个项目进入后然后把素材导进来,如果你想自己动手做的话Scripts里面的就别放进来了,然后把无法识别的脚本的组件都删了,因为我们要动手跟着大佬做一遍,

 我们先创建一个名字叫Gem的脚本,然后挂载到Gem Base的父预制体,这样其它的宝石预制体都会挂载他。

创建一个委托以及一个计时器要来记录什么时候执行委托,当碰到标签为Floor的地板后就触发开始计时器的bool

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

public class Gem : MonoBehaviour
{
    [SerializeField] private float lifeAfterLanding = 2f; //着陆后过了两秒就自动消除
    private float deactiveTimer;
    private bool hasLanded;
    System.Action<Gem> deactiveAcion;
   
    void Update()
    {
        if (!hasLanded)
            return;
        deactiveTimer += Time.deltaTime;
        if(deactiveTimer >= lifeAfterLanding)
        {
            deactiveTimer = 0;
            deactiveAcion.Invoke(this); // 执行这个委托
        }

    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Floor"))
        {
            hasLanded = true;
        }
    }

    public void SetDeactiveAction(System.Action<Gem> deactiveAcion)
    {
        this.deactiveAcion = deactiveAcion;
    }
}

再创建一个叫GemSpawnNormalVersion的脚本

这个用来控制生成宝石的。别忘了在生成宝石的函数最后订阅这个委托并给它要执行的事件

using UnityEngine;

public class GemSpawnNormalVersion : MonoBehaviour
{
    [SerializeField] private Gem[] gemPrefabs;
    [SerializeField] private int spawnAmounts = 50;
    [SerializeField] private float gemSpawnInterval;
    private float gemSpawnTimer;

    private void Update()
    {
        gemSpawnTimer += Time.deltaTime;

        if(gemSpawnTimer >= gemSpawnInterval)
        {
            gemSpawnTimer = 0;
            Spawn();
        }
    }

    private void Spawn()
    {
        for (int i = 0; i < spawnAmounts; i++)
        {
            var randomIndex = Random.Range(0, gemPrefabs.Length);
            var prefab = gemPrefabs[randomIndex]; //随机生成某种类型的宝石
            var gem = Instantiate(prefab, transform); //把这个宝石生成器对象作为生成宝石的脚本

            gem.transform.position = transform.position + Random.insideUnitSphere * 2;
            gem.SetDeactiveAction(delegate { Destroy(gameObject);});
        }
    }
}

再窗口配置好后,

[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool


[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool 

 这时候运行游戏,没问题12钟宝石随机生成,在地面过了两秒后触发垃圾回收机制GC,但你的电脑有没有红温呢,反正我笔记本电脑温度高的一批,我就不截图了怕我电脑烧了。

 这时候就到了使用对象池的时间了

 我们创建一个GemPool的脚本挂载上去。

请结合代码看我的解释,我们引用新命名空间,using UnityEngine.Pool;

然后创建一个ObjectPool<T>泛型T里面是Gem类,然后再Awake函数初始化,这里需要几个委托函数,第一个用于先创建也就是该类型的游戏对象这里我们是Gem类的要先实例化!,然后返回这个游戏对象,第二个是用于当我们调用对象池的Get函数索要执行的功能也就是启用这个对象,第三个则是调用Release()也就是返回这个对象池要执行的功能,第四个则是当对象池尺寸不足以容纳这么多游戏对象的时候就会销毁无法返回对象池的游戏对象,第五个则是一个bool的,用来自动检测对象池是否超尺寸,由于这个对象池本质是一个栈的数据结构,所以当尺寸小于实际产生的游戏对象,就会生成更多的游戏对象来扩大尺寸(但会产生GC),第六个则是对象池的默认尺寸,第七个是对象池能容忍的最大尺寸。

[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool

 

using UnityEngine;
using UnityEngine.Pool;

public class GemPool : MonoBehaviour
{
    [SerializeField] private Gem prefab;
    [SerializeField] private int minCapcitySize =10;//这两个变量用来定义对象池也就是栈的存储空间
    [SerializeField] private int maxCapcitySize = 100;
    [SerializeField] private int activeCount => pool.CountActive;
    [SerializeField] private int inacitveCount => pool.CountInactive;
    [SerializeField] private int totalCount => pool.CountAll;
    ObjectPool<Gem> pool;
    //对象池仅仅发生激活和非激活状态之间的切换,只有调用ObjectPool.Clear()或者Dispose()才会清除对象池中的元素
    private void Awake()
    {
        pool = new ObjectPool<Gem>(OnCreatePoolItem, OnGetPoolItem, OnReleasePoolItem, OnDestoryPoolItem, true, minCapcitySize, maxCapcitySize);
    }
    private void Update()
    {
        var gem = pool.Get();
        gem.transform.position = transform.position + Random.insideUnitSphere * 2;
    }

    private void OnDestoryPoolItem(Gem obj)
    {
        Destroy(obj.gameObject);
    }

    private void OnReleasePoolItem(Gem obj)
    {
        obj.gameObject.SetActive(false);
    }

    private void OnGetPoolItem(Gem obj)
    {
        obj.gameObject.SetActive(true);
    }

    private Gem OnCreatePoolItem()
    {
        var gem = Instantiate(prefab, transform);
        gem.SetDeactiveAction(delegate { pool.Release(gem); }); //在实例化宝石后再调用release函数回收这个宝石

        return gem;
    }
}

别忘了调用委托SetDeactiveAction先让实例化的对象返回对象池中。

 

[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool

这里随机选择一个预制体,运行游戏可以看到有一部分就在禁用状态。

那么我们怎么推广所有宝石预制体呢?很简单,只需要用数组给Gem类加个数组用foreach依次生成即可,那对于游戏中所有要用到的预制体呢,他们可没有用Gem这个类。

这样我们要造新轮子写个基类让所有要用到对象池的游戏对象继承使用即可,

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

public class BasePool<T> : MonoBehaviour where T : Component
{
    [SerializeField] protected T prefab;
    [SerializeField] int minSize =100;
    [SerializeField] int maxSize = 500;
    public int activeCount => pool.CountActive;
    public int inacitveCount => pool.CountInactive;
    public int totalCount => pool.CountAll;

    ObjectPool<T> pool;

    public void Initialize(bool checkPoolSize = true)
    {
        pool = new ObjectPool<T>(OnCreatePoolItem, OnGetPoolItem, OnReleasePoolItem, OnDestoryPoolItem, checkPoolSize, minSize, maxSize);
    }

    protected virtual void OnDestoryPoolItem(T obj)
    {
        Destroy(obj.gameObject);
    }

    protected virtual void OnReleasePoolItem(T obj)
    {
        obj.gameObject.SetActive(false);
    }

    protected virtual void OnGetPoolItem(T obj)
    {
        obj.gameObject.SetActive(true);
    }

    protected virtual T OnCreatePoolItem() => Instantiate(prefab, transform);

    public void Get() => pool.Get();
    public void Release(T obj) => pool.Release(obj);

    public void Clear() => pool.Clear();
    
}

可能你对这些Public的函数Lamada表达式后半部分的功能不太了解,其实官方API都有标明他们的功能

这样回到GemPool的脚本只需要继承这个类再重写两个函数即可 

using UnityEngine;
using UnityEngine.Pool;

public class GemPool : BasePool<Gem>
{
    
    private void Awake()
    {
        Initialize();
    }
    private void Update()
    {
        Get();
    }
    protected override Gem OnCreatePoolItem()
    {
        var gem = base.OnCreatePoolItem();
        gem.SetDeactiveAction(delegate { Release(gem); }); //在实例化宝石后再调用release函数回收这个宝石

        return gem;
    }

    protected override void OnGetPoolItem(Gem gem)
    {
        base.OnGetPoolItem(gem);
        gem.transform.position = transform.position + Random.insideUnitSphere * 2;
    }

    public void SetGemPrefab(Gem prefab)
    {
        this.prefab = prefab;
    }
}

最后的扩展:

其实这还不算是最好的性能,当游戏对象还是太多的时候,游戏帧数就会慢慢降到个位数直到卡死,接下来要介绍更好的运用ObjectPool性能

创建一个新脚本叫GemSpawnNormalVersion

首先是Gem【】数组用来管理每一种宝石的生成,在Start函数中为他们每个创造一个父对象poolHolder,这样方便管理各个种类的宝石,然后直到poolHolder挂载GemPool脚本并且为这个脚本上设置好它专属的Gem类(不然为空会报错)就可以激活它了,别忘了赋在我们的链表List<GemPool>上,最后在Spawn函数中,我们随机选择某种类型的宝石并在链表中取出来,并调用它GemPool脚本的Get()函数。

[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool

 

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

public class GemSpawnPoolVersion : MonoBehaviour
{
    [SerializeField] private Gem[] gemPrefabs;
    [SerializeField] private int spawnAmounts =50;
    [SerializeField] private float gemSpawnInterval;
    private float gemSpawnTimer;

    List<GemPool> gemPools = new List<GemPool>();
    private void Start()
    {
        foreach (var gemPrefab in gemPrefabs)
        {
            var poolHolder = new GameObject($"Pool:{gemPrefab.name}");

            poolHolder.transform.parent = transform;
            poolHolder.transform.position = transform.position;
            poolHolder.SetActive(false);

            var pool = poolHolder.AddComponent<GemPool>();

            pool.SetGemPrefab(gemPrefab);
            poolHolder.SetActive(true);
            gemPools.Add(pool);
        }
    }

    private void Update()
    {
        gemSpawnTimer += Time.deltaTime;

        if (gemSpawnTimer >= gemSpawnInterval)
        {
            gemSpawnTimer = 0;
            Spawn();
        }
    }

    private void Spawn()
    {
        for (int i = 0; i < spawnAmounts; i++)
        {
            var randomIndex = Random.Range(0, gemPrefabs.Length);
            var pool = gemPools[randomIndex];
            pool.Get();
        }
    }
}

[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool

 挂载后运行,无论你的SpawnAmounts调的多离谱都会帧数稳定很多了。(30帧左右吧)

这样性能就能妥善解决了大性能小号稳定


学习产出:

学习了怎么使用新命名空间UnityEngine.Pool,并且了探讨了更加优化版本的正确对象池使用。文章来源地址https://www.toymoban.com/news/detail-410198.html

到了这里,关于[Unity命名空间教程]介绍Unity新自带的命名空间UnityEngine.Pool的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • unity学习(16)——服务器组装(3)命名空间问题

    using GameServer.logic; using GameServer.NetModel; 代码中这两句存在命名空间的引用问题,c#和c++不一样,用的的using和命名空间,之前的惯性思维都是include和文件路径。 错误:直接把逆向文件夹粘到项目文件夹中,这样做vs的资源管理器根本没反应: 正确:在资源管理器中点击GameSe

    2024年02月21日
    浏览(39)
  • 【C++杂货铺】C++介绍、命名空间、输入输出

     C语言是 结构化 和 模块化 的语言,适合处理 较小规模 的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,20世纪80年代,计算机界提出了 OOP (object oriented programming: 面向对象 )思想,支持面向对象的程序设计语言应

    2024年02月16日
    浏览(31)
  • 【C++初阶】C++入门——C++介绍、命名空间、输入输出

     C语言是 结构化 和 模块化 的语言,适合处理 较小规模 的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,20世纪80年代,计算机界提出了 OOP (object oriented programming: 面向对象 )思想,支持面向对象的程序设计语言应

    2024年02月11日
    浏览(35)
  • Unity导入google.protobuf失败,无法找到google命名空间

    1.刚开始把protobuf的文件夹直接从其他项目里(unity2021)里复制到unity(2020)版本,当时报错protobuf.dll的依赖项system.memory版本不对。 2.没有使用原来的protobuf文件了。使用vs2019的NuGet管理包来下载Google.Protobuf ,仍然报错找不到Google 3.找到Google后,报错说该platform存在多个Assembly。 1.解

    2024年01月16日
    浏览(40)
  • Unity报错命名空间System.IO.Ports不存在解决方法SerialPort

    尝试解决方法:工具–Nuget包管理器(N)–管理解决方案的Nuget程序包(N)搜索SerialPort,重新下载System.IO.Ports,报错仍然存在; 发现问题在于设定,使用这个包的时候必须将Project Settings – Player – Other settings – Configuration – API Compatibility Level 从 .NET standard 2.1 改成 .NET

    2024年04月17日
    浏览(27)
  • unity的C#学习——命名空间的定义与访问、using语句的常见用法

    在C#中,命名空间(Namespace)是一种 将类和其他相关类型组织在一起的方式 。可以将命名空间看作是一个容器,用于管理和组织类和其他类型。通过将相关的类型组织在一起,可以使代码更加清晰和易于维护。 命名空间的主要目的是为了避免名称冲突,使得开发人员可以 使

    2024年02月11日
    浏览(43)
  • ## 在unity中无法使用RegistryKey类,引用了Microsoft.Win32命名空间也不行

    解决方法: 把unity api兼容级别改成.net framework

    2024年02月11日
    浏览(33)
  • C# 图解教程 第5版 —— 第22章 命名空间和程序集

    ​ 在许多项目中,会使用其他程序集的类或类型,而不仅仅是自己声明的。这些程序集(称为类库)可能来自 BCL 或第三方供应商,也可以是自己创建的。通常这些程序集文件的名称以 .dll 扩展名结尾,而不是 .exe。 图22.1 SuperLib 源代码和结果程序集 ​ 假设还要写一个名为

    2024年01月18日
    浏览(28)
  • 虹科教程 | Linux网络命名空间与虹科PROFINET协议栈的GOAL中间件结合使用

    PROFINET是由PI推出的开放式工业以太网标准,它使用TCP/IP等IT标准,并由IEC 61158和IEC 61784 标准化,具有实时功能,并能够无缝集成到现场总线系统中。凭借其技术的开放性、灵活性和性能优势,PROFINET可应用于过程/工厂自动化、运动控制等领域。通过PROFINET,可实现确定性响应

    2024年02月13日
    浏览(41)
  • 【C++】命名空间 namespace 与 标准流 iostream ( 命名空间概念简介 | 命名空间定义 | 命名空间使用 | iostream 中的命名空间分析 )

    命名空间 namespace 又称为 名字空间 , 名称空间 , 名域 , 作用域 , 是 C++ 语言 对 C 语言 的扩展 之一 ; C++ 中的 命名空间 namespace 指的是 标识符 的 可见范围 , C++ 标准库中的 所有 标识符 , 都定义在 std 命名空间中 ; 命名空间 英文名称是 \\\" namespace \\\" , name 是 名字 , 名称 的意思 ,

    2024年02月12日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包