前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《玩游戏,学技术》第一讲:画饼

《玩游戏,学技术》第一讲:画饼

作者头像
labuladong
发布2023-03-02 19:20:51
3930
发布2023-03-02 19:20:51
举报

我之前写过一篇文章 我用消息队列做了个联机游戏 用 Pulsar 这款消息队列实现了一个比较简陋的炸弹人游戏

所以我决定给这个小游戏添加更多有趣的功能,并附上更详尽的文档说明,具体介绍这个游戏开发的思路,用到的技术组件以及算法。

基于这个想法,充分结合现有的技术组件,我计划编写一系列教程:

这篇文章我将会更具体地介绍这个项目,后续的几篇文章我会逐一讲解每个功能的实现思路和代码。我已经把比较完善的代码和文档放在了 GitHub 上:

https://github.com/labuladong/play-with-pulsar

下面开始正文,首先要从我小时候特别爱玩的一款游戏说起:

这个游戏叫做 Q 版泡泡堂,应该有不少读者小时候都玩过。游戏里玩家可以操控一个机器人放炸弹,炸开障碍物能够获取随机道具,玩家消灭所有其他机器人则闯关成功,如果被其他机器人消灭,则闯关失败。

这个游戏中其他机器人都是电脑控制的,说实话有些蠢,我玩 Hard 难度一个小时就通关了。所以我在想,是否能够把这类炸弹人游戏做成多人在线的游戏,让几个好朋友联机 PK 呢?

我的这一系列教程的最终产物就是一个多人联机的炸弹人游戏:

游戏功能规划

经典的炸弹人游戏,每个玩家可以移动、放炸弹,炸弹在一段时间后会爆炸,被炸弹炸到的玩家会立即死亡,但允许玩家无限复活。

除了最基本的玩法,我们还有以下需求:

1、需要「房间」的概念,在相同房间里的玩家才能一起对战,不同房间之间不能互相影响。

2、为了提升游戏的操作难度和趣味性,允许玩家推炸弹

4、地图中的障碍物是随机生成的,障碍物分为可摧毁的和不可摧毁的两种类型。考虑到可摧毁的障碍物会被玩家炸掉,我们需要给每个房间定时更新新的地图

5、要有一个房间计分板,显示房间内每个玩家的得分情况。

6、除了当前游戏房间中的分数情况,我们还需要有一个全局计分板,可以对所有玩家在不同房间的总得分进行排名。

7、假设我们会举办重要赛事,需要支持游戏「录制」,以便观看游戏回放

8、最好能够支持 AI 玩家对战,一个人也能玩的很嗨。

多人游戏的难点

我没有专门搞过多人在线游戏的开发,但是简单分析一下,我总结出来以下关键点:

1、多人在线游戏肯定需要有一个后端服务供所有玩家连接,但由于这只是个小游戏,所以希望开发尽可能简单,尽可能少写代码,避免重复造轮子。

2、最重要的,所有玩家的操作必须同步,或者说要保证各个玩家视图的「一致性」

理想情况下,一个玩家做的操作能够通过量子纠缠瞬间同步到所有其他玩家那里,这样各个玩家的视图必然是一致的。

但实际情况是,每个玩家在本地的操作需要通过网络发送到游戏的服务端,然后服务端再通过网络同步给其他玩家。那么这里面多了两次网络通信,随便发生点意外就会破坏每个玩家的视图一致性。

比如就考虑两个玩家playerAplayerB,他们分别站在(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、从一个相同的初始状态开始,执行一系列相同的事件,得到的结果总是相同。

满足了上述条件,所有玩家的本地客户端按顺序消费服务端的全局事件队列,就可以保证每个玩家的本地客户端状态一致了。

综上所述,我们的后端服务就是一个消息队列,客户端本地产生的事件也要先成功发送到消息队列,再从消息队列读取之后才会更新本地状态:

用一段伪码表示可能会更清晰:

代码语言:javascript
复制
// 一个线程负责拉取并显示事件
new Thread(() -> {
    while (true) {
        // 不断从消息队列拉取事件
        Event event = consumer.receive();
        // 然后更新本地状态,显示给玩家
        updateLocalScreen(event);
    }
});

// 一个线程负责生成并发送本地事件
new Thread(() -> {
    while (true) {
        // 本地玩家产生的事件,要发送到消息队列
        Event localEvent = listenLocalKeyboard();
        producer.send(event);
    }
});

这样,所有玩家客户端都以后端消息队列中的事件顺序(全局一致)为准,依次消费这些事件更新本地状态,从而保证了所有客户端的本地状态全局强一致的。

PS:回想一下,我们在玩 MOBA 游戏时,如果由于网络原因短暂卡顿重连,也会出现类似放快速放电影的情况。所以我猜测真实的多人在线游戏可能真的是通过类似消息队列的机制来保证玩家之间同步的。

在下篇文章,我会具体讲讲如何使用 Apache Pulsar 这样一个消息队列实现上面列举的游戏功能,大家敬请期待。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-12-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 labuladong 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 游戏功能规划
  • 多人游戏的难点
  • 如何同步玩家
相关产品与服务
消息队列 CMQ 版
消息队列 CMQ 版(TDMQ for CMQ,简称 TDMQ CMQ 版)是一款分布式高可用的消息队列服务,它能够提供可靠的,基于消息的异步通信机制,能够将分布式部署的不同应用(或同一应用的不同组件)中的信息传递,存储在可靠有效的 CMQ 队列中,防止消息丢失。TDMQ CMQ 版支持多进程同时读写,收发互不干扰,无需各应用或组件始终处于运行状态。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档