不,真正的程序不是那样写的。真正的程序是通过注意到所有的怪物有很多共同点来编写的,并使用相同的代码来做这些事情。他们都寻路,但他们可以走的距离不同?伟大的。添加一个max_walking_distance
变量并每次调用相同的函数。
你所有的怪物都有 3D 模型吗?那么你就不需要虚拟render
方法了。您可以只渲染模型。
您不必根据“合理”的界限划分数据。您不必拥有. struct monster
你可以有一个struct monster_pathfinding
和一个struct monster_position
和一个struct monster_3d_model
。即使您只是将它们放在并行数组中(例如,monster 123 的寻路信息在monsters_pathfinding[123]
其中,位置在 中monster_positions[123]
),这也可以更有效地利用数据缓存,因为寻路代码不会将 3D 模型指针加载到缓存中。如果某些怪物没有寻路或没有,您可以通过跳过条目来变得更聪明使成为。本质上,建议您根据数据的使用方式将数据分组在一起,而不是根据您对游戏中事物的心理模型。是的,跳过条目使删除怪物变得更加困难。但是你经常勾选怪物,而且你不会经常删除怪物,对吧?
也许只有少数怪物向玩家开枪(其余的试图吃掉玩家)。如果你有 200 个怪物,但其中只有 10 个有枪,你可以有一个struct monster_gun_data {int ammunition; int max_ammunition; int reload_time; monster_position *position;};
然后,你的monstersShootGunsAtPlayers
函数只需要遍历monster_gun_data
数组中的 10 个条目(并通过指针加载它们的位置)。或者,您可能会对此进行分析并发现由于您游戏中的大多数怪物都有枪,因此迭代所有怪物并检查它们的MONSTER_HAS_GUN
标志比通过无法轻松预取的指针访问位置要快一些.
你如何进行不同类型的怪物攻击?好吧,如果它们完全不同(近战与远程),您可能会使用您所描述的不同功能来完成它们。或者,您可能只在确定怪物要攻击玩家后才检查攻击类型。您似乎建议怪物使用不同的攻击代码,但我敢打赌这几乎适用于所有怪物:
if(wantsToAttack(monster, player)) {
if((monster->flags & HAS_RANGED_ATTACK) && distance(monster, player) > monster->melee_attack_distance)
startRangedAttack(monster, player);
else
startMeleeAttack(monster, player);
}
拿着枪的怪物和拿着弓箭的怪物有什么区别呢?攻击速度、动画、弹丸移动的速度、弹丸的 3D 模型以及它造成的伤害量。这就是所有的数据。那不是不同的代码。
最后,如果你有完全不同的东西,你可以考虑把它做成一个带有虚函数的“策略对象”。如果可以的话,或者只是一个普通的函数指针。请注意,该Monster
对象仍然不是多态的,因为如果是,我们就不能拥有它们的数组,这会减慢所有公共代码的速度。只有我们所说的怪物的特定部分是多态的,实际上是多态的。
void SpecialBossTickFunction(Monster *monster) {
// special movement, etc
}
// ...
monster->onTick = &SpecialBossTickFunction;
// monster is still not polymorphic except for this one field
你也可以这样做:
struct SpecialBossTickStrategy : TickStrategy {
void onTick(Monster *monster) override {...}
// then you can also have extra fields if needed
// but you also have more indirection
};
monster->onTick = new SpecialBossTickStrategy;
并且不要做不必要的事情。尝试事件驱动,而不是每刻都做事:
// bad because we're calling this function unnecessarily every tick
void SpecialUndeadMonsterTickFunction(Monster *monster) {
if(monster->isDead) {
// do some special reanimation sequence
}
}
monster->onTick = &SpecialUndeadMonsterTickFunction;
// better (for performance)
void SpecialUndeadMonsterTickWhileDeadFunction(Monster *monster) {
// do some special reanimation sequence
if (finished doing whatever) {
monster->onTick = NULL;
}
}
void SpecialUndeadMonsterDeathFunction(Monster *monster) {
monster->onTick = &SpecialUndeadMonsterTickWhileDeadFunction;
}
// ...
monster->onDead = &SpecialUndeadMonsterDeathFunction;
// Also better (for performance)
void DoUndeadMonsterReanimationSequences() { // not virtual at all, called from the main loop
for(Monster *monster : special_undead_monsters_which_are_currently_dead) {
// do some special reanimation sequence
}
}
// Not great, but perhaps still less bad than the first one!
void DoUndeadMonsterReanimationSequences() { // not virtual at all, called from the main loop
for(Monster &monster : all_monsters) {
if(monster.type == TYPE_SPECIAL_UNDEAD_MONSTER && monster.isDead) {
// do some special reanimation sequence
}
}
}
请注意,在第三个示例中,您必须使该数组保持special_undead_monsters_which_are_currently_dead
最新。没关系,因为您只需要在怪物生成、消失、死亡或不死时更改它。这些都是相对罕见的事件。你在这些事件中做更多的工作,以节省每一次的工作。
最后,请记住,这些技术可能会或可能不会在您的实际程序中提高性能。我认为国防部是一个收集想法的包。它并没有说你必须以某种特定的方式编写程序,但它提供了一堆非传统的建议,解释它们为什么工作的理论,以及其他人如何在他们的程序中使用它们的例子。由于 DOD 通常建议您完全重组数据结构,因此您可能只想在程序的性能关键区域中实现它。