草庐IT

Unity Game FrameWork—模块使用—对象池分析

哈哈,好啊好啊 2023-05-01 原文

官方说明:提供对象缓存池的功能,避免频繁地创建和销毁各种游戏对象,提高游戏性能。除了 Game Framework 自身使用了对象池,用户还可以很方便地创建和管理自己的对象池。
下图是Demo中用到的对象池,所有的实体以及UI都使用了对象池。

Domo中已经有了实体Entity对象池,可以满足存储GameObject的需求,这里仅能满足我们的实体使用需求,当我们需要添加新的对象池而非依赖于实体时,由于对原理不了解,导致无从下手。下面我们通过实体对象池来分析一下框架的原理和思路。

实体对象池引用池联系

如图,实体对象上挂在了两个脚本,Entity和Asteroid。这两个脚本并没有继承对象基类ObjectBase,而ObjectBase则是实现实体对象池必须继承的。下面我们先理一下实体的相关逻辑,理清分支。

实体管理器里的实体信息类EntityInfo继承了IReference

实体信息类的创建利用了引用池,却没有用到对象池

m_EntityInfos字典存储了实体的ID以及实体信息
InternalShowEntity方法将实体编号以及实体信息填入了m_EntityInfos字典。即,每次显示实体都会填入新的实体信息。并且每次实体信息填入前,实体组件的序号m_Serial对应+1。

实体对象(池)继承关系

1、EntityInfo : IReference
实体信息类继承于IReference,是实体管理器EntityManager的私有密封类,实体信息类由引用池创建和释放,用于辅助管理实体。
实体信息类内部属性:
m_Status:实体状态用于防止状态切换出错,如我们调用了影藏实体A,而另外一个地方却在给实体A添加子实体B吗,此时程序内会报错,以避免出现不可控的现象。当我们连续两次调用影藏实体时,若第一次已经影藏,则第二次不再生效。相当于状态锁,处于过渡状态时,避免再次调用。
m_Entity、m_ParentEntity、m_ChildEntities:存储了实体的引用,以及父实体、子实体列表的引用。
实体信息类用途:
m_EntityInfos实体信息存储字典
字典存储了实体的编号以及实体信息类,编号从实体的数据表获取。实体管理器EntityManager显示实体是,将实体信息填入m_EntityInfos,隐藏实体时,从m_EntityInfos里移除。
m_RecycleQueue实体信息回收队列
m_RecycleQueue的作用是配合m_EntityInfos释放实体,每一帧会检测回收队列是否有待回收的实体,如果有,则释放队列里的第一个实体。缓慢释放避免了集中释放造成的卡顿。
实体信息类如何管理实体:
显示实体的方法InternalShowEntity里面可以看到,实体辅助器DefaultEntityHelper.CreateEntity方法给加载出来的游戏对象添加了Entity(UnityGameFramework.Runtime)组件,并返回IEntity。IEntity作为参数用于从引用池创建实体信息类。随着实体信息类entityInfo.Status的状态切换,Entity完成了实体初始化(下面会说),实体加入实体组,以及实体显示。
实体影藏也是如此,在InternalHideEntity方法中,随着状态切换完成解除实体,影藏实体,从实体组中移除实体,随后将实体信息类加入到回收队列。

2、Entity : EntityLogic : MonoBehaviour(实体业务逻辑组件)
Entity继承于实体逻辑基类EntityLogic,EntityLogic作为抽象类定义了实体逻辑生命周期的相关方法。
具体的每一个实体则继承于Entity。实体在其业务逻辑的适当时机触发实体的生命周期方法。Asteroid、Aircraft等其他实体的业务逻辑类均继承与EntityLogic。
至此,Entity的实体业务逻辑并不完整,Entity实现后也只是在其中增加了业务逻辑,例如当飞行器掉血死亡时,实体影藏的生命周期方法里只是实现了影藏时触发特效等逻辑内容。并没有对其调用,也没有涉及实体对象池,而继承的EntityLogic中定义的也只是虚方法,并没有对象池的实现。继续往下:

