paint-brush
Singularity:使用通用后端框架简化游戏开发by@makhorin

Singularity:使用通用后端框架简化游戏开发

Andrei Makhorin13m2024/07/25
Read on Terminal Reader

就游戏设计而言,“通用框架”是什么?为什么需要它们或它们有用?它们如何帮助简化开发?我们将回答所有这些问题(以及更多问题),并展示我们的解决方案 Singularity。
featured image - Singularity:使用通用后端框架简化游戏开发
Andrei Makhorin HackerNoon profile picture


大家好!我是 Pixonic (MY.GAMES) 的服务器开发人员 Andrey Makhorin。在本文中,我将分享我和我的团队如何创建通用的后端开发解决方案。您将了解这个概念、其结果以及我们的系统(称为 Singularity)在实际项目中的表现。我还将深入探讨我们面临的挑战。

背景

当游戏工作室刚刚起步时,快速制定并实施一个有吸引力的想法至关重要:数十种假设需要测试,游戏需要不断变化;新功能不断增加,不成功的解决方案则需要修改或放弃。然而,这种快速迭代的过程,加上紧迫的期限和较短的规划期限,可能会导致技术债务的积累。


由于已有的技术债务,重用旧解决方案可能会很复杂,因为需要解决各种问题。这显然不是最佳选择。但还有另一种方法:“通用框架”。通过设计通用的可重用组件(例如布局元素、窗口和实现网络交互的库),工作室可以大大减少开发新功能所需的时间和精力。这种方法不仅减少了开发人员需要编写的代码量,还确保代码已经过测试,从而减少维护时间。


我们已经在一个游戏的背景下讨论了功能开发,但现在让我们从另一个角度来看这种情况:对于任何游戏工作室来说,在项目中重复使用小段代码可能是简化生产的有效策略,但最终他们需要制作一款新的热门游戏。从理论上讲,重复使用现有项目的解决方案可以加速这一过程,但会出现两个重大障碍。首先,同样的技术债务问题也适用于此;其次,任何旧解决方案都可能针对之前游戏的特定要求进行量身定制,因此不适合新项目。


这些问题因进一步的问题而变得更加严重:数据库设计可能无法满足新项目的要求,技术可能已经过时,新团队可能缺乏必要的专业知识。


此外,核心系统通常是针对特定类型或游戏而设计的,因此很难适应新项目。


同样,这也是通用框架发挥作用的地方,虽然创建彼此截然不同的游戏似乎是一项难以克服的挑战,但有些平台已经成功解决了这个问题:PlayFab、Photon Engine 和类似平台已经证明它们能够显著缩短开发时间,让开发人员专注于构建游戏而不是基础设施。


现在,让我们开始我们的故事。

奇点的必要性

对于多人游戏来说,强大的后端至关重要。例如:我们的旗舰游戏《战争机器人》。这是一款移动 PvP 射击游戏,已经存在 10 多年,积累了大量需要后端支持的功能。虽然我们的服务器代码是根据项目的具体情况量身定制的,但它使用的技术已经过时了。


到了开发新游戏的时候,我们意识到尝试重用 War Robots 的服务器组件会存在问题。代码过于特定于项目,需要新团队所缺乏的技术专业知识。


我们还认识到,新项目的成功并不能保证,而且即使成功了,我们最终也需要创建另一款新游戏,而且我们也会面临同样的“空白”问题。为了避免这种情况并为未来做好准备,我们决定确定游戏开发所需的基本组件,然后创建一个可用于所有未来项目的通用框架。


我们的目标是为开发人员提供一种工具,使他们无需重复设计后端架构、数据库模式、交互协议和特定技术。我们希望将人们从实施授权、支付处理和用户信息存储的负担中解放出来,让他们专注于游戏的核心方面:游戏玩法、设计、业务逻辑等。


此外,我们不仅希望通过新框架加速开发,还希望客户端程序员无需深入了解网络、DBMS 或基础设施即可编写服务器端逻辑。


除此之外,通过标准化一组服务,我们的 DevOps 团队将能够以同样的方式处理所有游戏项目,只有 IP 地址会发生变化。这将使我们能够创建可重复使用的部署脚本模板和监控仪表板。


在整个过程中,我们制定了架构决策,考虑到了在未来游戏中重复使用后端的可能性。这种方法确保了我们的框架灵活、可扩展且能够适应各种项目需求。


(还值得注意的是,该框架的开发并不是一个孤岛——它是与另一个项目并行创建的。)

创建平台

我们决定赋予 Singularity 一组与游戏类型、设置或核心玩法无关的功能,其中包括:

  • 验证
  • 用户数据存储
  • 游戏设定与平衡解析
  • 交付过程
  • AB 测试分布
  • 分析服务集成
  • 服务器管理面板


这些功能对于任何多用户移动项目来说都是至关重要的(至少,它们与在 Pixonic 中开发的项目相关)。


除了这些核心功能外,Singularity 还旨在容纳更多更贴近业务逻辑的项目特定功能。这些功能使用抽象构建,使其可在不同项目中重复使用和扩展。


