现代复杂游戏中成就系统的实现

51

现在许多游戏都自带成就系统,奖励玩家完成某些任务。而stackoverflow的徽章系统正是一个这样的例子。

然而,这里存在一些问题,我无法找到好的解决方案。

成就系统必须时刻关注某些事件,比如一个游戏提供20到30个与战斗有关的成就。服务器需要检测这些事件(例如:玩家在此战斗中避免了对手的x次攻击或玩家走了x英里)一直

  • 服务器如何处理这么多的操作而不会减速甚至崩溃?

成就系统通常需要核心引擎中才会用到的数据,如果没有这些讨厌的成就,这些数据也不会被使用(例如:玩家在每场战斗中跳了多少次,你不想将所有这些信息存储在数据库中)。我的意思是,在某些情况下,添加一个成就的唯一方法就是将检查其当前状态的代码添加到游戏核心中,而这通常是一个非常糟糕的想法。

  • 成就系统如何与保留不必要信息的游戏核心进行交互?(参见上面的例子)

  • 它们如何与游戏核心分离?

我的例子可能看起来“无害”,但想象一下魔兽世界中目前1000多个成就和同时在线的众多玩家。


2
我确定你要找的是这个:如何设置一个灵活的框架来处理成就? 这些人提出了一些非常好的解决方案。 - Diego Palomar
1
这是一个非常好的问题,作为一名游戏玩家,我自从掌握了编程技能以来就一直在思考这个问题。我知道事件肯定与此有关,但我仍然想知道一件事:旧游戏并没有考虑到成就,我认为它们不会发出那些事件。然而,当这些游戏在Steam等平台上重新发布时,有时会添加成就。这是否意味着Valve对核心进行了一些修改? - MarioDS
4个回答

28
成就系统实际上只是一种日志记录形式。对于这样的系统,发布/订阅是一个不错的方法。在这种情况下,玩家发布有关自己的信息,而感兴趣的软件组件(处理个人成就的)可以订阅。这使您可以使用专门的日志记录代码监视公共值,而不影响任何核心游戏逻辑。
以“玩家走了x英里”为例。我会将步行距离实现为玩家对象中的字段,因为这是一个简单的增量值,不需要随时间增加空间。奖励走10英里的玩家的成就将是该字段的订阅者。如果有很多玩家,则将这个值与一个或多个中间代理级别聚合起来是有意义的。例如,如果游戏中存在100万名玩家,则您可能会使用1000个代理聚合这些值,每个代理负责跟踪1000个单独的玩家。然后,成就将订阅这些代理,而不是直接订阅所有玩家。当然,最佳层次结构和订阅者数量是特定于实现的。
在打斗的例子中,玩家可以以完全相同的方式发布他们最后一次战斗的详细信息。监视战斗跳跃的成就将订阅此信息,并检查跳跃次数。由于不需要历史状态,因此这也不会随时间增长。同样,无需修改核心代码;您只需要能够访问一些值即可。
请注意,大多数奖励并不需要即时完成。这样可以让您在管理流量方面有些余地。在前面的例子中,您可能要等到玩家走了总共一英里,或者自上次更新以来已经过去了一天(在此之前进行内部递增),才会更新代理商发布的行驶距离。这实际上只是一种缓存形式;确切的参数将取决于您的问题。

那么如果需要历史数据呢?比如在一分钟内执行某个操作6次。那么你是否被迫记录所有事件(例如此操作或其他操作),然后定期检查? - Manual5355
2
@JosephShanak - 不需要记录所有事件。您可以记录一些子集事件,然后在它们之间运行一个窗口,并在它们变得不相关时使事件变老。在您的示例中,您永远不需要记录超过6个值。当有一个新值进来时,比较您的起始值和结束值,看看它们是否在一分钟内。如果是,则获得成就。如果不是,请删除最旧的值,调整列表,等待下一个值出现,并重复。 - ire_and_curses
@ire_and_curses 如果你需要复杂的历史数据,比如“用户已经玩了超过2周,至少回答了65%的问题,并且在过去3天内给出了100%正确的答案”,那该怎么办呢?而且这还是一个基于Web的游戏,统计数据存储在SQL数据库中。SQL查询历史数据是很昂贵的,特别是在聚合数据时。在这种情况下,你会建议采用不同的方法吗? - Valerio Santinelli
@ValerioSantinelli - 正确的设计权衡取决于你的要求。只要在开始之前知道这是你需要的成果,你就可以按照我的答案中的技术实现示例中的成果。为了更加灵活,直接查询数据是首选。有很多方法可以避免查询“费用”。选择DB布局,考虑周到的主键,智能分区,脱机查询到同步只读DB,使用辅助DB进行部分成果目标跟踪...有很多选择。像所有编程一样,魔鬼在细节中。 - ire_and_curses

4

即使您无法访问源代码(例如在游戏模拟器中),也可以执行此操作。可以编写简单的内存扫描工具来查找显示的分数,例如。一旦您有了这个,您的成就系统就像每帧轮询该内存位置并查看其当前“分数”或其他是否高于其最高分数一样容易。游戏模拟器的酷之处在于,内存位置是确定性的(没有操作系统)。


3
在普通游戏中,有两种方法可以实现这个目标。
1. 离线游戏:不需要像发布/订阅(pub/sub)那样复杂,那是完全过度的。相反,您只需使用一个大地图/字典,并记录命名为“事件”的内容。然后,每X帧或Y秒(通常是:“每次有东西死亡时,并在关卡结束时执行1次”),您会遍历成就并进行快速检查。当设计师想要记录新事件时,程序员只需添加一行代码即可轻松记录它。
注:在我看来,发布/订阅并不适合这种情况,因为设计师从来没有想过“当玩家距离= 50”时会发生什么。他们实际上想要的是“当玩家距离被观察屏幕的人感知到已经穿过第一个村庄,或者至少向右移动了4个屏幕宽度”——即比简单的计数器更加模糊和抽象。
实际上,这意味着逻辑在变化发生的点处(甚至在事件发布之前)执行,这是一种使用发布/订阅的不良方式。有一些游戏引擎可以使“逻辑在接收点处执行”(“订阅”部分)更容易,但根据我的经验,它们并不占大多数。
2. 在线游戏:与1几乎相同,只是您存储“计数器”(int会增加),通常还包括:“增量”(从一帧到另一帧发生的循环缓冲区),以及:“事件”(在游戏中发生的复杂事情,可以硬编码为单个ID加上固定大小的参数数组)。然后,通过例如SNMP公开这些内容,以便其他服务器可以以低CPU成本异步地收集它们。
即几乎与1相同,只是要注意两件事:
- 固定大小的内存使用;如果“读取”服务器离线一段时间,那么在此期间获得的成就将需要重新获得(尽管通常可以让客户支持人员手动查看主系统日志,并确定成就“可能”已经获得,并手动授予它)。 - 非常低的开销;SNMP对于此非常好,我认识的大多数团队最终都会使用它。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接