衡量游戏开发中的成功

导航至

我仍然在进行我的个人使命,探索和理解指标作为技术基础组成部分的价值。我们已经进入了仪表化时代,我感觉自己被落下了。我见过指标被用于监控复杂系统、分析行为(包括软件、硬件和人类的行为)以及跟踪机器学习算法的变化。传感器数据可以告诉我空气中一氧化碳是否过多,或者我的在线商店是否在黑色星期五崩溃。指标可以帮助保障工厂工人的安全,并跟踪我的杂货配送。这些都非常令人印象深刻,当其他人向我解释他们的指标时,我很容易看到其价值。

我决定,探索指标在我自己工作中价值的最简单方法是为一个对我自己有价值的应用程序进行仪表化。“Paper Pirates”应运而生:这是一款基于浏览器的 Node.js 游戏,我构建它的目的只有一个,就是为了好玩(在这里找到 repo)。“Paper Pirates”没有输赢——唯一的制胜之道是不玩。

我选择网页游戏有几个原因:1) 我喜欢游戏。它们比现实好太多了。2) 游戏开发真的很难。即使像这样简单的游戏,也有很多机会出现故障和无法解释的行为。

游戏是指标的金矿,我可以收集大量的统计数据来确保我的应用程序按预期运行。这是 DevOps 的核心,但我想探索一个稍微不同的角度。在本文中,我将重点关注指标如何帮助我调整游戏性能和改进游戏玩法。让我们开始吧!

从 “Paper Pirates” 收集指标很容易;我使用了 InfluxDB Node.js 客户端。本质上,我监听某些事件,当这些事件发生时,我向 InfluxDB 发送值 1。在这种情况下,我不需要特定的值——我只需要知道事件是否发生以及何时发生。

与 InfluxDB 的所有交互都位于 index.js 中。查看代码,您会看到我正在收集指标,以帮助构建排行榜、衡量导弹精度和整体玩家详细信息。目前,我们将专注于这个代码块

socket.on("enemyfire", results => {
    client.writeMeasurement("events", [
      {
        tags: { sessionID, gameID, enemyID: results.enemyID, event: "enemyMissile" },
        fields: {
          enemyFires: 1,
        }
      }
    ])
  })
});

游戏最重要的部分之一是玩家与世界的互动,说实话,这是我最后才想到的事情之一——直到我第一次玩这个游戏。这就是敌舰开火的方式

export const enemiesFire = () => (dispatch, getState) => {
  const { enemies } = getState();

  _.each(enemies, enemy => {
    const roll = _.random(4000);

    if (roll <= 10) {
      Client.emit("enemyfire", { enemyID: enemy.id })
      dispatch({ type: "ENEMYFIRE", payload: enemy });
    }
  });
};

这里有很多事情在发生。本质上,我们处理的是敌人开火的概率。在 0 到 4000 之间选择一个随机数,只有当它小于 10 时,敌人才会发射导弹。当我第一次编写这段代码时,我不知道哪些数字会起作用,所以我猜了一个。我们将第一次实验的结果称为“虚空”。

<figcaption> 独自面对错误的地方</figcaption>

敌人开火太频繁了,仅仅生成导弹就导致画布上的其他所有东西都卡顿。然后玩家立即死亡,因为大量的导弹撞击了他们。

这让我多次更改 rand 函数中的数字,直到找到一个不会导致浏览器崩溃的数字。然而,游戏开发最困难的部分之一是弄清楚什么感觉是对的。诸如控件的响应速度、精灵的反应速度,甚至碰撞检测的精确度等因素都会影响玩家是否享受这种体验。因此,对我来说,找到合适的敌人开火频率非常重要,即使这很困难。

指标登场!回顾第一个代码片段,我将每个敌方导弹标记为一个事件,该事件会递增发送到我的 InfluxDB 实例的计数。这使我能够跟踪与我在代码中选择的随机数相关的有意义的聚合。

通过这种方式,指标推动了 “Paper Pirates” 游戏玩法的改进,并使我成为一个更明智的开发者。我第一次编写随机开火时,它看起来像这样

_.each(enemies, enemy => {
    const roll = _.random(1000);

    if (roll <= 10) {
      Client.emit("enemyfire", { enemyID: enemy.id })
      dispatch({ type: "ENEMYFIRE", payload: enemy });
    }

通过跟踪事件,我确定它破坏整个游戏的原因是它在 10 秒内发射了大约 3,500 次。“Paper Pirates” 无法处理这种负载。当我玩游戏并跟踪开火情况时,我确定每秒一次敌方开火对我来说感觉最好,并使玩家有可能生存超过一分钟。

<figcaption> 海上最精良的船只</figcaption>

我需要聚合的另一个原因是屏幕上有多个敌人。游戏目前限制为一次最多四个敌人,但它们都以稍微不同的速率开火,最简单地说,这意味着我不知道会发生什么。收集敌方开火事件的数量减轻了我个人试图弄清楚这一点的精神负担。它还允许我对它执行各种聚合,尽管我主要关心的是平均值。

理解指标的价值对于每个人来说都不尽相同。是的,我想知道 “Paper Pirates” 是否启动并运行,指标和事件可以对此有所帮助。但我也想知道,当它启动并运行时,它也是最好的状态。

能够微调游戏玩法是创建人们真正想玩的游戏的一部分,而随着我的代码更改,衡量游戏玩法的随时间变化是我成为一名更好的游戏开发者的一种方式。