游戲隨筆之游戲資源池的設計


  很久沒有更新了,今天給大家寫一篇游戲資源池的相關文章,就當作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