游戏随笔之游戏资源池的设计


  很久没有更新了,今天给大家写一篇游戏资源池的相关文章,就当作2017年的最后一篇文章吧。 转载请标明出处:http://www.cnblogs.com/zblade/

一、游戏项目中的资源池

  在一款游戏中,随着游戏的进行,我们会不断的创建和销毁一些角色,比如我们玩一款射击游戏,我们需要不断的发射子弹,一般的情况下,我们会不断的创建子弹,然后发射出去,在击中物体后销毁。分析整个设计的过程,我们会不断的创建子弹,然后发射出去,最后销毁它。这儿,其实就可以引入资源池的概念来解决子弹的反复创建和销毁。

  如果只是反复的创建和销毁子弹,那么每次的性能点主要在子弹的创建上,在场景中有较多角色频繁操作射击的时候,不断执行创建,会带来性能的较大消耗,从而让游戏卡帧。如果我们把这些子弹预先创建出来,塞入到一个弹夹,然后每次在射击发射子弹的时候,从预先创建的弹夹中取出来发射,每次在销毁的时候,又将其还原到弹夹中,这样循环反复的利用,可以避免每次射击子弹的时候的创建操作带来的性能消耗。将弹夹拓展一步,就是资源池的概念了。

 

二、不同设计下的资源池

  1、简单lua版本的资源池

  基于前文子弹的阐述,我就写一个简单版本的资源池的lua版本,首先,是资源池的定义:

function BulletPool:initialize() --缓存子弹的table
    self.mBulletPool = {} end

  是不是觉得很简单,是的,lua版本的资源池可以只需要用一个table就简单的表示,接下来,我们只需要维护好这个table即可。首先是取子弹的接口:

function BulletPool:GetBullet() --如果没有定义,则执行一次兜底定义
    if not self.mBulletPool then self.mBulletPool = {} end
    -- 如果池子里面没有可以取的了,则返回nil
    if next(self.mBulletPool) == nil then
           return nil
    end

    local bulletObj = self.mBulletPool[#self.mBulletPool] table.remove(self.mBulletPool, #self.mBulletPool) return bulletObj end

  有了设计的接口,接下来,我们可以继续设计归还的接口,所谓有借有还再借不难,不能只从池子里面取,不归还,那池子早晚会干涸的 :D

function BulletPool:InsertBullet(bullet) if not bullet then return end
    if not self.mBulletPool then self.mBulletPool = {} end
    --子弹归还前的释放操作,可以不在意这一步操作
 bullet:Release() table.insert(self.mBulletPool, bullet) end

  好了,有了整体的获取和归还的操作,池子的基本接口就有了,有的同学会说,如果我们想重置一遍池子怎么办?那就再写一个清除池子的操作吧 :b

function BulletPool:Release() for k, v in pairs(self.mBulletPool) do v:Release() end
    
    for k, v in pairs(self.mBulletPool) do self.mBulletPool[k] = nil
   end
end

  这下接口都有了,让我们来应用这些接口吧 :D

      首先给角色挂载一个子弹的资源池的获取接口吧:

--获取接口
function Character:GetBullet() return BulletPool:GetBullet() end
--塞入接口
function Character:RemoveBullet(bullet) BulletPool:InsertBullet(bullet) end

  因为每个角色都会射出一堆的子弹,所以我们是直接挂在角色身上,就不在整个场景管理器中去管理子弹了,可以通过场景管理器的更新来执行角色的更新,从而执行所有子弹的更新,这样每个角色的子弹更新和角色更新一致。这种设计模式下,不会出现先更新角色,然后再更新子弹的带来的一些问题。

  有了这两个接口,下面就是让角色调用这2个接口:

... --获取子弹
local bullet = mChar:GetBullet() --没有则新建,有则重新初始化相关参数
if bullet == nil then bullet = Bullet:new(...) else bullet:initialize(...) end
--塞入到角色身上的一个table中维护
mChar:AddBullet() ... --移除子弹,比较简单
if bullet:Update() then mChar:RemoveBullet(bullet) end

  到这儿,我们完成了一个简单的lua版本的资源池的设计和实现,通过这几个接口,对资源池有一个简单的入门理解了。接下来,我们进一步编写一个c#版本的资源池吧。

  2、C#版本的资源池

     在有了lua版本的资源池入门之后,接下来我们可以进一步的设计一个c#版本的资源池了。在unity的c#中,会有各种各样的资源需要资源池来进行管理,所以我们不能单独的做某个类的资源池了,我们需要引入泛型来指代各种类型的资源池。

  先写一个简单的资源池,就实现一个获取和归还接口吧: 

using System.Collections; using System.Collections.Generic; public class ObjectPool<T> where T:class { //用一个列表来代替lua中的table,用作资源池
    LinkedList<T> objs = new LinkedList<T>(); public T GetObject() { if(objs.Count > 0) { T obj = objs.Last.Value; objs.RemoveLast(); return obj; } return null; } public void ReturnObj(T obj) { if(obj != null) { obj.AddLast(obj); } } }    

  有了简化版本的资源池,我们可以进一步的拓展这个池子的设计。首先,我们可以将链表改为堆栈,用一个栈来代替链表,相对会比较容易控制,只需要管理入栈和出栈即可。其次,在池子已经被榨干,取完的时候,前面是直接返回一个null,我们可以继续拓展,在没有的时候,就进行一次创建操作,这个可以通过委托来实现,在池子的初始化的时候就注册相关的委托。同理,进一步的拓展出取完后的操作和归还释放时的操作委托,这样就把我们前面lua中归还池子时候释放子弹的操作封装为一个事件。说完思路,下面让我们开始吧:

using System; using System.Collections.Generic; public class ObjectPool<T> where T:class { //堆栈
   private readonly Stack<T> m_stack; //事件
   private readonly Func<T> m_ActionOnCreate; private readonly Action<T> m_ActionOnGet; private readonly Action<T> m_ActionOnRelease; //构造函数
  public ObjectPool(Func<T> actionOnCreate, Action<T> actionOnGet, Action<T> actionOnRelease) { m_stack = new Stack<T>(); m_ActionOnCreate = actionOnCreate; m_ActionOnGet = actionOnGet; m_ActionOnRelease = actionOnRelease; } //获取接口
    public T Get() { T obj; if(m_stack.Count == 0) { //执行构建操作
              obj = m_ActionOnCreate(); } else { obj = m_stack.Pop(); } //执行回调
           if(m_AcitonOnGet != null) { m_ActionOnGet(obj); } return obj; } //释放接口
    public void Release(T obj) { if(m_ActionOnRelease != null) { m_ActionOnRelease(obj); } m_stack.Push(obj); } //clear接口
    public void Clear() { m_stack.Clear(); } }  

   写到这儿,一个基本的资源池的构建算是完成了,大家可以在这个版本的基础上进一步的衍生出资源池的使用,比如给资源池的对象添加一个计时的功能,当资源计时超过一定的时间后,就将其从资源池中去除,避免资源池不断扩大。诸如此类种种,都是后续可以操作的,好了,这篇文章就写到这儿,也祝提前祝大家2018年新年快乐!


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM