两个字:过早优化。
你关心性能。但是考虑到你想制作一个 Minecraft 克隆,这意味着游戏世界可以很好地用一个 3D 数组来表示。在所有提到的编程语言中访问这些都相当快;游戏逻辑的执行时间应该比访问数百万个数组条目要多得多。那么,为什么要优化一个无论如何都不会占用大部分计算时间的部分——甚至在你编写一个最低限度的工作版本之前呢?
您可能想要创建一个代表游戏世界的 Java 接口或 Scala 特征。它提供了获取和存储游戏世界块内容的方法。稍后,您还可以添加批量方法以进一步优化性能;例如,将检查给定立方体中的所有块是否都是空的,或者计算木块的数量,沿着这些线。但一开始,最好忽略那些方法,或者做一些依赖重复调用抽象方法的琐碎实现。您可以稍后对其进行优化。
然后,您可以提供该接口的一个非常简单的 Java/Scala 实现,它实际上使用了一个三维数组。另一种方法是映射其键是坐标,值是块状态。优点是游戏世界的大小没有真正的限制,空块不会占用任何内存(对于空块的坐标,地图中没有条目)。缺点显然是性能。
此时,如果数据消耗过多内存,您可能需要考虑更紧密地打包数据。您可以使用位集。当您到达那个阶段时,使用 JNI 将一些用 C 或 C++ 编写的代码注入 JVM 中实际上是有意义的。因此,您将游戏逻辑保留在 Java/Scala 中,并在 C 中进行内存打包和查找。
创建一个可以创建 Java/Scala 和 C/C++ 版本的代码本机部分的通用“脚本”源没有真正意义;本机 C/C++ 函数将严重依赖无法直接转换为 Java 的优化。当您想以“纯 Java/Scala 模式”启动服务器时,即不使用 JNI 函数,只需使用您在上一步中创建的其他类。它们可能会慢一点,但它们是纯 JVM 字节码。而且由于您使它们保持简单,因此您不必疯狂地扩展它们或向它们引入新的错误。至少,创建或调整跨编程语言代码生成器的开销远远大于保留两个单独的代码库,尤其是在 Java/Scala 实现非常简单的情况下。
当然,位包装只能让你走这么远。您可能想注意到游戏世界的某些部分几乎完全是空的(尤其是地表以上的部分),而其他部分则包含充满相同类型块的巨大区域(例如几乎完全由石头组成的地下区域)。维护一个具有这么多冗余的巨大内存结构确实是在浪费内存。因此,您可能会考虑将游戏世界打包成一棵树,其中每个节点代表游戏世界的一个大立方体区域,孩子们将其进一步细分,直至描述仅描述一个特定游戏世界坐标内容的叶子。当一个节点只有相同内容类型的子节点时,您不需要存储子节点。在这一点上简单地砍树并让节点说,“你不需要再看下去了。很好!- 当然,你必须保持树是可变的。如果世界中仅由一个节点表示的无聊部分发生了变化,您需要打开该节点并将其拆分为两个或多个子节点。如果之后又变得简单,你可以再次和孩子们一起砍树。
此时您可能会注意到的一件事是:内存打包和访问优化在这里不再是真正的问题。像这样的树不能通过使用存储和查找方法的本机函数来合理优化。如果你能从这样的优化中获得超过 10% 的收益,那么这将是非常不可能的并且非常令人印象深刻。(更可能的是,这可能意味着 Java/Scala 对应的优化很差。)如此最小的速度提升并不能证明需要投入的巨大额外努力是合理的。而是将更好的 CPU 放入机器中,享受通过吃冰淇淋、观看 Dr. House 或继续进一步增强游戏并使其对玩家更有趣和更具吸引力所节省的时间。通过创造能够真正改善产品的有价值的东西。
但这仍然不是它。如果我没记错的话,Minecraft 世界的初始状态是程序生成的。使用分形算法,您真的可以在眨眼间创造出让人感觉复杂自然的无尽区域。因此,与其预先计算游戏世界的内容并将其存储在一个巨大的数据结构中,不如使用世界生成过程作为查找方法:与其从内存中查找坐标的内容,不如简单地使用算法。这样一来,世界的初始状态就可以完全存储在四个字节中:算法的种子值。
当然,世界不会一直停留在这种状态。当玩家(或其他东西)改变世界时,这就是你需要存储的东西。因此,您只存储世界的种子值和对其所做的更改。每当您查找坐标的内容时,请尝试在更改的图块存储中找到它。当它在那里时,使用该信息。当它不存在时,默认为程序世界生成算法。这将使您的内存消耗大大减少。而且由于世界的变化相对较小并且包含巨大的空白区域,因此您应该相对容易编写一个快速有效地存储这些变化的数据结构。同样,为此编写本机代码不会产生显着的性能提升,也不值得付出努力。
但是可以优化其他东西:程序世界生成算法。这是您可能希望用 C 或 C++ 编写的一个关键组件。它应该相对较小且代码不多,但它是数学密集型的,并且会经常被调用。所以优化它并用它制作一个小型 JNI 库。这将为您带来巨大的性能提升,值得付出努力。(当然,你可能想先做一个 Java/Scala 实现。如果这已经足够快,那么就没有必要陷入 JNI 的麻烦。)
如果你的世界生成过程仍然太慢,那么你可以为它实现一个缓存。缓存甚至可以在JVM有一些懒惰的时候抢先生成玩家的一些环境。
我为您列出了这个开发过程,作为几个想法的迭代,一个比前一个更好。您可能已经在第一阶段就开始编写优化的 C/C++ 代码库了。这将是浪费时间;你本可以在后期把它全部扔掉。使用 C 语言编写的位打包的高效数组存储是一件好事,但是当您将世界重新组织成二进制空间分区树时,它绝对没有用。
所以,不要过度。当您无法仅在 Java/Scala 中创建最低限度的工作(但缓慢且未优化)版本时,您也无法在 C/C++ 或某些交叉编译脚本语言中创建优化版本。先做简单版本,再做性能测试,真正需要的时候才优化。不要通过首先制定优化概念来开始您的项目。这种优化应该是您最后要做的事情。