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