《游戏编程模式》

Posted pandawuwyj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《游戏编程模式》相关的知识,希望对你有一定的参考价值。

Chatper 11 字节码

通过将行为编码成虚拟机指令,而使其具备数据的灵活性。

 

解释器模式(慢):

 1 class Expression
 2 {
 3 
 4 public:
 5   virtual ~Expression() {}
 6   virtual double evaluate() = 0;
 7 
 8 };
 9 
10 class NumberExpression : public Expression
11 {
12 
13 public:
14   NumberExpression(double value)
15   : value_(value)
16   {}
17  
18   virtual double evaluate()
19   {
20     return value_;
21   }
22 
23 private:
24   double value_;
25 
26 };
27 
28 class AdditionExpression : public Expression
29 {
30 
31 public:
32   AdditionExpression(Expression* left, Expression* right)
33   : left_(left),
34     right_(right)
35   {}
36 
37   virtual double evaluate()
38   {
39     // Evaluate the operands.
40     double left = left_->evaluate();
41     double right = right_->evaluate();
42  
43     // Add them.
44     return left + right;
45   } 
46 
47 private:
48   Expression* left_;
49   Expression* right_;
50 
51 };

 

字节码模式:

当你的游戏需要定义大量行为,可以指定一套指令集,虚拟机逐条执行指令栈上的这些指令。通过组合指令,即可完成很多高级指令。

 1 enum Instruction
 2 {
 3   INST_SET_HEALTH = 0x00,
 4   INST_SET_WISDOM = 0x01,
 5   INST_SET_AGILITY = 0x02,
 6   INST_GET_HEALTH = 0x03,
 7   INST_GET_WISDOM = 0x04,
 8   INST_GET_AGILITY = 0x05,
 9   INST_PLAY_SOUND = 0x06,
10   INST_SPAWN_PARTICLES = 0x07,
11   INST_LITERAL = 0x08,
12   INST_ADD = 0X09,
13 };
14 
15 class VM
16 {
17 
18 public:
19   VM()
20   : stackSize_(0)
21   {}
22  
23   void interpret(char bytecode[], int size)
24   {
25     for (int i = 0; i < size; i++)
26     {
27       char instruction = bytecode[i];
28       switch (instruction)
29       {
30          case INST_SET_HEALTH:
31          {
32               int amount = pop();
33               int wizard = pop();
34               setHealth(wizard, amount);
35               break;
36          } 
37 
38          case INST_SET_WISDOM:
39          case INST_SET_AGILITY:
40          // Same as above... 
41 
42          case INST_GET_HEALTH:
43          {
44               int wizard = pop();
45               push(getHealth(wizard));
46               break;
47          } 
48 
49          case INST_GET_WISDOM:
50          case INST_GET_AGILITY:
51          // You get the idea...
52 
53          case INST_PLAY_SOUND:
54               playSound(SOUND_BANG);
55               break;
56 
57          case INST_SPAWN_PARTICLES:
58               spawnParticles(PARTICLE_FLAME);
59               break;
60 
61          case INST_LITERAL:
62          {
63               // Read the next byte from the bytecode.
64               int value = bytecode[++i];
65               push(value);
66               break;
67          }
68  
69          case INST_ADD:
70          {
71               int b = pop();
72               int a = pop();
73               push(a + b);
74               break;
75          }
76     }
77   } 
78 
79 private:
80      void push(int value)
81      {
82          // Check for stack overflow.
83          assert(stackSize_ < MAX_STACK);
84          stack_[stackSize_++] = value;
85      }
86 
87      int pop()
88      {
89          // Make sure the stack isn‘t empty.
90          assert(stackSize_ > 0);
91          return stack_[--stackSize_];
92      }
93 
94      static const int MAX_STACK = 128;
95      int stackSize_;
96      int stack_[MAX_STACK]; 
97 
98 };

 

指令的组合:

1 setHealth(0, getHealth(0) + (getAgility(0) + getWisdom(0)) / 2);

翻译为:

 1 LITERAL 0    [0]            # Wizard index
 2 LITERAL 0    [0, 0]         # Wizard index
 3 GET_HEALTH   [0, 45]        # getHealth()
 4 LITERAL 0    [0, 45, 0]     # Wizard index
 5 GET_AGILITY  [0, 45, 7]     # getAgility()
 6 LITERAL 0    [0, 45, 7, 0]  # Wizard index
 7 GET_WISDOM   [0, 45, 7, 11] # getWisdom()
 8 ADD          [0, 45, 18]    # Add agility and wisdom
 9 LITERAL 2    [0, 45, 18, 2] # Divisor
10 DIVIDE       [0, 45, 9]     # Average agility and wisdom
11 ADD          [0, 54]        # Add average to current health
12 SET_HEALTH   []             # Set health to result

 

基于栈的虚拟机:

1.  指令小(通常一字节);

2.  指令数多

基于寄存器的虚拟机:

  1. 指令大(Lua虚拟机每个指令占用32位,6位存储指令类型,剩下的存储参数);
  2. 指令数少

值的表示:

  1. 坚持使用单一数据类型 or
  2. 使用带标签的联合体
 1 enum ValueType
 2 {
 3   TYPE_INT,
 4   TYPE_DOUBLE,
 5   TYPE_STRING
 6 }; 
 7 
 8 struct Value
 9 {
10   ValueType type;
11 
12   union
13   {
14     int    intValue;
15     double doubleValue;
16     char*  stringValue;
17   };
18 };

 

Chatper Twelve 子类沙盒

使用基类提供的操作集合来定义子类中的行为。

扁平的继承树比起长纵深的继承树更易用。 

使用情境:

  1. 有一个带有大量子类的基类;
  2. 基类能够提供所有子类可能需要的操作集合;
  3. 希望更简便地共享子类之间重复的代码;
  4. 使子类和程序其他模块的耦合最小化。 

基类:

 1 class Superpower
 2 {
 3 
 4 public:
 5   virtual ~Superpower() {}
 6  
 7 protected:
 8   virtual void activate() = 0;
 9  
10   double getHeroX()
11   {
12     // Code here...
13   }
14 
15   double getHeroY()
16   {
17     // Code here...
18   }
19 
20   double getHeroZ()
21   {
22     // Code here...
23   }
24  
25   void move(double x, double y, double z)
26   {
27     // Code here...
28   } 
29 
30   void playSound(SoundId sound, double volume)
31   {
32     // Code here...
33   } 
34 
35   void spawnParticles(ParticleType type, int count)
36   {
37     // Code here...
38   }
39 
40 };

Activate()就是沙盒函数,子类必须重写这个抽象虚函数。Move()或许和物理引擎有关,playSound()或许和声音模块有关,在基类中实现使得所有的耦合都有SuperPower这个基类来承担。

子类:

 1 class SkyLaunch : public Superpower
 2 {
 3 
 4 protected:
 5   virtual void activate()
 6   {
 7     if (getHeroZ() == 0)
 8     {
 9       // On the ground, so spring into the air.
10       playSound(SOUND_SPROING, 1.0f);
11       spawnParticles(PARTICLE_DUST, 10);
12       move(0, 0, 20);
13     }
14     else if (getHeroZ() < 10.0f)
15     {
16       // Near the ground, so do a double jump.
17       playSound(SOUND_SWOOP, 1.0f);
18       move(0, 0, getHeroZ() - 20);
19     }
20     else
21     {
22       // Way up in the air, so do a dive attack.
23       playSound(SOUND_DIVE, 0.7f);
24       spawnParticles(PARTICLE_SPARKLES, 1);
25       move(0, 0, -getHeroZ());
26     }
27   }
28 
29 };

 

设计决策:

需要提供什么操作(多少操作)

  1. 仅被少数子类使用的操作不必加入基类;
  2. 不修改任何状态的方法调用,不具备侵入性,是个安全的耦合;

避免臃肿的基类:

把相关操作分流到辅助类

 1 class SoundPlayer
 2 {
 3   void playSound(SoundId sound, double volume)
 4   {
 5     // Code here...
 6   }
 7  
 8   void stopSound(SoundId sound)
 9   {
10     // Code here...
11   } 
12 
13   void setVolume(SoundId sound)
14   {
15     // Code here...
16   }
17 };
18 
19 class Superpower
20 {
21 
22 protected:
23   SoundPlayer& getSoundPlayer()
24   {
25     return soundPlayer_;
26   } 
27 
28   // Sandbox method and other operations...
29  
30 private:
31   SoundPlayer soundPlayer_;
32 
33 };

 

基类如何获取所需状态

分段初始化:

构造函数不传参,在第二步传递所需参数

1 Superpower* createSkyLaunch(ParticleSystem* particles)
2 {
3   Superpower* power = new SkyLaunch();
4   power->init(particles);
5   return power;
6 }

 

状态静态化

 1 class Superpower
 2 {
 3 
 4 public:
 5   static void init(ParticleSystem* particles)
 6   {
 7     particles_ = particles;
 8   } 
 9 
10   // Sandbox method and other operations...
11  
12 private:
13   static ParticleSystem* particles_;
14 
15 };

静态变量Particle_不必为每个SuperPower实例所存储,可以减少内存占用。

  

Chatper 13 类型对象

通过创建一个类来支持新类型的灵活创建,其每个实例都代表一个不同的类型对象。

 

类型对象:

 1 class Breed
 2 {
 3 
 4 public:
 5   Breed(int health, const char* attack)
 6   : health_(health),
 7     attack_(attack)
 8   {}
 9 
10   Monster* newMonster() { return new Monster(*this); } 
11 
12   int getHealth() { return health_; }
13   const char* getAttack() { return attack_; } 
14 
15 private:
16   int health_; // Starting health.
17   const char* attack_;
18 
19 };

使用类型对象的类:

 1 class Monster
 2 {
 3   friend class Breed; 
 4 
 5 public:
 6   const char* getAttack() { return breed_.getAttack(); }
 7  
 8 private:
 9   Monster(Breed& breed)
10   : health_(breed.getHealth()),
11     breed_(breed)
12   {}
13  
14   int health_; // Current health.
15   Breed& breed_;
16 
17 };

这样创建一个怪物:

1 Monster* monster = someBreed.newMonster();

Monster的构造函数是private,不能直接调用。友元类Breed绕开了这个限制,可以直接访问,这意味着newMonster方法是创建Monster的唯一方法。

 

通过继承共享数据

基种族:

 1 class Breed
 2 {
 3 
 4 public:
 5   Breed(Breed* parent, int health, const char* attack)
 6     : health_(health),
 7       attack_(attack)
 8   {
 9       // Inherit non-overridden attributes.
10       if (parent != NULL)
11       {
12         if (health == 0) health_ = parent->getHealth();
13         if (attack == NULL) attack_ = parent->getAttack();
14       }
15   }
16 
17   int         getHealth() { return health_; }
18   const char* getAttack() { return attack_; }
19 
20 private:
21   Breed*      parent_;
22   int         health_; // Starting health.
23   const char* attack_;
24 
25 };

 

类型能否改变

怪物死的时候,尸体能变成会动的僵尸:

  1. 类型不变

创建新的僵尸怪物,拷贝原怪物属性,然后删除。有新对象创建,编码和理解较简单,易于调试。

  1. 类型可变

简单赋值或修改属性即可。减少对象创建,但新类型需要符合对象现在的状态。

以上是关于《游戏编程模式》的主要内容,如果未能解决你的问题,请参考以下文章

《游戏编程模式》记录

#游戏编程模式# --- 框架性能游戏

《游戏编程模式》

从片段调用 Google Play 游戏服务

使用 Git 来管理 Xcode 中的代码片段

游戏编程模式KeyNote