一些示例包括:

  • 任务
  • 优惠
  • 好友列表
  • 匹配
  • 评级表
  • 玩家在线状态
  • 游戏内通知



从技术上讲,Singularity 平台由四个组件组成:

  • 服务器SDK:这是一组库,游戏程序员可以在此基础上开发他们的服务器。
  • 客户端 SDK:也是一组库,但用于开发移动应用程序。
  • 一组现成的微服务:这些是现成的服务器,不需要修改。其中包括身份验证服务器、余额服务器等。
  • 扩展库:这些库已经实现了各种功能,例如优惠、任务等。如果游戏需要,游戏程序员可以启用这些扩展。


接下来,让我们检查一下每个组件。


服务器 SDK

有些服务(例如个人资料服务和匹配)需要特定于游戏的业务逻辑。为了适应这种情况,我们将这些服务设计为以库的形式分发。然后,开发人员可以在这些库的基础上创建包含命令处理程序、匹配逻辑和其他特定于项目的组件的应用程序。


这种方法类似于构建 ASP.NET 应用程序,其中框架提供低级 HTTP 协议功能,同时开发人员可以专注于创建包含业务逻辑的控制器和模型。


例如,假设我们想添加在游戏内更改用户名的功能。为此,程序员需要编写一个命令类,其中包含新用户名和此命令的处理程序。


以下是 ChangeNameCommand 的一个示例:

 public class ChangeNameCommand : ICommand { public string Name { get; set; } }


该命令处理程序的一个示例:

 class ChangeNameCommandHandler : ICommandHandler<ChangeNameCommand> { private IProfile Profile { get; } public ChangeNameCommandHandler(IProfile profile) { Profile = profile; } public void Handle(ICommandContext context, ChangeNameCommand command) { Profile.Name = command.Name; } }


在此示例中,必须使用 IProfile 实现初始化处理程序,该实现通过依赖项注入自动处理。某些模型(如 IProfile、IWallet 和 IInventory)无需额外步骤即可实现。但是,由于这些模型的抽象性质,它们提供的数据和接受的参数并非针对特定项目需求量身定制,因此使用起来可能不太方便。


为了简化操作,项目可以定义自己的域模型,以类似于处理程序的方式注册它们,并根据需要将它们注入构造函数。这种方法可以在处理数据时提供更加个性化和便捷的体验。


以下是领域模型的一个示例:

 public class WRProfile { public readonly IProfile Raw; public WRProfile(IProfile profile) { Raw = profile; } public int Level { get => Raw.Attributes["level"].AsInt(); set => Raw.Attributes["level"] = value; } }


默认情况下,玩家资料不包含 Level 属性。但是,通过创建特定于项目的模型,可以添加此类属性,并且可以轻松读取或更改命令处理程序中的玩家级别信息。


使用域模型的命令处理程序的示例:

 class LevelUpCommandHandler : ICommandHandler<LevelUpCommand> { private WRProfile Profile { get; } public LevelUpCommandHandler(WRProfile profile) { Profile = profile; } public void Handle(ICommandContext context, LevelUpCommand command) { Profile.Level += 1; } }


该代码清楚地表明,特定游戏的业务逻辑与底层传输或数据存储层是隔离的。这种抽象使程序员可以专注于核心游戏机制,而不必担心事务性、竞争条件或其他常见的后端问题。


此外,Singularity 还为增强游戏逻辑提供了广泛的灵活性。玩家的个人资料是“键类型值”对的集合,游戏设计师可以根据自己的设想轻松添加任何属性。


除了个人资料之外,Singularity 中的玩家实体还由几个基本组件组成,旨在保持灵活性。值得注意的是,其中包括一个跟踪其中每种货币数量的钱包以及一个列出玩家物品的库存。


有趣的是,Singularity 中的物品是类似于个人资料的抽象实体;每个物品都有一个唯一标识符和一组键类型值对。因此,物品不一定是游戏世界中的有形物体,如武器、衣服或资源。相反,它可以代表任何专门向玩家发布的一般描述,如任务或优惠。在下一节中,我将详细介绍如何在特定的游戏项目中实现这些概念。


Singularity 的一个关键区别是,物品在资产负债表中存储了对一般描述的引用。虽然此描述保持不变,但发行的单个物品的属性可能会发生变化。例如,玩家可以更换武器皮肤。


此外,我们拥有强大的玩家数据迁移选项。在传统的后端开发中,数据库架构通常与业务逻辑紧密耦合,对实体属性的更改通常需要直接修改架构。


然而,传统方法并不适合 Singularity,因为该框架缺乏对与玩家实体相关的业务属性的认识,并且游戏开发团队缺乏对数据库的直接访问。相反,迁移被设计和注册为命令处理程序,无需直接存储库交互即可运行。当玩家连接到服务器时,他们的数据将从数据库中获取。如果服务器上注册的任何迁移尚未应用于此玩家,则会执行它们,并将更新的状态保存回数据库。


已应用迁移的列表也存储为玩家属性,这种方法还有另一个显著的优势:它允许迁移随时间错开。这使我们能够避免大量数据更改可能导致的停机和性能问题,例如向所有玩家记录添加新属性并将其设置为默认值时。

客户端 SDK

