使用 WebSocket 构建多人游戏 - 第 1 部分
一份关于如何在 NodeJS 和 React 上使用 socket.io 在浏览器上正确构建多人游戏的权威指南。涵盖从文件夹结构到项目部署的所有内容。
拥有一个独特的想法固然重要,但更重要的是,在项目开始时,找到正确的方向。
“未来属于那些学习更多技能并以创造性的方式结合这些技能的人。”
- 罗伯特·格林,《精通》
为什么要有另一个教程?
澄清这一点非常重要。网上有很多关于“Socket.io 入门”的指南,如果全是聊天应用,那就更让人抓狂了。但这里我们重点介绍“使用 Socket.io 构建可扩展项目入门”,它可不是聊天应用 :)。
本指南将更多地讲解代码基础架构,而非 UI/UX。如果您觉得 UI 看起来不太吸引人,请耐心等待。
什么是 socket.io?
Socket.io 是基于 WebSockets 协议构建的抽象。WebSockets 是一种允许客户端和服务器之间进行双向同步数据交换的协议。或者简单地说,它是一个双向通信管道。
注意:除非另有说明,否则 WebSockets 和 socket.io 可以互换使用(尽管它们在某些方面有所不同)。
为什么是 WebSockets 而不是 HTTP?
对于实时多人游戏,我们要求客户端向服务器发送信息包,服务器也同时发送/广播数据。这无法通过 HTTP 实现,因为客户端必须向服务器发送请求才能真正接收数据。这对于多人游戏来说并不可行。
您说的“正确方法”是什么意思?
正确的方法——这里指的是从易于进一步扩展的代码库入手,同时不会给小型项目带来太多麻烦。它涵盖了模块化程度更高的项目可以遵循的常见实践。这并不意味着它是构建 WebSocket 项目的官方方法。这只是我个人的看法,你可以轻松地替换掉项目中你不喜欢的部分 :D
这个项目是关于什么的?⚡⚡
现在进入指南的核心部分。本指南通过一个真实项目讲解了如何构建一个“多人 socket.io 游戏”。这样,我们就能更轻松地了解项目的运行情况,并且确保代码/基础架构能够正常运行!该项目是……
多人足球选秀模拟器
这个游戏有什么用?⚡
这是一款回合制多人游戏。玩家进入并创建一个房间。其他人进入房间。游戏开始后,所有玩家洗牌,第一人称玩家有机会选择自己想要的足球运动员。他可以从球员列表中搜索(查看他们的数据、位置、评分和其他详细信息),并在指定时间内确认选择。现在,轮到另一位玩家了。重复此操作,直到每个人都选完了自己想要的球员。
很简单?是或否,没关系。我们将详细分析其背后的代码基础架构。
服务器架构
游戏架构
上图从鸟瞰的角度解释了一切是如何联系在一起的。
本指南中的 HTTP 和 Websockets 服务器均使用 NodeJS。我们使用 Redis DB,因为 socket.io 支持开箱即用的集成,而且由于数据存储在内存中,读写操作速度更快。MongoDB 用作更持久的存储解决方案。每轮选秀结束后,每个房间的比赛结果和用户队伍信息都会存储在 MongoDB 中。如果用户希望注册,它还会存储用户凭证(本项目包含可选的注册/登录步骤)。
WebCrawler 使用 Python3 编写,使用了 Scrapy 库。足球运动员数据集是从https://sofifa.com爬取的。该数据集包含超过 20,000 名球员的数据,包括他们的评分、统计数据、身价、俱乐部等。它还有一个可选的数据分析工具 jupyter-notebook,可用于处理爬取的数据,但本指南不讨论该工具。
文件夹结构(ExpressJS + MongoDB + socket.io)
NodeJS 不会强制你遵循代码结构。这给了我们很大的灵活性来设计它们,但你可能会犯下严重的错误,从而导致项目维护和扩展困难。这种特殊的项目结构可以在使用 Socket + NodeJS 时使用。
让我们深入了解一下项目代码库的结构
.{src}
├── controller
│ ├── authController.js # Handles authentication requests
│ ├── searchController.js # Handles search queries
│ ├── userController.js # Handles user profile operations
│ └── ...
│
├── database
│ ├── db.js # Initialize DB connection
│ └── ...
│
├── middlewares
│ ├── authenticated.js # Decode and verify JWT token
│ ├── error.js # Common Error Handler
│ ├── logger.js # Control logging levels
│ └── ...
│
├── models
│ ├── roomsModels.js # DB model for rooms
│ ├── usersModel.js # DB model for users
│ └── ...
│
├── schema
│ ├── rooms.js # DB Schema for rooms
│ ├── users.js # DB Schema for users
│ └── ...
│
├── socker
│ ├── roomManager.js # Socket listeners/emitters handle
│ ├── sockerController.js # Control socket connections
│ └── ...
│
├── app.js # Entry file for the project
├── env.js # Store environment variables
├── routes.js # All routes initializer
└── ...
后端根据项目需求划分到不同的目录。如果您想跳过或替换某些模块,只需添加新的目录即可。
大多数子目录在 Node 项目中都是通用的,所以我就不在这里详细解释了。每个目录旁边的注释应该能让你大概了解它的含义。
我们将重点关注子目录 socker/。这是核心 socket.io 代码所在的位置。
socket.io 的入口点(App.js)
import { socker } from './socker'; | |
import express from 'express'; | |
import http from 'http'; | |
import { API_PORT, host } from './env'; | |
const app = express(); | |
const server = new http.Server(app); | |
socker(server); | |
app.listen(API_PORT, () => { | |
logger.info(`Api listening on port ${Number(API_PORT)}!`); | |
}); | |
server.listen(Number(API_PORT) + 1, () => { | |
logger.info(`Socker listening on port ${Number(API_PORT) + 1}!`); | |
logger.info(`Api and socker whitelisted for ${host}`); | |
}); |
这里创建了两个服务器,app
一个监听 HTTP 请求,server
另一个监听 WebSocket 连接。建议将它们连接到不同的端口,以免混淆。
您可能想知道第 1 行和第 8 行的“socker”是什么。
socker 是什么?
Socker 只是一个函数别名(因为我在这里构建一个足球选秀游戏,呵呵!)。这个函数将Server
(在 app.js 第 8 行传入的)附加到 new 上的 engine.io 实例http.Server
。简而言之,它将 socket.io 引擎附加到传递给它的服务器上。
但上面的代码并没有解释太多。现在,出现了以下问题:
- 我如何与连接的客户端进行交互?
- 命名空间在哪里?
- 房间/频道在哪里?
- 最重要的是,游戏在哪里?
创建命名空间以及为什么?
命名空间是 socket.io 的一个重要特性。它代表一个套接字池,这些套接字在给定的范围内连接,并通过路径名(例如 、 、 等)进行标识/classic-mode
。/football-draft
这/pokemon-draft
本质上是在创建不同的端点或路径。它允许我们最小化资源(TCP 连接)的数量,同时通过在通信通道之间引入隔离来分离应用程序中的关注点。默认情况下,socket.io 连接到/
命名空间。
创建房间/频道以及为什么?
在每个命名空间内,您可以创建任意频道或房间。这进一步允许您创建套接字可以连接join
或连接的连接leave
。在这里,我们使用channels
来创建不同的房间,用户可以加入或创建房间一起玩。
加入房间的示例
该join()
操作会检查所需的房间是否roomId
已创建。如果没有,则创建房间并将玩家添加到指定的 roomId。如果已创建,则直接加入该房间。
总结命名空间和通道使用的完整示例:
第一部分就到这里。这里展示的代码结构非常适合中型项目。如果您正在构建一个快速原型,可以省略或合并 schema 和 models 文件夹。如果需要,请随时简化项目 :)
如果项目规模扩大怎么办?当前的结构可能无法正常工作。您可以根据所需的服务和组件创建子文件夹(user-authentication
、__tests__
、analytics
等)。您甚至可以创建微服务,即单独部署每个进程或服务,以便在高负载下进行负载均衡或仅扩展进程。
请记住不要过度设计您的产品,分阶段构建和部署!
无论是一个笑话还是一个工程项目,没有人喜欢做得过火:)
如果您有兴趣,以下是我的项目链接:
- 后端(Websockets + HTTP) - https://github.com/sauravhiremath/fifa-api
- 前端(ReactJS) - https://github.com/sauravhiremath/fifa
- WebCrawler(Python3 + Scrapy) - https://github.com/sauravhiremath/fifa-stats-crawler
下一篇文章将重点关注:
- 为每个命名空间创建和处理房间。
- 项目客户端的简要说明(使用 ReactJS)
- 每个项目包的CI和部署。
文章来源:https://dev.to/sauravmh/building-a-multiplayer-game-using-websockets-1n63如果您对以后的文章有任何问题或建议,请告诉我
✌️️祝你有美好的一天!
在 Twitter 上关注我@sauravmh了解最新动态!