zoukankan      html  css  js  c++  java
  • 游戏随笔之游戏资源池的设计

      很久没有更新了,今天给大家写一篇游戏资源池的相关文章,就当作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年新年快乐!

  • 相关阅读:
    项目--Asp.net全局变量的设置和读(web.config 和 Gloab)
    项目--后台代码提示
    项目--给项目添加提示声音
    项目--正则表达式
    项目--HTML Canvas 和 jQuery遍历
    项目--用户自定义控件
    Bzoj2120/洛谷P1903 数颜色(莫队)
    Poj2482 Stars in Your Window(扫描线)
    Poj2182 Lost Cows(玄学算法)
    Poj3468 A Simple Problem with Integers (分块)
  • 原文地址:https://www.cnblogs.com/zblade/p/8134121.html
Copyright © 2011-2022 走看看