OK, 那我们过到下⼀个话题,什么叫做pool manager,我们现在知道市⾯上现在最火爆的就是各种各样的 MMO 的游戏,那么这种以 RPG,Role Playing为主,在很多时候这里的游戏都遇到需要频繁的去创建一些⻆色或者说是一些 NPC 、怪物等这些东西,那么一个最 Low 的做法就是我们每创建⼀个怪物的时候我们把角色 Create 进来,那么不⽤的时候就 Destroy 掉,那么这样以来就相当于从我们的应⽤里面把它 Load 到我们的内存中来,然后 Destroy 再把它释放掉,如果下次再遇到⼀个这样的NPC,就再重新这样做⼀次。那么这个过程本身它可能并不是很慢,如果用 c++ 来做的话这个机制可能没有太多问题,但是因为如果你采⽤用 C# 脚本,就涉及到一个 GC 的问题,这个过程是 一个⾮非常耗时的过程,所以我们说这样的一个⾏为是绝对不要做的。那么这样的结果就是我们去创建⼀个我们⾃己的 PoolManager。
那么怎么设计呢?
我们说有⼀种⾮常⾮常简单的设计,就是说我每⼀次能不能把我的⼀些已经 Load 到内存中的资源不要⻢上就把它 Destroy 掉,⽽是先把它放在⼀个池⼦⾥备用, 如果下一次再需要同样的东西,我就把它拿出来,⾮常⾮常简单的⼀个想法,如果你具备了这样的一个想法,那么我说你的这样的⼀个设计就可以应付大多数小型规模的游戏了。
那么做法怎么做呢?
我们只要在你的程序⾥面加上⼀一行代码:
    
    
List < GameObject > dormantOnjects = new List < GameObject > ( ) ;
代码中 dormant 意思是: 暂时不用的
有这么⼀个东西,凡是你⽤过一次暂时不用的东西,就把它扔到这个池⼦⾥面去。 我们会有两个典型的操作,⼀个叫 Spawn,⼀个叫做 Despawn。
Spawn 是什么意思呢?
那么 Despawn 是什么意思呢?
如果说我现在有一个东⻄首次 Load 进来⽤完了,不要把它立刻 Destroy 掉,而是要把它放到这个池⼦里去。
OK,这两步了解之后,我们还要有一个 Trim 的操作,我们的池⼦不可能无限的⼤大,我把它定义一个尺⼨,比方说我最多只能装 100 个 NPC,超过这个数字的时候,因为放进去的时候是有顺序 的,0,1,2,3,4,5⼀一直到99,多了之后呢就把前⾯面的第 0 个,第 1 个,第 2 个推出去,这是⼀个队列的概念,先进先出。
那么有了这样的⼀个机制之后,我们说我们的 PoolManager 或者说 ObjectPool 就设计完成了了,简简单单就是这样一些代码,就可以解决你⼤大分的内存管理的问题,何乐⽽不为? 但是我们说这样的一个设计还是一个非常非常简单的⼀个设计,它有很多的问题,接下来我们就讲⼀下怎么改善这个设计。
它有什么问题呢?
第⼀个呢就是 Unity 它有很多资源是通过 Prefabs 来管理的,那么这些 Prefabs 有一个过程就是要把它从我们的磁盘 Load 到我真正的内存⾥面来,那么这个过程⼤家注意到,在刚才的 Manager 的管理里面,没有任何的体现,所以你需要在这个 Manager 的外部去管理 Load/Unload 的这样的过程。
第⼆个问题,我们注意到只有这些所谓的暂时不用的东⻄我们把它放到池子⾥面去,那我当前正在使⽤的这些东西满屏幕也可能有⼆三十个这样的 NPC,这些东⻄又谁来管理,OK,你需要写额外的代码,池的外⾯乱七⼋槽的代码来管理,我能不能把它也归在⼀起,这些当前 Active 的,如果说前面我们放在池⼦里的叫做 Deactive 的,那么 Active 的这个 Object 我是不是也可以把它管理理起来? 这是第二个问题。
第三个问题,⼤家注意到,就是我这个池⼦里面刚才我说,有 Trim 操作的时候我设置了了一个大的 Number,⽐方说我设了 100,但是大家注意到这 100 个它是不分类的,⽐⽅说我⼀个游戏⾥面有五⼗种 NPC,这五⼗种可能我扔进去之后,可能有一种 NPC 在我的池⼦里占了了 60 个,其他的这些很少,但这个应该是不太合理的,所以我希望是不是可以针对每一种 Prefab 都单独管理起来? 所以看到这些问题之后我们就有这些 Better Design了,能不能拿它设计成一个层次,最上面我们有⼀个这样的⼀个总的 Manager,那么再下⾯的呢,会管理若⼲着所谓的 SpawnPool,最下⾯放⼀个 PrefabPool,那我接下来就捋⼀下它们之间的层次关系,它们到底应该放些什么东⻄。下面这张图很简单的就能把问题说明清楚。 这样一个 PoolManager,它实际上你把它设计成 Singleton,在程序⾥面的任何位置都可以访问就 OK 了,那么它下面呢? 可以有若⼲个 SpawnPool,几个都没关系,那么这每⼀个 SpawnPool 是管理一类的物体。⽐方说我现在要把是 NPC 放到⼀个 Pool 里,就是所有NPC 相关的 GameObject/Prefab 都放到⼀个 Pool 里,场景⾥⾯还有⼀些掉落的物体,就是一些奖励,你跑到某⼀个地⽅打了⼀个关卡之后掉落⼀些东⻄出来,那么这些东西我放到⼀个单独的 Pool 里,就是给大家⼀个归类的一个方法,也就是说一个 SpawnPool ⾥⾯可以存在多个 Prefab,这个 Prefab 就对应你各种各样的宝箱啊,各种各样的 NPC 的类型啊,这些东西,我们说每一个 SpawnPool ⾥面可以包含若⼲个 PrefabPool,那么每一个 Prefab Pool ⾥只能放一个 Prefab。那么这个时候,其实我们的意义就是说我在这⾥能管理这个 Prefab 的加载和卸载,还能够管理由这个 Prefab 它 Instantiate 的⼀些新的实例,以及⼀些 Deactivate 的实例,那么有了这么的⼀个层次关系之后,你所有的这些问题,前⾯列掉的这些问题就都可以解决掉了。
那么我们再稍微多讲⼀点,那么我们说这个 PoolManager 一定要把它设计成一个 Singleton,那么它可以管理很多个SpawnPools,那么每⼀个 SpawnPool 呢,它呢,可以把它定义为⼀个 Empty 的 GameObject,⼜回到我们最开始提到的这个最简单的设计,所以我们很多时候最简单的规则并不一定是没有用的规则。 那么我们把它的 Transform 设置成最上⾯面的 Top Parent,那么所有的下⾯我们说这些 Instance,就是⼀些 PrefabPool 我们都把它直接挂在上面,这些 Transform 把它 Attach上⾯就可以了,那么甚⾄可以通过字典来管理这样一个 PrefabPool。 对于每⼀个 PrefabPool 呢? 我们可以去管理两个 List,⼀个就是 ActivetedList,⼀个是 DeactivateList,那么还可以在这⾥管理里我们所有的 Prefab 的加载和卸载的过程,那么这样的一个设计就⼏乎完美了。 那么我们再稍微举两个例子,我就给⼤家解释⼀下,如果说你现在想要管理场景中的类似于所有的子弹啊或者说技能特效⾥打出的⼀些⽕球啊这些东西,那么我们说你给这些东⻄设⼀个,给每个 Prefab,因为我们说一个⼦弹就是一个 Prefab 的话,你要给这个 PrefabPool 或者说 SpawnPool 要设⽴一个它的 MaxNumber, 超过这个MaxNumber 的时候,你有两种策略来处理,第⼀种策略就是:我就暂时停⽌生成新的东⻄了,那还有一种策略就是我们前⾯提到的 Trim 策略,我把前⾯的东西推出去,把后⾯新⽣成的压进来,那显然是第二种⽅法更好⼀些,那么这是针对⼀些⼦弹啊,⽕焰啊,这些不太紧要的东西,但是如果对这些所谓的 Character、NPC 这种东⻄你可能就不能简单的去设置⼀个这样的 MaxNumber,我⼀个 NPC 换句话说少出来⼀个,你这个游戏的打法就变掉了对吧? 这个时候所采⽤的策略一定一定要⾮常谨慎。就是你可能需要设置⼀个所谓的 DelayTime,超过⼀定的延时时间,这个 NPC 确认没有用的时候我们才可以把它删掉,⽽且每次删掉的数量要严格的控制,不要说我现在这个池⼦⾥面数量超了,我的最⼤的 Number 设置的是 10 ,我现在⼀共有 20 个,我一次一帧将⼆十个全都删掉,OK,你的游戏一定瞬间卡住,因为再进行 GC 操作,它会在后台占⽤你⼤量的系统资源,所以说⾃然而然的⼀个想法就是 PoolManager 管理的时候能缓释,所谓的缓释胶囊,能不能⼀帧只删三个,最多不要超过五个,那么留在这里下⼀帧再删除三个、五个,这样才能保证你不会在某一帧突然卡顿。
OK,那么 PoolManager 我们就讲到这⾥,我们下一篇再见,拜拜~
转载请注明地址:凉鞋的笔记: liangxiegame.com 订阅整套专栏: liangxiegame.com
Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