我目前正在开发一款小型地下城模拟游戏。游戏非常详细,我计划随着时间的推移,有一个代表“怪物”的类的+ 200k实例。它们包含该怪物的特权,技能和历史。像他使用了多少药水,他住在哪里,他的巡逻路线是什么等等。
我开始用SQLite实现这一点并使用一个名为“monsters”的简单表,其中包含所有数据。这允许我使用SQL查询来查找每帧上模拟计算所需的怪物。例如:找到所有巡逻A点的怪物,或找到使用了药水X的所有怪物等等。不幸的是,在每一帧上多次查询SQLite会很快减慢游戏速度。即使它是2D游戏,我也需要宝贵的毫秒来进行模拟计算。
此外,我将来需要JOIN来制作图表:我需要知道怪物是否攻击了另一个怪物,或者怪物是否是另一个怪物团队的一部分。这会使事情进一步放缓。
有没有人对如何处理此问题有任何建议?
我的数据类似于:
答案 0 :(得分:6)
如果您不使用实体组件系统,请考虑将游戏迁移到使用一个。每个相关数据位可以存储在一个独立的组件中,实体本身是一个标识该组件的不透明标识符。使用ECS时,不要让游戏实体挂起一堆数据,而是颠倒关系。特定类型的所有组件一起存在于一个大池中,并且您的实体只是一个标识符,用于指定它们关注的池中的哪个组件。这允许您做的是批量组件更新。如果每个具有库存的怪物都有一个库存组件,则所有库存组件可以或多或少地连续存储在内存中。这意味着在处理它们时,您具有高缓存一致性,这可以显着提升性能。
也可能是你每次都试图做太多。使用实体组件系统,您可以根据需要将特定子系统限制为每X帧或每X秒。也许AI组件只需要每秒运行一次以考虑下一步该做什么,但是它们需要不断移动以便每帧都更新位置。或者将图表和图形中的一个放在一起需要花费很长时间才能在一个帧中完成,因此您可以每隔一帧计算一次,或者将处理分成两帧,以便在帧上迭代一半以上的实体,其余的在第二帧上迭代帧。有很多方法可以拆分它。
Here is some more reading on component systems if you haven't seen them before
答案 1 :(得分:3)
据我了解,您遇到的主要问题是找到存储怪物的优化方法。例如,您可以使用一些树数据结构在平面上查找有效的所需怪物。这些数据结构之一是BSP(二进制搜索分区)树。这是简要说明https://en.wikipedia.org/wiki/Binary_space_partitioning。 Qt的图形视图框架使用这种方法。有关它的更多信息,请参阅http://doc.qt.io/qt-4.8/graphicsview.html
答案 2 :(得分:3)
还没有被接受的答案,所以我会给你一个问题。但我会对你说实话; - )
首先:你没有指定太多的细节,这使得一个非常合适的答案很难,如果不是不可能的话。对我来说,不清楚你想在什么时间处理多少数据。你提到的是框架,但这是一个框架,就像每秒帧数一样,还是更像是我所说的#34;世界的标记"? 如果你想以> 30fps的速度运行游戏并每秒更新整个世界状态30次:不是,我想你可以忘记这样做(我们做了一个作为CUDA讲座的一部分,大约有1000个节点/人的恐慌模拟。虽然这比模拟更简单,但它几乎无法在GTX780上实时运行;所以我假设一个经验丰富的CUDA开发人员很可能会在该硬件上达到10,000个节点的限制 - 并且您希望拥有> 20倍的节点,其中更多的方式更复杂的AI /模拟而不是"远离火源到下一个可见的出口并且如果您的恐慌程度太高+简单的二维墙碰撞"),则会践踏其他人。
但是,如果逐帧表示世界模拟标记,那么是的,这可能是可行的。
现在你的问题还有一些细节缺失:你是否计划开发一个拥有> 200k怪物和数千名玩家的专用MMO服务器,或者它是一个本地主机单人游戏?或介于两者之间(网络多人RPG,最多16名玩家)?
如果你只有一些玩家(我猜是这样,因为你说2D;并且一个玩家或者四个玩家之间没有太大差异):不要完全做所有的模拟细节一下子。对于完全沉浸,在玩家附近进行详细模拟就足够了。就像在纸笔和纸上一样:作为游戏大师(GM),你通常只会在世界各地发生一些关键事件,你可以在你去的时候做出其余的事情/粗略地概述其他地方发生的事情,但没有确切的说明。细节。如果你是一名优秀的总经理,那对于球员来说足够有说服力,因为谁会关心50英里以外的某个王座室内卫兵当前的位置?
我有一种感觉,你想要做一个正确的,完全模拟的游戏,在NPC /怪物之间进行充分的社交互动,因为没有其他人在做那样的事情" (如果我错了,请纠正我),但有一个很好的理由没有人这样做:这很难。
想法:如果您考虑"区域",您只需要在玩家当前活动的那些区域中运行模拟。所有其他区域都被冻结。一旦玩家切换区域,您就可以解冻并快进新区域。您可以完整地详细说明,也可以近似它。如果您不想加载屏幕,可以解冻并快进他们可能进入的玩家附近的区域。
最重要的是,您应该了解您的架构。例如,你提到你想知道什么怪物做了药水X.当然这很容易写在SQL中,但你不会对性能感到满意。或者至少我不认为我愿意,并且经过一次基本的数据库讲座后,我们会写一个高性能的SQL服务器" -advanced-lecture [full]披露:由于我通常不使用SQL,因此我在编写高性能SQL查询时表现不佳。另外:谁需要完整的游戏ACID? 好的,为了简单起见,您可以将经常需要的东西放入SQL DB(怪物高度,重量,风味文本,......?),或ECS,或您认为最好的技术中。但是你想要每隔几秒触摸的一切都可以进入记忆。我的意思是,如果你为每个怪物存储1kByte,那么对于200k怪物,你的存储量大约为200MBy。
无论如何,回到问题"哪些怪物叮叮作为药水X?":你为什么要知道这个?要应用效果?检查效果是否磨损?我正在使用一个事件队列:一个怪物喝一剂力量 - >更新库存,给它奖励STR,计算超时并为事件排队"奖励STR磨损为"。 这可能比处理200MByte的内存更快,因为你只需要做什么"需要做什么",每个刻度 - 而不是"检查每个可能的条件下的所有内容,每一个刻度"。
另外,请注意你的算法:你有人X知道人Y关系,注明"公共/私人"?根据您在该图表上的操作,您可能会遇到NP-hard problems。例如,如果您不小心尝试解决clique problem的导数,则可能会遇到错误的时间。
我可以添加更多内容,但由于问题有点模糊,我会停在这里,希望你能得到一些好主意。
答案 3 :(得分:2)
我不会在每一帧查询SQL数据库,而是缓存 你认为的怪物最有可能在计算中需要。
我应该缓存多少怪物?
如果有人知道,那就是你!但是,我认为只有通过试验才能找到缓存大小的最佳位置。您可以搜索缓存的实现并获得灵感,例如this。
为什么要在第一时间使用SQL?考虑写入磁盘。
您的图片会显示一个图表,那么为什么不将您需要的内容存储为图表?如你所知,The Boost Graph Library (BGL)可以派上用场!检查一下:https://stackoverflow.com/questions/2751826/which-c-graph-library-should-i-use
答案 4 :(得分:1)
有两种方法可以限制发生的事情,它们可以一起使用。
假设怪物不是每一帧都行动,怪物的下一个动作可以安排在未来的N帧中,通过计算出需要在这个帧上执行的动作,然后是怪物的数量。处理每帧,可以减少。
sqlite有一个R * Tree,支持只选择地理区域内的那些项目。
屏幕只能显示那些可见的怪物,这样只允许屏幕区域内的怪物进行精确模拟,其他怪物可以进行更粗略的模拟。
据我所知,魔兽世界有一个怪物位置的时间等式......
MyXY = getPosition( me, currentTime );
当怪物处于稳定的“未被观察状态”时,这将是怪物的自然状态。这意味着怪物的动作在不可见时根本不需要模拟。
答案 5 :(得分:1)
根据以前的答案/评论以及您对它们的回答,我假设您决定继续使用SQL,但您希望有更好的方法来阅读/处理它。因此,我的回答只关注这个方面,而且只与设计有关,而不是与其他建议有关(比如使用不同的SQL引擎)。
您的数据库中的主要问题,正如它在您的原始问题中所反映的那样,给定怪物的所有方面都在一个记录中,都在同一个表中。随着时间的推移,这使得桌子变得非除此之外,这种设计使您的数据库和代码难以维护和发展。
虽然它可能听起来像#34;对"将怪物的所有细节保存在单个记录中(也许是在代码本身中反映它的单个对象),这很少是一个好的决定。你应该在你的" world"中建模的对象属性之间有明确的分离。到那些用软件建模的属性。例如,怪物的位置可能会经常更改,而其名称则不然。由于您将所有数据保存在一个表中,因此对该位置的任何更改都在包含所有其他属性的同一个表上完成,这使得它非常繁重。
相反,你要做的是:
这样,任何读取和更改都只在相关的上下文中完成;例如,更改怪物的位置只会更改位置表,而不会影响更持久的细节表。连接不应该花费很多时间,只要您有良好的索引并且只过滤那些您感兴趣的数据。 除了加快查询速度之外,这种设计更加灵活。例如,如果要向怪物添加新类型的属性,则可以添加新表,或使用具有类似数据的现有表(例如怪物的设备)来添加它。
如果您的查询非常依赖"地理"您仍然希望使用关系数据库来处理它,您可能会考虑在空间查询中有更好支持的其他类型的数据库。
HTH!