![]() |
1
50
那么,国防部是怎么回事?显然,这是关于性能的,但不仅仅是这样。它还涉及设计良好的代码,可读性强,易于理解,甚至可重用。 现在的问题是,在你的电脑cpu的工作方式完全不同。当你让它一次又一次地做同样的事情时,效果最好。为什么?因为一个叫缓存的小东西。在现代计算机上访问RAM可能需要100或200个CPU周期(CPU必须一直等待!),太长了。所以CPU上的这一小部分内存可以很快被访问,缓存。问题是它最多只有几MB。因此,每次需要缓存中不存在的数据时,仍然需要在很大程度上使用RAM。不仅数据是这样,代码也是这样。当代码从RAM加载时,尝试执行不在指令缓存中的函数将导致暂停。 回到面向对象编程。对象很大,但大多数函数只需要一小部分数据,所以我们通过加载不必要的数据来浪费缓存。方法调用调用其他方法的其他方法,从而破坏指令缓存。尽管如此,我们还是经常一遍又一遍地做同样的事情。举个游戏中的子弹为例。在一个简单的实现中,每个项目符号都可以是一个单独的对象。可能有一个项目符号管理器类。它调用第一个项目符号的update函数。它使用方向/速度更新三维位置。这将导致来自对象的大量其他数据加载到缓存中。接下来,我们调用世界管理器类来检查与其他对象的冲突。这会将许多其他内容加载到缓存中,甚至可能会导致原始bullet manager类中的代码从指令缓存中删除。现在我们返回到项目符号更新,没有冲突,所以我们返回到项目符号管理器。它可能需要再次加载一些代码。下一步,bullet#2更新。这会将大量数据加载到缓存中,调用world。。。在这种夸张的情况下,我们有两个加载代码的暂停,假设两个加载数据的暂停。这至少浪费了400个循环,一颗子弹,我们还没有把击中其他物体的子弹考虑在内。现在一个CPU运行在3+GHz,所以我们不会注意到一颗子弹,但是如果我们有100颗子弹呢?或者更多? 所以这是一个有很多故事的地方。是的,在某些情况下,您只使用了对象、管理器类、文件访问等,但更常见的是,有很多类似的情况。幼稚的,甚至不幼稚的面向对象设计会导致很多问题。所以进入面向数据的设计。国防部的关键是围绕数据建立代码模型,而不是像面向对象设计那样。这从设计的第一阶段开始。您不需要先设计OO代码,然后再对其进行优化。您首先列出并检查数据,然后考虑如何修改它(稍后我将讨论一个实际的示例)。一旦您知道代码将如何修改数据,您就可以以一种尽可能高效的方式来处理它。现在,您可能认为这只会导致到处都是可怕的代码和数据,但只有当您设计得不好时才会出现这种情况(糟糕的设计与OO编程一样容易)。如果设计得好,代码和数据可以围绕特定的功能进行整洁的设计,从而产生可读性很强甚至可重用的代码。
所以回到“有一有多”的话题。在设计OO代码时,您需要考虑一个对象,原型/类。一个子弹有一个速度,一个子弹有一个位置,一个子弹会以它的速度移动每一帧,一个子弹能击中什么东西,等等。当你想到这个,你会想到一个类,有速度,位置,还有一个更新函数,它移动子弹并检查碰撞。但是,当您有多个对象时,您需要考虑所有对象。子弹有位置,有速度。有些子弹可能有碰撞。你看到我们不再考虑单个物体了吗?我们考虑了所有这些问题,现在设计的代码也大不相同。
关于问题的第一部分,这是国防部的其他例子。对不起,我没有那么多。不过,有一个非常好的例子,我在前一段时间遇到过,Bjoern Knafla的行为树面向数据设计系列: http://bjoernknafla.com/data-oriented-behavior-tree-overview 希望这仍然有帮助,尽管老问题。或者其他用户遇到了这个问题,并从这个答案中得到了一些帮助。 |
![]() |
2
2
我和你差不多在同一条船上。
完成设计后,您会问自己以下问题:
把你的整个设计写成一个功能性的方法,有很多附属的方法。它让我想起了我年轻时的50万行Cobol程序。 现在,你可能不会把整个游戏写成一个巨大的函数方法。实际上,在这篇文章中,Noel谈论的是游戏的渲染部分。把它想象成一个游戏引擎(一个巨大的函数方法)和驱动游戏引擎的代码(OOP代码)。 你在用物体来思考。试着从功能的角度来思考。 每个敌人的更新都是一个循环的迭代。
|