《游戏编程模式》
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. 指令数多
基于寄存器的虚拟机:
- 指令大(Lua虚拟机每个指令占用32位,6位存储指令类型,剩下的存储参数);
- 指令数少
值的表示:
- 坚持使用单一数据类型 or
- 使用带标签的联合体
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 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 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 };
类型能否改变
怪物死的时候,尸体能变成会动的僵尸:
- 类型不变
创建新的僵尸怪物,拷贝原怪物属性,然后删除。有新对象创建,编码和理解较简单,易于调试。
- 类型可变
简单赋值或修改属性即可。减少对象创建,但新类型需要符合对象现在的状态。
以上是关于《游戏编程模式》的主要内容,如果未能解决你的问题,请参考以下文章