游戏编程模式之享元模式

享元模式的“共享”

“使用共享以高效地支持大量的细粒度对象”(摘自《游戏编程模式》第三章)

  • 从这一句话可以看出,享元模式重要的目的是关于代码的高效性。而一般提升效率的本质则是减小计算量、工作量。例如:
    • LOD:层次细节技术,它降低远处的渲染质量来减小计算机的工作量
    • 比较的二分法:利用规律减少比较次数
  • 理解上述问题是比较简单的。而享元模式,它减小计算量的手段是:通过对一个集合的公共部分的抽离,让公共部分共享一份数据模型,避免了工作的重复,以提高效率
引例:GPU对森林的渲染
  • 关于森林中的每一棵树,我们都有如下性质并封装成类:
    class Tree
    {
        private:
            Mesh      m_Mesh;
            Texture   m_Bark;
            Texture   m_Leaves;
            Vector    m_Position;
            double    m_Height;
            double    m_Thickness;
            Color     m_BartTint;
            Color     m_LeaveTint;
    }
    
  • 在渲染中,如果我们将上述类实例化作为对象,则需要向GPU传输对象的数据。在一帧内传输至GPU是不太可行的。(Mesh和Texture数据量很大)
  • 我们可以发现,一个树其实是有公共部分的:它们分别是:Mesh、Bark、Leaves。因此,在类的设计时,我们可以将公共部分抽离成一个类,并采用C++组合的关系。
    class TreeModel
    {
        private:
           Mesh      m_Mesh;
           Texture   m_Bark;
           Texture   m_Leaves; 
    }
    
    class Tree
    {
        private:
            TreeModel*  m_Model;
            Vector      m_Position;
            double      m_Height;
            double      m_Thickness;
            Color       m_BartTint;
            Color       m_LeaveTint;
    }
    

    在C++中,类与类关系主要有两种,一个是继承,一个是组合。继承是拷贝,即有size(子类) ≈ size(父类)+ size(差异)。而对于组合,是在类中定义一个类的指针,以达到have one 的效果。size(组合类)=size(差异)+size(ptr) (ptr为对象指针)

  • 因此,在本次渲染工作中,仅需要提供两组数据。一组为通用数据:TreeModel,第二组就是实例列表以及差异化参数。(关于这次渲染涉及到实例绘制,这里仅给出名词)
享元模式
  • 享元模式通过将对象数据切分为两种类型来解决问题。第一类为不属于单一对象实例并且能够被对象共享的数据,称为内部状态(the intrinsic state);其他数据称为外部状态(the extrinsic state)

    这两个词或许只是GoF小组(书的作者团队)所命名

拓展
  • 游戏中将使用基于瓦片(Title-base)的技术来构建地面

  • 例子中,地面的属性有:

    • 移动开销
    • 是否是一片能够行驶船只的水域的标志位
  • 第一种解决方案:枚举作为返回标志

       enum Terrain
       {
           TERRAIN_GRASS,
           TERRAIN_HILL,
           TERRAIN_RIVER
       };
       
       class World
       {
           public:
             int  getMovementCost(int x,int y);
             bool isWater(int x,int y);
             
           private:
             Terrain m_Tiles[WIDTH][HEIGHT];    //瓦片二维数组
       }
       
       int Terrain::getMovementCost(int x,int y)
       {
           switch(m_Tiles[x][y])
           {
               case TERRAIN_GRASS:
                       return grassCost;
                       reak;
               case TERRAIN_HILL:
                       break;
           }
       }
       ……
    
    • 这一方法的缺陷在于:代码在组织上比较简陋,因为地形和判断方法没有组织在一起。一个整体被分成了两个部分。
  • 如同上述的森林渲染一样,设计一个类,每一个地形为一个类,它保证了整体完整。但这又回到了引例中的问题。

    class Terrain
    {
        public:
          Terrain(int movementCost,bool isWater,Texture texture): 
          
          int  getMoveCost() const {return m_MoveCost;}
          bool isWater()     const {return m_IsWater; }
          const Texture& getTexture() const
          {
              return m_Texture;
          }
          
        private:
          int  m_MoveConst;
          bool m_IsWater;
          Texture m_Texture;
    }
    
  • 我们学习引例的做法:将数据拆分成内部数据和外部数据。例子中,三个数据均为内部数据。并对复用数据进行 have one的设计

    class World
    {
        private:
        Terrain* m_Tiles[WIDTH][HEIGHT];  //地形对象的网格指针
        
        public World() : m_GrassTerrain(1,fase,GRASS_TEXTURE),m_HillTerrain(2,fasle,HILL_TEXTURE),m_RiverTerrain(3,true,RIVER_TEXTURE){}
        
        //初始化瓦片
        bool generateTerrain()
        {
            m_Tiles[1][1]=&m_RiverTerrain;
            ……
        }
        
        
        //have one 设计
        Terrain m_GrassTerrain;
        Terrain m_HillTerrain;
        Terrain m_RiverTerrain;
    }
    
    
总结
  • 享元模式关键字在于,把一个对象的公共数据资源抽离成一个实例,因此所有的对象都可以公用这一个实例,减少了计算量。
  • 如果你发现自己创建了一个枚举,并使用switch,那么你可以考虑使用这一模式。
  • 继承和组合是类的两种关系,我们要学习在特定情况下如何选择。

知识点全部源于《Game Programming Patterns》,中文名《游戏编程模式》,图片来源于网络

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值