我之前写过一篇文章 我用音讯队列做了个联机游戏 用 Pulsar 这款音讯队列实现了一个比拟简陋的炸弹人游戏,后果不少读者对这个小游戏都很感兴趣,甚至在 Pulsar 的技术交换群里都遇到了公众号的读者。
所以我决定给这个小游戏开发更多功能,并附上更详尽的文档阐明,具体介绍一下这个游戏开发的思路,用到的技术组件以及算法。
没看过之前那篇文章也没关系,这篇文章我将会更具体地介绍这个我的项目,后续的几篇文章我会逐个介绍每个性能的实现思路。
我曾经把比较完善的代码和文档放在了 GitHub 上:
https://github.com/labuladong…
上面开始注释,首先要从我小时候特地爱玩的一款游戏说起:
这个游戏叫做 Q 版泡泡堂,应该有不少读者小时候都玩过。游戏里玩家能够操控一个机器人放炸弹,炸开障碍物可能获取随机道具,玩家毁灭所有其余机器人则闯关胜利,如果被其余机器人毁灭,则闯关失败。
这个游戏中其余机器人都是电脑管制的,说实话有些蠢,我玩 Hard 难度一个小时就通关了。所以我在想,是否可能把这类炸弹人游戏做成多人在线的游戏,让几个好敌人联机 PK 呢?
基于这个想法,充沛联合现有的技术组件,我打算编写一系列教程:
这个教程的最终产物就是一个多人联机的炸弹人游戏:
游戏性能布局
经典的炸弹人游戏,每个玩家能够挪动、放炸弹,炸弹在一段时间后会爆炸,被炸弹炸到的玩家会立刻死亡,但容许玩家有限复活。
除了最根本的玩法,咱们还有以下需要:
1、须要「房间」的概念,在雷同房间里的玩家能力一起对战,不同房间之间不能相互影响。
2、为了晋升游戏的操作难度和趣味性,容许玩家 推炸弹。
4、地图中的障碍物是随机生成的,障碍物分为可捣毁的和不可捣毁的两种类型。思考到可捣毁的障碍物会被玩家炸掉,咱们须要给每个房间 定时更新新的地图。
5、要有一个 房间计分板,显示房间内每个玩家的得分状况。
6、除了以后游戏房间中的分数状况,咱们还须要有一个 全局计分板,能够对所有玩家在不同房间的总得分进行排名。
7、假如咱们会举办重要赛事,须要反对游戏「录制」,以便 观看游戏回放。
8、最好可能 反对 AI 玩家对战,一个人也能玩的很嗨。
多人游戏的难点
我没有专门搞过多人在线游戏的开发,然而简略剖析一下,我总结进去以下关键点:
1、多人在线游戏必定须要有一个后端服务供所有玩家连贯,但因为这只是个小游戏,所以心愿开发尽可能简略,尽可能少写代码,防止反复造轮子。
2、最重要的,所有玩家的操作必须同步,或者说要保障各个玩家视图的「一致性」。
现实状况下,一个玩家做的操作可能通过量子纠缠霎时同步到所有其余玩家那里,这样各个玩家的视图必然是统一的。
但理论状况是,每个玩家在本地的操作须要通过网络发送到游戏的服务端,而后服务端再通过网络同步给其余玩家。那么这外面多了两次网络通信,轻易产生点意外就会毁坏每个玩家的视图一致性。
比方就思考两个玩家 playerA
和 playerB
,他们别离站在 (2, 3)
和 (6, 4)
,此时每个玩家的本地状态和服务端的状态都是统一的,这很好:
此时 playerA
进行一次挪动,先更新本地状态,而后通知服务端,服务端再告诉到 playerB
。然而服务端和 playerB
通信时呈现了网络抖动导致通信失败,那么就造成了 playerB
的本地状态谬误:
playerB
看到 playerA
依然站在 (2, 3)
,而实际上 playerA
曾经站在了 (3, 3)
。
此时如果 playerB
攻打 (2, 3)
,他看见本人攻打了 playerA
,但实际上 playerA
并没有受到攻打,这显然是一个十分重大的 bug。
你兴许说,如果服务端和 playerB
通信失败,那就重试呗?
实际上也不好搞,因为要保障重试期间 playerB
不能有任何动作,否则 playerB
的本地状态原本就是错的,基于这个谬误状态上的所有动作都会让问题更重大。
如何同步玩家
解法其实很简略,咱们的后端用一个音讯队列就能够解决玩家间同步的问题:
1、把所有玩家的操作形象成一个事件。
2、在服务端有有一个全局统一的事件序列(音讯队列)。
3、从一个雷同的初始状态开始,执行一系列雷同的事件,失去的后果总是雷同。
满足了上述条件,所有玩家的本地客户端按程序生产服务端的全局事件队列,就能够保障每个玩家的本地客户端状态统一了。
综上所述,咱们的后端服务就是一个音讯队列,客户端本地产生的事件也要先胜利发送到音讯队列,再从音讯队列读取之后才会更新本地状态:
用一段伪码示意可能会更清晰:
// 一个线程负责拉取并显示事件
new Thread(() -> {while (true) {
// 一直从音讯队列拉取事件
Event event = consumer.receive();
// 而后更新本地状态,显示给玩家
updateLocalScreen(event);
}
});
// 一个线程负责生成并发送本地事件
new Thread(() -> {while (true) {
// 本地玩家产生的事件,要发送到音讯队列
Event localEvent = listenLocalKeyboard();
producer.send(event);
}
});
这样,所有玩家客户端都当前端音讯队列中的事件程序(全局统一)为准,顺次生产这些事件更新本地状态,从而保障了所有客户端的本地状态全局强统一的。
PS:回忆一下,咱们在玩 MOBA 游戏时,如果因为网络起因短暂卡顿重连,也会呈现相似放疾速放电影的状况。所以我猜想实在的多人在线游戏可能真的是通过相似音讯队列的机制来保障玩家之间同步的。
在下篇文章,我会具体讲讲如何应用 Apache Pulsar 这样一个音讯队列实现下面列举的游戏性能。
更多高质量干货文章,请关注我的微信公众号 labuladong 和算法博客 labuladong 的算法秘籍。