3、Entity : MonoBehaviour, IEntity(实体生命周期组件)
Entity 实体完成了实体生命周期的回收、显示、轮询、附加解除子实体等调用。命名空间为:UnityGameFramework.Runtime。
Entity的私有成员包含EntityLogic成员,即实体业务逻辑组件。Entity生命周期方法主要是触发继承于EntityLogic的实体类的生命周期的相关业务逻辑。
其本身并没有花哨的功能实现,只是在生命周期方法里调用了EntityLogic的生命周期业务逻辑。它存在的意义是什么?注意:在上文的实体信息类如何管理实体的部分,实体管理器EntityManager的InternalShowEntity方法给游戏对象添加了该组件,并触发了初始化、加入实体组、实体显示的功能。在InternalHideEntity方法触发了实体解绑、影藏的逻辑。
Entity的作用便体现出来了它沟通了实体管理器EntityManager与实体业务逻辑之间的联系EntityLogic使生命周期的框架代码与生命周期的业务逻辑代码分割开,对框架做了一层封装只对外暴露了生命周期的接口。外部调用生命周期方法时,通过管理器来实现间接调用。

对象池使用流程分析

最开始的一张图里面我们可以看到实体是通过对象池完成创建回收的,可是分析到这,丝毫没有涉及到对象池,反倒多了一个实体信息类EntityInfo的引用池用来管理实体信息。从下图可以看到实体相关的引用池有AttachEntityInfo、EntityInfo、EntityInstanceObject、ShowEntityInfo。本着对象池是对引用池的一种封装的原则,我们从实体相关的引用池入手。

我们通过观察这几个类,只有EntityInstanceObject继承于ObjectBase,其他都是继承于IReference。可以肯定,对象池是的内容是基于EntityInstanceObject开始的。其他的实体相关引用,有兴趣可自行学习,这里主要看下EntityInstanceObject是如何实现实体对象池的。
最终结束的地方是,对象池的信息是怎么显示到对象池组件的检视器面板上的。
在对象池组件检视器面板修饰类ObjectPoolComponentInspector里面可以找到,检视器面板显示的是对象池基类数组ObjectPoolBase[]所存储的信息。开头(EntityInstanceObject)和结尾(ObjectPoolBase[])被找到了,剩下就是怎么从开头到结尾的,把整个过程联系起来。
ObjectPoolBase[]是通过ObjectPoolComponent.GetAllObjectPools()方法获取的,来源则是对象池管理器ObjectPoolManager的m_ObjectPools对象池字典。对象池的创建、销毁调用的是InternalCreateObjectPool()和InternalDestroyObjectPool()方法。而实体对象池的创建必然是通过实体管理器来调用对象池管理器的InternalCreateObjectPool()方法创建对象池,继续顺着这个思路往下找,ObjectPoolManager里面有三处调用了该方法,分别是406行创建UI对象池,699行创建实体对象池,774行创建资源对象池。
实体对象池的创建是在实体组EntityManager.EntityGroup初始化时调用了
objectPoolManager.CreateSingleSpawnObjectPool()方法,并将实体组的信息传入完成创建。

实体对象池的信息存储在实体组的对象池m_InstancePool里面。m_InstancePool则是实现了IObjectPool接口的对象池ObjectPool。ObjectPool里面实现了初始化对象池的新实例、创建对象、回收对象、释放对象等方法,增加了很多对象池功能。
ObjectPool的m_Objects存储了对象名及对象类的键值对,m_ObjectMap存储了对象本体及对象类的键值对。
我们看一下对象池ObjectPool里是在哪创建(注册)对象的,通过溯源可以找到EntityManager.ShowEntity()方法,用户调用该方法显示实体,在显示之前首先完成加载m_ResourceManager.LoadAsset(),在加载成功的回调事件中EntityManager.LoadAssetSuccessCallback()中调用EntityInstanceObject.Create()创建对象实例,create方法里面完成了从引用池获取引用。实例对象创建完成后,调用对应实体组的RegisterEntityInstanceObject方法,RegisterEntityInstanceObject调用了ObjectPool的Register方法,最终将对象实例添加进了对象池。
从对象池中取出的过程,有兴趣的话,可以走一遍流程,分析一下。
从整个过程可以看出实体对象池是依赖于实体组存在的,实体组中创建了对应实体的对象(EntityInstanceObject)。当对象被创建时,首先被添加到引用池,随后再添加到实体组的对象池中。实体对象池中对象的释放,则是按照实体组定义的间隔秒数来执行,先从对象池中释放,再从引用池中释放。
Demo实现了实体对象池、UI对象池以及资源对象池,后续我们实现自己的对象池,加深对对象池的使用和理解。

有关Unity Game FrameWork—模块使用—对象池分析的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  6. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  7. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  8. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  9. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  10. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

随机推荐