不,真正的程序不是那样写的。真正的程序是通过注意到所有的怪物有一堆共同点,并使用相同的代码来完成这些事情而编写的。他们都是寻路者,但他们可以走的距离不同?伟大的。添加一个 max_walking_distance
变量并每次调用相同的函数。
你所有的怪物都有 3D 模型吗?那么您就不需要虚拟 render
方法。您可以只渲染模型。
您不必根据“合理”边界划分数据。您不必必须有 struct monster
。您可以有一个 struct monster_pathfinding
和一个 struct monster_position
以及一个 struct monster_3d_model
。即使你只是将它们放在并行数组中(即怪物 123 的寻路信息在 monsters_pathfinding[123]
中,它的位置在 monster_positions[123]
中),这也可以更有效地利用数据缓存,因为寻路代码没有t 将 3D 模型指针加载到缓存中。如果某些怪物不寻路或不渲染,您可以通过跳过条目来变得更聪明。从本质上讲,为了提高性能,建议您根据数据的使用方式将数据组合在一起,而不是根据您对游戏中事物的心理模型。是的,跳过条目会使删除怪物变得更加困难。但是你刷怪物的次数很多,你也不会经常删除怪物,对吧?
也许只有少数怪物向玩家开枪(其余的怪物试图吃掉玩家)。你可以有一个 struct monster_gun_data {int ammunition; int max_ammunition; int reload_time; monster_position *position;};
,然后如果你有 200 个怪物,但其中只有 10 个有枪,你的 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 通常建议您完成对数据结构的重组,因此您可能只想在程序的性能关键领域实施它。
,
只是为顶级问题添加更多视角:
要求非常好的性能的大项目真的不用多态吗?
您错过了一整类多态性。
我经常在一个项目中混合使用以下所有三种样式,因为并非所有代码都具有相同的性能要求:
-
设置和配置代码通常不需要(非常)快。对属性、工厂等使用 OO 风格和运行时多态性。
运行时多态泛指虚函数。
-
确实需要快速的稳态代码通常可以使用编译时多态性。这适用于具有相似接口的静态已知(理想情况下为小型)类型集合。
编译时多态意味着模板(函数模板、类型模板、用等效的策略替换运行时策略模式等)
-
具有最严格性能要求的代码可能需要面向数据(即围绕缓存友好性而设计)。
这不是项目中的全部代码,甚至可能不是所有需要快速处理的代码。这是所有需要快速且性能受缓存效果支配的代码。
如果你只有一个对象的副本,你可能会尽可能地内联(并尝试将它放入尽可能少的缓存行中),但是将它分成四个不同的数组,每个数组只有一个元素不会取得很大成就。
本文链接:https://www.f2er.com/23527.html