可扩展性游戏机制建模 #

在构建游戏时,很容易直接根据玩家的经验对服务器代码进行建模,特别是对于基于进度的机制。但其中潜伏着代价高昂的问题。

考虑一个假设的角色扮演游戏,对玩家获得经验分并在一周内每天完成任务给予奖励。您可能会构建两个系统:一个系统在玩家获得足够的经验分以获得奖励时升级玩家,另一个系统(或使用 cron 作业)在每天午夜检查玩家的每日任务进度。

虽然这两个系统很容易解释,但随着时间的推移,您的游戏可能会有更多的系统。这就是这种方法的弊端:它爆炸性地增加成本,滋生复杂性,并隐藏脆弱性。

它的成本很高,因为处理量随每个新玩家的加入而线性增加。 夜间 cron 作业将耗费越来越长的时间来完成,或者需要额外、昂贵的计算能力来快速运行,更不用说夜间负载阻塞服务器。

它很复杂,因为您必须为每个新的机制、计时器或事件构建一个新的系统。 您的等级晋升系统不能方便地利用每日任务系统中的代码,反之亦然。

它很脆弱,因为您可以将服务器代码与时钟联系起来。 如果没有在午夜完成每日进度检查,游戏可能会中断,或者需要在深夜进行救援。更糟糕的是,一些周期性的系统可能会变得完全不稳定,例如玩家资源的不断流失或积累。cron 作业不能在每一秒都对每个玩家正常运行。

那么游戏开发者该怎么办?

摆脱天真做法的一种方法:以玩家为中心的处理 #

好消息是,有一种方法可以借助通用的方式对服务器代码进行建模,这种方法适用于许多游戏系统:对玩家事件(如登录、注销、结束比赛等)作出反应。

与其定期检查给定玩家、团队或物品可能经历的更改,不如等待并响应真正发生的事件。这有点类似于事件驱动的编程范例:服务器的作用类似于主循环,会触发回调功能。

此方法可降低服务器成本,最大限度降低复杂性并提高游戏的稳健性,因为:

  • 进度代码只有在直接影响一个玩家时才在服务器上运行。
  • 服务器负载的时间分布更加均匀。
  • 事件处理在更接近玩家活动的时间发生,而不是等待预定的 cron 作业。
  • 在游戏机制之间可以更方便地重新利用进度代码。

让我们看一下这个方法的概要,然后看一个真实的例子。

实现模式:进度模板 #

实现此模型的一种模式的工作分为三个部分:

  1. 定义静态进度:编写一个模板,定义系统进度的里程碑、奖励和默认值。
  2. 进度开始:进度开始时,将模板复制到玩家的数据。
  3. 玩家操作更新:每次相关事件发生时,更新玩家的进度副本。

通过模板和事件处理实现每日连局 #

让我们看一个例子:在我们假设的游戏中,我们想奖励连续几天登录游戏的玩家。如果玩家每 24 小时至少登录一次,重复 7 次,他们将获得完成奖励,此外第三天还获得小奖励。

为了进行设置,我们将创建一个 JSON 格式的静态进度模板(JSON 只是这里的一个选项,您可以使用不同的格式或数据结构,但这个概念仍然适用):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "title":"7-day check-in challenge",
  "progress":0,
  "next_login_after":"1602092170",
  "rewards": [
    {}, {},
    { "gold":300 },
    {}, {}, {},
    {
      "gold":1000,
      "items":[
        "item374"
      ]
    }
  ]
}

每当玩家登录时,我们都会为该玩家运行一些代码,以确定他们是否有活动的连局。如果他们没有活动的连局(或他们现有的连局已被打破),我们将把模板复制到该玩家的数据中:

function onLoginEvent(player)
  if (player.streak == undefined or streakIsExpired(player.streak))
    player.streak = copyNewStreakFromTemplate()

如果玩家已有活动的连局,并且他们在合适的时间登录,我们可以解锁其连局,让他们前进:

if (time.now() > next_login_after and time.now() < next_login_after + ONE_DAY)
    player.streak.progress += 1
    awardUnlock(player, player.streak.rewards[player.streak.progress])
    setTimeForNextUnlock(player.streak)

如果我们到达连局末尾,我们需要一些额外的逻辑来重新开始(更复杂的模板可能支持倍数并对连局进行迭代)。

其他事件也有可能触发对连局的操作。例如,玩家可以选择取消连局(或许为了开始其他进度)。这将有其专用的事件处理程序。

Nakama 中的事件挂钩 #

在 Nakama 中,我们可以用 after 挂钩实现此模式。您可以注册一个在服务器接收到每条消息后执行的函数。在此函数中,您可以调度更具体的事件处理程序来晋级玩家、奖励奖杯,或者就像在本例中一样,更新连局。

在这种情况下,您可以编写一个 after 挂钩,每当客户端调用 getAccount() 功能(发送有关该连局的带外通知),它就会更新玩家的连局进程。

当调用 getAccount 函数时,after 挂钩更新玩家的连局进度
当调用 getAccount 函数时,after 挂钩更新玩家的连局进度
在本例中,我们在一个 Nakama 已经提供且您可能会使用的 API 上获得一个事件触发器:getAccount()。在客户端,不需要额外的代码来触发连局更新。

一种模式,多种应用 #

此方法适用于许多进度式游戏机制,包括:

  • 玩家、NPC 和物品晋级
  • 资源消耗与续费,例如物品降级和充能
  • 挑战、请求和成就
  • 留人激励,例如连局和每日奖励

别碰无休无止的 cron 作业带来的麻烦,以事件为中心考虑对游戏机制建模。

进一步了解 #

学习注册 Nakama 挂钩

Related Pages