Singularity 提供了一个简单的后端交互界面,让项目团队可以专注于游戏开发,而不必担心协议或网络通信技术问题。(话虽如此,SDK 确实提供了灵活性,可以在必要时覆盖项目特定命令的默认序列化方法。)


SDK 支持与 API 直接交互,但它还包含一个可自动执行常规任务的包装器。例如,在个人资料服务上执行命令会生成一组事件,这些事件指示玩家个人资料的变化。包装器会将这些事件应用于本地状态,确保客户端维护个人资料的当前版本。


以下是命令调用的示例:

 var result = _sandbox.ExecSync(new LevelUpCommand())


现成的微服务

Singularity 中的大多数服务都设计为多功能的,不需要针对特定项目进行定制。这些服务以预构建应用程序的形式分发,可用于各种游戏。


这套现成的服务包括:

  • 客户端请求的网关
  • 身份验证服务
  • 用于解析和存储设置和平衡表的服务
  • 在线状态服务
  • 好友服务
  • 排行榜服务


有些服务是平台的基础,必须部署,例如身份验证服务和网关。其他服务是可选的,例如好友服务和排行榜,可以从不需要它们的游戏环境中排除。

稍后我将讨论与管理大量服务相关的问题,但现在必须强调的是,可选服务应该保持可选性。随着服务数量的增加,新项目的复杂性和入职门槛也会增加。


扩展库

尽管 Singularity 的核心框架功能强大,但项目团队可以独立实现重要功能,而无需修改核心。当发现功能可能对多个项目有益时,框架团队可以开发该功能并将其作为单独的扩展库发布。然后可以集成这些库并将其用于游戏中的命令处理程序。


这里可能适用的一些示例功能是任务和优惠。从核心框架的角度来看,这些实体只是分配给玩家的物品。但是,扩展库可以为这些物品赋予特定的属性和行为,将它们转化为任务或优惠。此功能允许动态修改物品属性,从而实现对任务进度的跟踪或记录向玩家提供优惠的最后日期。


目前结果

Singularity 已成功应用于我们最新的全球发行游戏之一 Little Big Robots,这让客户端开发人员能够自行处理服务器逻辑。此外,我们能够创建利用现有功能的原型,而无需平台团队的直接支持。


然而,这种通用解决方案并非没有挑战。随着功能数量的增加,与平台交互的复杂性也随之增加。Singularity 已从一个简单的工具演变为一个复杂而复杂的系统——在某些方面类似于从基本的按键式手机到功能齐全的智能手机的转变。


虽然 Singularity 减轻了开发人员深入研究数据库和网络通信复杂性的需要,但它也带来了自己的学习曲线。开发人员现在需要了解 Singularity 本身的细微差别,这可能是一个重大转变。


从开发人员到基础设施管理员,各种人员都面临着挑战。这些专业人员通常在部署和维护 Postgres 和 Kafka 等知名解决方案方面拥有深厚的专业知识。然而,Singularity 是一款内部产品,因此他们必须掌握新技能:他们需要了解 Singularity 集群的复杂性,区分必需服务和可选服务,并了解哪些指标对于监控至关重要。


虽然在公司内部,开发人员可以随时联系平台的创建者寻求一些建议,但这个过程不可避免地需要时间。我们的目标是尽可能降低进入门槛。要实现这一点,就需要为每个新功能提供全面的文档,这可能会减慢开发速度,但仍被视为对平台长期成功的投资。此外,强大的单元和集成测试覆盖率对于确保系统可靠性至关重要。


Singularity 严重依赖自动化测试,因为手动测试需要开发单独的游戏实例,这是不切实际的。自动化测试可以捕获绝大多数(即 99%)的错误。但是,总有一小部分问题只有在特定的游戏测试期间才会显现出来。这可能会影响发布时间表,因为 Singularity 团队和项目团队通常异步工作。在很久以前编写的代码中可能会发现阻塞错误,而平台开发团队可能正忙于另一项关键任务。(这一挑战并非 Singularity 独有,也可能发生在自定义后端开发中。)


另一个重大挑战是管理所有使用 Singularity 的项目的更新。通常,有一个旗舰项目会通过源源不断的功能请求和增强功能来推动框架的开发。与该项目团队的互动非常紧密;我们了解他们的需求以及他们如何利用我们的平台来解决他们的问题。


虽然一些旗舰项目与框架团队密切合作,但处于早期开发阶段的其他游戏通常独立运作,仅依赖现有的功能和文档。这有时会导致冗余或次优的解决方案,因为开发人员可能会误解文档或误用可用功能。为了缓解这种情况,通过演示、聚会和团队交流促进知识共享至关重要,尽管此类举措确实需要投入大量时间。

未来

Singularity 已在我们的游戏中展现了其价值,并有望进一步发展。虽然我们确实计划推出新功能,但目前我们的主要重点是确保这些增强功能不会影响项目团队使用该平台的难度。

除此之外,还需要降低进入门槛,简化部署,增加分析方面的灵活性,让项目能够连接他们的解决方案。这对团队来说是一个挑战,但我们相信并在实践中看到,我们为解决方案所付出的努力一定会得到充分的回报!