百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

看完这三篇教程,估计你可以写一个多人在线游戏出来了

haoteby 2025-02-20 15:39 9 浏览

在这个系列教程中,我们以多人贪吃蛇游戏为例,开发了一个异步 Python 应用。第一篇介绍如何实现异步,第二篇文章主要讲了如何编写游戏循环。前两篇传送门如下:

跟着一起写一个多人在线游戏(一)

跟着一起写一个多人在线游戏(二)

本文作者为 Kyrylo Subbotin,是一家 IT 咨询公司的 Python 工程师。译者:oo7ww,校对:EarlGrey@编程派。

译者简介:oo7ww,北京邮电大学大三学生,计算机科学与技术专业,方向 Python Web。

4.制作一个完整的游戏

4.1 项目总览

在这部分,我们将复习一个完整的在线游戏的设计。

这是一个可增加玩家的经典贪吃蛇游戏。你可以试玩(http://snakepit-game.com)。源代码托管于github。游戏包含以下文件:

  • server.py - 一个处理游戏主循环与连接的服务器。

  • game.py - 一个主要的 Game 类,它实现了游戏的逻辑和大部分网络协议。

  • player.py - Player 类,它包含了个人玩家的数据和蛇的表示。这个类负责获取玩家的输入,并对蛇做对应的移动。

  • datatypes.py - 基本的数据结构。

  • settings.py - 游戏设置,有注释做具体描述。

  • index.html - 所有的 html 和 javascript 客户端部分都在这个文件里。

4.2 游戏循环内部

由于简单,多玩家贪吃蛇游戏是一个学习的好例子。每一帧,所有的蛇移动一个位置,而帧以很慢的速率改变,这使你能够观察游戏引擎是如何工作的。由于游戏速度慢,对玩家键盘输入没有即时响应。每个被按下的键会被记录下来,然后,在游戏循环迭代的末尾计算下一帧时,按键将被纳入计算。

现代动作游戏以相对更高的帧率运行,而且服务器和客户端的帧率并不相等。客户端帧率通常取决于客户端的硬件性能,而服务器帧率是固定的。一个客户端可能在获取对应于一个“游戏 tick ”的数据后呈现几帧。这允许创作仅受限于客户端性能的平滑动画。

在这种情况下,服务器应该不仅传递那些目标的当前位置,也传递它们的运动方向、速度和加速度。客户端帧率用 FPS (帧数每秒)表示,而服务器帧率则用 TPS (tick 数每秒)表示。

在这个贪吃蛇游戏例子中,这两个值是相等的,而且呈现在客户端的一帧是在服务器的一个 tick 事件内计算出的。

我们将使用类似文本格式的游戏区域。实际上,这是个包含许多单字符单元格的 html 表格。游戏中的所有对象都是由置于单元格中的不同颜色的字符呈现的。大部分时候,用户端传递按键的编码到服务器,并获取对应每个”tick”的游戏区域的更新。从服务器获取的一份更新包含表示生成字符及字符坐标和颜色的信息。所以我们把所有的游戏逻辑保存在服务器,而只向用户端发送生成数据。此外,我们降低了通过替代由网络发送的信息来入侵游戏的可能性。

4.3 它如何工作?

这个游戏的服务器和例 3.2 的简单例子相似。但我们并没有使用一个全局的 websockets 列表,而是用了一个服务器范围的 Game 对象。一个 Game 实例包含了一个 Player 对象列表(在 self._players 属性中),表示加入游戏的玩家以及他们的私有数据和 websocket 对象。所有游戏相关数据置于一个Game 对象中也允许我们有多个游戏房间。在这种情况下,我们需要维护多个 Game 对象,因为每个游戏开始就需要一个。

服务器和客户端之间的所有交互是通过以 json 格式编码的消息完成的。从客户端发出的消息只包含一个数字,是玩家按键的代码。其它从客户端消息都按以下格式发送:

[command, arg1, arg2, ... argN]

服务器的消息以列表的形式发送,因为通常许多消息需要立刻发送(大部分是渲染数据):

[[command, arg1, arg2, ... argN], ... ]

在每个游戏循环迭代末尾,计算下一帧并发送给所有的客户端。当然,我们不是每次都发送完整的帧,只是发送针对下一帧变化的列表。

需要注意的是,玩家连接到服务器后,不会立即加入游戏。连接后开始“观众”模式,这样可以看别人如何玩。如果游戏已经开始,或者之前游戏出现“游戏结束”画面。玩家才可以按“加入”按钮,加入现有的游戏。或者如果游戏当前没有运行,则可以创建一个新的游戏(没有其他活动的玩家)。在后一种情况下,游戏区域在开始之前被清除。

游戏区域保存在 Game._world 属性中,这是一个二维数组的嵌套列表。它是用来保存游戏区域的内部状态。数组中的每个元素代表一个区域的单元格,而后单元格才被呈现为 html 表格单元。

它有一个 Char 类型,这是一个包含单个字符和颜色的 nametuple。保持游戏区域与所有连接的客户端同步至关重要,所以所有游戏区域的更新应该连同相应的消息发送给客户。这由Game.apply_render() 方法实现。它接收一个 Draw 对象列表,然后使用它在内部更新游戏区域和发送render 信息给客户。

我们使用 namedtuple ,不仅因为它能很好地表示简单的数据结构,而且因为与 dict 相比,它在发送 json 格式的消息时所需的空间更少。

如果你在真实游戏应用中发送复杂数据结构,建议将它们序列化到一个普通甚至更短的格式,或打包为一个二进制格式(如 bson,而不是 json),从而减少网络流量。

Player 对象包含一个表示蛇的 deque 对象。此数据类型类似于一个列表,但可以更有效地添加和删除它上面的元素,所以能够理想地表示一条移动的蛇。该类的方法主要是 Player.render_move() ,它返回渲染数据从而使玩家的蛇移动到下一位置。

基本上,它在新的位置渲染出蛇头,删除尾巴所在的最后一个元素。考虑到蛇吃了一个数字就会增长,尾巴不会移动相应数量的帧。蛇的渲染数据可以用在 Game.next_frame() 方法中,该方法实现了所有的游戏逻辑。它将渲染所有蛇的移动,检查每条蛇前面的障碍,同时产生数字和“石块”。每个 tick 期间,游戏会从 game_loop() 直接调用该方法,以生成下一帧。

如果在蛇头前面有一个障碍,会在 Game.next_frame() 中调用 Game.game_over() 。它将通知给所有在线的客户端(死蛇由 player.render_game_over() 变成石头)贪吃蛇已经死了,并更新最高成绩表。Player 对象的 alive 标志被设置为 False ,这样在渲染下一帧时该玩家将被忽略,直到他再次加入游戏。如果没有蛇活着,“游戏结束”消息呈现在游戏区域。同时,主游戏循环将停止并将game.running 标志设置为 False,玩家下一次按下 “Join” 键时会清空游戏区域。

在每次生成下一帧时,数字和石头也同时由随机值决定出现。出现一个数字还是一块石头的几率可以在 settings.py 中修改。请注意,在游戏区域每一条活着的蛇都有相应的数字出现,所以蛇越多,数字也将更多,因而它们将有足够的食物。

4.4 网络协议

从客户端发送的消息列表

命令参数描述
new_player[name]设置玩家昵称
join玩家将加入游戏

从服务器发送的消息列表

命令参数描述
handshake[id]将id分配给一位玩家
world[[(char, color), …], …]初始化游戏区域地图
reset_world清理地图,所有字符替换为空格
render[x, y, char, color]在对应位置显示字符
p_joined[id, name, color, score]新加入游戏的玩家
p_gameover[id]一位玩家游戏结束
p_score[id, score]为一位玩家设置得分
top_scores[[name, score, color], …]更新最高得分表

典型的消息交换规则

客户端->服务器服务器->客户端服务器->所有客户端注释
new_player名称传递给服务器
handshakeID分配
world初始游戏地图传递完成
top_scores最近的最高得分表传递完成
join玩家按下“加入”,游戏循环开始
reset_world命令客户端清理游戏区域
render, render,…第一个游戏标志,第一帧渲染
(key code)玩家按下某个按键
render, render,…第二帧渲染
p_score蛇吃了一个数字
render, render,…第三帧渲染
…重复数帧…
p_gameover蛇在吃障碍时死亡
top_scores更新最高成绩表(如果有更新)

5.总结

说实话,我真的很喜欢使用最新版 Python 的异步功能。新的语法与之前不同,所以异步代码现在简单易读,很容易就可以分辨出哪些调用时非阻塞的,是否正在切换为 green 协程。现在我可以满怀信心地声称, Python 是一种异步编程的好工具

SnakePit 在 7WebPages 团队中很受欢迎。如果你决定在公司用它放松一下时,请记得通过 Twitter 或者Facebook 给我们反馈。

Python 翻译组是EarlGrey@编程派发起成立的一个专注于 Python 技术内容翻译的小组,目前已有近 30 名 Python 技术爱好者加入。

翻译组出品的内容(包括教程、文档、书籍、视频)将在编程派微信公众号首发,欢迎各位 Python 爱好者推荐相关线索。

推荐线索,可直接在编程派微信公众号推文下留言即可。

相关推荐

Python爬虫进阶教程(二):线程、协程

简介线程线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能...

A320-V2500发动机系统FADEC介绍(2)

目的全权数字发动机控制(FADEC)系统在所有飞行和运行阶段提供全范围发动机控制。...

三国志战棋版:玩家“二叔”用这套群DOT在比武中拿下31胜5负

声明:本文首发于今日头条,而后发布于“鼎叔闯三棋”的微信公众号、抖音、哔哩哔哩和小红书平台,如果在其他平台就是抄袭。...

真正的独一无二:Dot One 推出 DNA 定制系列 139英镑起

相信很多人在挑选衣物时有着这样的困扰,综合了性价比、面料等因素后好不容易找到了心仪的款式,还要担心是否会撞衫,不管是擦肩而过的陌生人还是身边的熟人,都令人尴尬。小部分人为此热衷于购买少量的古着或者限量...

崩铁:周年庆福利再升级,老角色加强时间确定,3.xdot体系反转

#埃安UT大一圈高级很多#...

Dotgo推出RBMHub,扩大了CPaaS提供商的覆盖范围和功能

据telecompaper网7月15日报道,用于商业消息传递的RichCommunicationServices(RCS)解决方案的领先提供商Dotgo宣布推出RBMHub。RBMHub的推出扩大了C...

深度解析:快照取消Dot职业的将何去何从

写在前面曾几何时,术士的出现便被冠以dot大师的名头,从远古时期的献祭腐蚀虹吸不如暗牧一个痛,到TBC上满dot=荣誉击杀+1,到wlk接近全暴击的冰晶腐蚀,再到CTM就算了吧MOP的各种变态吸x放...

星穹铁道:抽卡芙卡之前,你必须了解什么是dot!

卡妈终于上线了,可还是有很多人不明白什么是dot伤害,抽了卡妈直接玩起了直伤流,把一个持续伤害的引爆器玩成了打手,卡妈打dot伤害是远高于直伤的,有了卡妈的玩家一直了解dot,不然这卡妈就真被玩成四不...

游戏界的闪耀星辰陨落:悼念知名游戏博主″dotα牛娃″

无尽哀思!在数字时代浪潮中,游戏不仅是消遣娱乐的代名词,更是连接心灵的桥梁,构筑了无数人的青春回忆。在这片浩瀚无垠的游戏宇宙中,有这样一位博主,他以独特的风采、深邃的洞察力和无尽的热情,成为了玩家心中...

直击2017新加坡同性恋聚会Pink Dot,自由爱!

今年的“粉红点”又来啦~这个支持LGBT群体(男女同志、双性恋、跨性别等)群体的活动,从2009年起,已经在新加坡举办8年了!”这个非营利的同性恋权益活动,主要是希望大家了解到,不管一个人的性倾向或...

python-dotenv,一款超级实用处理环境变量python库

python-dotenv,一款超级实用处理环境变量python库python-dotenv概述:...

亚马逊语音助手毫无征兆发笑 诡异至极吓坏用户

来源:新华网美国电商亚马逊7日承诺,将更改名下“亚历克萨”语音系统设置,令它不会莫名发笑,免得吓坏用户。“亚历克萨”是亚马逊开发的语音助手软件,可服从用户语音指令完成对话、播放音乐等任务。依照原来设计...

2022最火英文网名男女生

精选好听英文昵称带翻译1.moveon(离开)2.Monster(怪物)3.Solo吉他手4.Finish.(散场)...

智能家具 RecycleDot 的出现给传统家具厂商带来新的挑战

从可穿戴手环、手表到智能衣服,智能硬件逐步渗透到每一个领域。最近有一对父子MikeSandru和JohnSandru在自家的车库中设计了一款智能家具RecycleDot,给日渐萧条的家具行...

欧洲通信卫星公司 OneWeb 敦促印度DoT尽早批准提供卫星宽带服务

据telecomtalk2月17日报道,欧洲通信卫星公司EutelsatOneWeb近日敦促印度电信部(DoT)尽快批准其在印度部署双地球站网关的计划,以便连接其近地轨道(LEO)全球卫星星座,并...