游戏编程模式--享元模式
享元模式
享元模式是把数据分为两种类型,一种是不属于单一对象而是为所有对象共享的数据,GoF将其称为内部状态;而另一种数据则为单一对象独有的。比如我们要渲染很多的草和树,草和树的形状是共享的,每棵树的位置和大小等数据是对象唯一的。通过共享数据的使用来节省内存。
地形
我们使用一个地形的例子来解释享元模式是如何工作的。假设在我们的虚拟世界中由三种地形:草地、山地、河流,而我们使用基于瓦片(tile-based)的技术来构建地形,每个瓦片由一种地形覆盖。对于每一种地形,都有一些影响着游戏玩法的属性:
- 移动开销决定角色穿过这个地形所使用的时间
- 用来决定是否是一片能够行驶船只的水域的标志位
- 纹理,用来渲染地形
先来看一种常用的实现方法,游戏开发人员为了高性能,通常不会为每一个瓦片保存状态,为每一种地形创建一个枚举类型,每个瓦片都拥有一种地形:
enum Terrain { TERRAIN_GRASS, TERRAIN_HILL, TERRAIN_RIVER, //other terrains.... }; class world { private: Terrain tiles_[WIDTH][HEIGHT]; };
这样,如果想获得瓦片的数据则使用这样的方式:
int World::getMovementCost(int x, int y) { switch(tiles_[x][y]) { case TERRAIN_GRASS: return 1; case TERRAIN_HILL: return 3; case TERRAIN_RIVER: return 2; //other Terrians.. } } bool World::isWater(int x,int y) { switch(tiles_[x][y]) { case TERRAIN_GRASS: return false; case TERRAIN_HILL: return false; case TERRAIN_RIVER: return true; //other terrains... } }
这种做法是比较粗糙的,地形的数据分散在一个个的方法中,这与面向对象设计的原则不符。接下来我们看一种比较符合面向对象设计的做法:把地形的数据封装到一个地形类中,使用不同的数据实例化对应的地形对象,瓦片则引用这些地形对象即可。
代码如下:
class Texture { public: Texture(int id) : id_(id) { } int id_; }; class Terrain { public: Terrain(int moveCost, bool isWater, Texture texture): moveCost_(moveCost), isWater_(isWater), texture_(texture) { } int getMoveCost() { return moveCost_; } private: int moveCost_; bool isWater_; Texture texture_; }; class World { public: World() : grassTerrain_(1, false, Texture(1)), riverTerrain_(2, false, Texture(2)), hillTerrain_(3, false, Texture(3)) { } void generateTerrain() { std::default_random_engine random; std::uniform_int_distribution<int> u(0, 9); for (int i = 0; i < kWidth; ++i) { for (int j = 0; j < kHeight; ++j) { if (u(random) < 2) { titles_[i][j] = &hillTerrain_; } else { titles_[i][j] = &grassTerrain_; } } } std::uniform_int_distribution<int> u100(0, kWidth-1); int x = u100(random); for (int i = 0; i < kHeight; ++i) { titles_[x][i] = &riverTerrain_; } } const Terrain& getTile(int x, int y) const { return *titles_[x][y]; } private: const static int kWidth = 100; const static int kHeight = 100; Terrain* titles_[kWidth][kHeight]; Terrain grassTerrain_; Terrain riverTerrain_; Terrain hillTerrain_; };
对比之前的做法,我们可以看到其实就是枚举类型和指针的区别,有人可能会决定指针的性能会略慢,主要考虑的是指针是间接引用,需要查找地址,而且为了获取地形的数据,需要先找到对象,然后在根据对象查找其属性,在跟踪这样的指针过程中会引起缓存未命中,拖慢程序的速度。但现代计算机非常的复杂,性能的问题通常都不是单一因素引起的,所以如果你想为了性能而不使用享元模式时,最好先分析性能的瓶颈在哪,享元模式下,内存数据存放的好,同样可以获得很高的性能。