简介
Superstellar 是一款开源的多人 Web 太空游戏,非常适合入门 Golang 游戏服务器开发。
规则很简单:摧毁移动的物体,不要被其他玩家和小行星杀死。你拥有两种资源 — 生命值(health points)和能量值(energy points)。每次撞击和与小行星的接触都会让你失去生命值。在射击和使用提升驱动时会消耗能量值。你杀死的对象越多,你的生命值条就会越长。
技术栈
游戏分为两个部分:一个中央服务器(central server)和一个在每个客户端的浏览器中运行的前端应用程序(a front end app)。
我们之所以选择这个项目,主要是因为后端部分。 我们希望它是一个可以同时发生许多事情的地方:游戏模拟(game simulation),客户端网络通信(client network communication),统计信息(statistics),监视(monitoring)等等。 所有这些都应该并行高效地运行。因此,Go 以并发为导向的方法和轻量级的方式似乎是完成此工作的理想工具。
前端部分虽然很重要,但并不是我们的主要关注点。然而,我们也发现了一些潜在的有趣问题,如如何利用显卡渲染动画或如何做客户端预测,以使游戏运行平稳和良好。最后我们决定尝试包含:JavaScript, webpack 和 PixieJS 的堆栈。
在本文的其余部分中,我将讨论后端部分,而客户端应用程序将留待以后讨论。
游戏状态主控模拟 - 在一个地方,而且只有一个地方
Superstellar 是一款多人游戏,所以我们需要一个逻辑来决定游戏世界的当前状态及其变化。它应该了解所有客户端的动作,并对发生的事件做出最终决定 — 例如,炮弹是否击中目标或两个物体碰撞的结果是什么。我们不能让客户端这样做,因为可能会发生两个人对是否有人被枪杀的判断不同。更不用说那些想要破解协议并获得非法优势的恶意玩家了。因此,存储游戏状态并决定其变化的最佳位置是服务器本身。
下面是服务器工作方式的总体概述。它同时运行三种不同类型的动作:
- 侦听来自客户端的控制输入
- 运行仿真模拟(simulation)以将状态更新到下一个时间点
- 向客户端发送当前状态更新
下图显示了飞船的状态和用户输入结构的简化版本。 用户可以随时发送消息,因此可以修改用户输入结构。仿真步骤每 20 毫秒唤醒一次,并执行两个操作。 首先,它需要用户输入并更新状态(例如,如果用户启用了推力,则增加加速度)。 然后,它获取状态(在 t 时刻)并将其转换为时间的下一个时刻(t + 1)。 整个过程重复进行。
在 Go 中实现这种并行逻辑非常容易 — 多亏了它的并发特性。每个逻辑都在其自己的 goroutine 中运行,并侦听某些通道(channel),以便从客户端获取数据或同步到 tickers,以定义模拟步骤(simulations steps)的速度或将更新发送回客户端。我们也不必担心并行性 - Go 会自动利用所有可用的 CPU 内核。goroutine 和通道(channels)的概念很简单,但是功能强大。
与客户端通信
服务器通过 websockets 与客户端通信。由于有了 Gorilla web toolkit,在 Golang 使用 websockets 既简单又可靠。还有一个原生的 websocket 库,但是它的官方文档说它目前缺少一些特性,因此推荐使用 Gorilla。
为了让 websocket 运行,我们必须编写一个 handler 函数来获取初始的客户端请求,建立 websocket 连接并创建一个 client 结构体:
superstellar_websocket_handler.go
handler := func(w http.ResponseWriter, r *http.Request) {
conn, err := s.upgrader.Upgrade