Node.js多人游戏的体系结构

该系统的主要组件从上到下分别是:

  • ServerController 。 处理来自客户端的新连接,执行身份验证,在必要时创建和销毁游戏和玩家控制器。 它还负责将数据持久保存到数据库,日志记录和其他维护任务。
  • GameController 。 根据游戏的类型,每个游戏世界,游戏室或其他虚拟场所可以有一个对象,多个用户可以一起玩。 它负责根据玩家的动作更改游戏状态,并负责将这些更改通知玩家。
  • 游戏 。 表示客户端和服务器上游戏的当前状态。 我会在一段时间内写更多有关它的内容。
  • PlayerController 。 每个连接到服务器的客户端都有一个对象。 它处理来自客户端的传入消息,并代表玩家在游戏中执行操作。 它还定期向客户端发送包含游戏状态更新的消息。
  • ClientController 。 每个客户端只有一个对象。 它处理来自服务器的传入消息并更新游戏状态。 它还处理用户输入,并将有关用户操作的消息发送到服务器。
  • GameView 。 在canvas元素上渲染游戏,并将输入事件从浏览器传递到客户端控制器。

如您所见,客户端部分的体系结构与MVC相似。 主要区别在于,控制器基于来自服务器的输入而非用户输入来更新模型(即游戏状态)。 但是,实际上,我们希望用户操作立即可见,而不必等待客户端和服务器之间的往返。

解决方案是将游戏模型分成单独的逻辑部分:

  • 不变的数据 。 这是在游戏过程中永远不变的数据。 在RPG游戏中,这可能是地形,在进行益智游戏时,则可能是单个棋子的形状。 这些数据只需要在游戏开始时发送给客户端一次,客户端就可以认为它总是准确的。
  • 持久状态 。 这是在游戏中可以更改的数据,例如角色的位置或拼图的位置。 客户端具有存储在服务器上的游戏状态快照,该快照可能不再准确。 出于这个原因,客户端永远不要仅响应服务器消息直接修改此状态。
  • 临时状态 。 这是持久状态与用户看到的状态之间的区别。 仅在客户端需要。 例如,当玩家的角色移动时,临时位置会立即更改,因此移动动画很流畅,但是持久位置仅在服务器确认此更改后才更新。 服务器可以拒绝更改,例如,与此同时其他对象挡住了路。

服务器的体系结构也有点类似于MVC。 每个游戏世界都有一个模型。 该模型由游戏控制器响应于玩家动作和其他游戏事件而更新。 尽管服务器上没有传统视图,但玩家控制器的操作方式类似于视图-当游戏状态更改时,它们会接收更新,但它们不会将屏幕更新,而是将这些更新中继给客户端。 它们还将用户输入从客户端中继到游戏控制器。

示例数据流

所有这些听起来可能非常复杂,所以让我们分析一个简单的场景,看看幕后到底发生了什么。 想像一个简单的游戏,当您可以使用键盘移动角色时。 让我们看看当用户按下向右箭头键时会发生什么。

这些都是按时间顺序排列的步骤:

  1. 游戏视图对浏览器发送的按键按下事件做出反应,并将“右移”事件发送到客户端控制器。
  2. 客户端控制器根据游戏状态的当前快照检查角色是否可以移动。 例如,如果有一堵墙挡住了路,则可以丢弃该事件。 如果可以采取措施,则客户端控制器将更新角色的临时位置并重新绘制游戏视图。 它还使用Web套接字向服务器发送“向右移动”事件。
  3. 服务器上的播放器控制器通过Web套接字接收“向右移动”事件。 它将事件与要移动角色的玩家有关的信息传递给游戏控制器。
  4. 游戏控制器检查给定玩家的角色是否可以向右移动。 根据该信息,它可能会接受或拒绝该操作。 如果该动作是可能的,则游戏控制器更新游戏的持久状态并将“状态改变”事件发送给与该游戏相关联的所有玩家控制器。
  5. 播放器控制器序列化已更改的数据,并使用其相应的Web套接字将其发送到客户端。
  6. 当前连接到游戏的客户端控制器接收带有新数据的“状态更改”事件。 他们更新游戏的持久状态,并在必要时重新绘制视图。

管理复杂性

像任何实时分布式系统一样,多人游戏也不容易实现。 设计游戏的体系结构时,一开始就必须考虑游戏的多人游戏性质。 您将面临许多挑战,例如,很难捕捉到各种比赛条件下可能引起的所有细微错误。 因此,首先要非常简单,并确保它在所有可能的情况下都能正常工作,包括网络滞后和暂时性的连接中断。

幸运的是,Node.js提供了一些使我们的生活更轻松的工具。 例如,使用EventEmitter可以更轻松地解耦系统组件。 游戏视图和游戏控制器都是事件发射器; 它们的事件分别由客户端控制器和播放器控制器消耗。 客户端和播放器控制器还使用通过其Web套接字传递的事件进行通信。

通过将JavaScript用于客户端代码和服务器端代码,可以在客户端和服务器之间共享很多代码。 例如,客户端和服务器端用于检测冲突的逻辑是相同的,因此不必重复。

我通常以以下方式组织JavaScript代码:

  lib / 
客户/
服务器/
共享/
client.js
server.js

客户端和服务器之间共享的所有代码分别进入lib/shared/ ,客户端或服务器特定的代码分别进入lib/client//lib/server/client.js是客户端捆绑软件的入口点, server.js是服务器上运行的Node.js服务的入口点。