基于 WebRTC 创建一款多人联机游戏

简介: 本项目的目标旨在尽可能少用服务器资源的前提下研发一款在线多人游戏,同时期望在一个用户的浏览器上运行游戏,同时让另一个玩家来连接。此外还希望程序尽可能简单以便于在博客中分析。 运用的技术 在我刚接触 P2P 网络技术的时候便发现了 WebRTC,并认为这项技术正好适合此项目。

本项目的目标旨在尽可能少用服务器资源的前提下研发一款在线多人游戏,同时期望在一个用户的浏览器上运行游戏,同时让另一个玩家来连接。此外还希望程序尽可能简单以便于在博客中分析。

运用的技术

在我刚接触 P2P 网络技术的时候便发现了 WebRTC,并认为这项技术正好适合此项目。WebRTC 是一个新型网络标准旨在给网络浏览器提供即时通信的能力。大部分 WebRTC 案例都是关于建立一个视频或者音频流,但是这项技术也可以用来传输二进制数据。在此项目中,更倾向于使用数据通道将用户的输入传输到主机;游戏状态传输给玩家。

但是,WebRTC 并不能完全消除对服务器的依赖。为了建立一个连接,两个服务器必须传输少量的信息。一旦连接建立完成,接下来的整个连接过程是纯 P2P。

WebRTC API 相对复杂,所以一个好的简化库是有必要的。PeerJS 目前功能最全面的库之一,然而已经有两年没有更新了。在使用 PeerJS 的过程中碰到了几个严重的漏洞导致我不得不放弃使用它。Simple-peer 是一个不错的替代品,提供了很多用于创建 WebRTC 连接的简单接口。以下是他的代码:

var SimplePeer = require('simple-peer')

var peer1 = new SimplePeer({ initiator: true }) var peer2 = new SimplePeer() peer1.on('signal', function (data) { // when peer1 has signaling data, give it to peer2 somehow peer2.signal(data) }) peer2.on('signal', function (data) { // when peer2 has signaling data, give it to peer1 somehow peer1.signal(data) }) peer1.on('connect', function () { // wait for 'connect' event before using the data channel peer1.send('hey peer2, how is it going?') }) peer2.on('data', function (data) { // got a data channel message console.log('got a message from peer1: ' + data) })

创立连接

为了创建两个浏览器之间的连接,需要进行大约 2kb 的信号输出传输。使用 Firebase 实时数据库是一个不错的选择,因为它允许轻松地在两个浏览器之间同步数据,并且免费层提供了大量的存储空间。

从用户的角度来看,主机给玩家一个四字母代码,用于连接游戏。从浏览器的角度来看,这个过程也只是稍微复杂一些。作为参考,我的数据库规则如下所示:

{
  "rules": {
    "rooms": {
      // 4 Digit room code used to connect players "$room_code": { "host": { "$player": { "$data": { "data": { // Data from the host for the player } } } }, "players": { "$player": { "$data": { "data": { // Data from the player for the host } } } }, "createdAt": { // Timestamp set by host when room is created } } } } }

创建一个房间

为了创造一个有效房间,主机首先通过随机地尝试 4 个字符代码生成代码,直到找到一个没有使用的房间。如果房间在数据库中不存在,或者房间是在 30 分钟前创建的,房间被视为未使用。主机应该在游戏开始时删除房间,但我想确保避免僵尸房间。当主机找到一个开放的房间时,主机的浏览器将自己添加为房间的主机并等待玩家加入。

function getOpenRoom(database){
 return new Promise((resolve, reject) => { const code = generateRoomCode(); const room = database.ref('rooms/'+code); room.once('value').then((snapshot) => { const roomData = snapshot.val(); if (roomData == null) { // Room does not exist createRoom(room).then(resolve(code)); } else { const roomTimeout = 1800000; // 30 min const now = Date.now(); const msSinceCreated = now - roomData.createdAt; if (msSinceCreated > roomTimeout) { // It is an old room so wipe it and create a new one room.remove().then(() => createRoom(room)).then(resolve(code)); } else { // The room is in use so try a different code resolve(getOpenRoom(database)); } } }) }); }

加入游戏

玩家通过输入房间代码和用户名加入游戏。加入的玩家的浏览器会通过在在路由中添加条目来提醒主机rooms/[code]/players。当玩家获取他们信号数据时,将数据发送到路由rooms/[code]/players/[name]

// code and name are entered by user
const peer = new SimplePeer({initiator: true}); this.peer = peer; this.setState({host: peer}); // Sending signaling data from player peer.on('signal', (signalData) => { const nameRef = database.ref('/rooms/'+code+'/players/'+name); const newSignalDataRef = nameRef.push(); newSignalDataRef.set({ data: JSON.stringify(signalData) }); }); // Listen for signaling data from host for me const hostSignalRef = database.ref('/rooms/'+code+'/host/'+name); hostSignalRef.on('child_added', (res) => { peer.signal(JSON.parse(res.val().data)); });

主机会一直等待直到新的玩家被加入进来。一旦玩家连线,主机接收他们发出的信号并用自己的信号经路由回复rooms/[code]/host/[name]

// Listen for new players
playersRef.on('child_added', (res) => { const playerName = res.key; // Create Peer channel const peer = new SimplePeer(); // Listen for signaling data from specific player playerRef.on('child_added', (res) => peer.signal(JSON.parse(res.val().data))); // Upload signaling data from host const signalDataRef = database.ref('/rooms/'+code+'/host/'+playerName); peer.on('signal', (signalData) => { const newSignalDataRef = signalDataRef.push(); newSignalDataRef.set({ data: JSON.stringify(signalData) }); }); });

在这之后,主机与用户的连接用 peer.on(‘data’, cb) 与 peer.send(data)。一旦与主机连接,玩家的机器将终止其 Firebase 连接,并且主机在游戏启动时也执行相同操作。

大功告成!到这一步我已经在玩家与主机间建立了双向连接,就像以前的传统服务器一样。接下来就是开始游戏然后传输玩家间的数据。

获得用户输入

一旦用户按键,他们的输入会以 JSON 格式进行传输。比如 { up: true }

主机持续追踪每个玩家的注入并根据这些输入控制玩家的行动。

分享游戏状态

为了保证游戏的开发过程简单,我更倾向于使用 2D 框架 Phaser。游戏在主机的机器上运行,主机处诸如碰撞之类的基本物理运算。每一帧,所有精灵的位置与大小都会被传输给每一个玩家。为了简化过程,我使用精灵数据在玩家的浏览器中重绘整个游戏。尽管这个解决方案很实用,但更复杂的游戏可能需要一个更有效的共享游戏状态的过程。

游戏画面

我用来测试上述代码的游戏是一个简单的横版2D 游戏。游戏中有随机出现的平台,最后一个留在平台上的玩家获胜。如果游戏有问题,是因为我并没有花很多的精力在打磨游戏上。

注意

因为游戏的服务器是运行在其中一个玩家的机器上,因此玩家是可以通过修改代码来操控这个游戏的。这个联机方案对于朋友间游玩的游戏来说很好,只要你的朋友不作弊。

总结

我建立了一个每个玩家只需要使用 2kb 服务器带宽的 P2P 多人游戏。以此推断,我的游戏在使用 Firebse 试用版的情况下,每月可以支持最多50 万名玩家。另外,文中的代码足以适用大部分应用。总而言之,WebRTC 是一个很灵巧的技术,期待有更多基于它的项目诞生!

http://geek.csdn.net/news/detail/210754

 

相关文章
|
7月前
|
Web App开发 开发工具 Android开发
Android平台不需要单独部署流媒体服务如何实现内网环境下一对一音视频互动
我们在做内网环境的一对一音视频互动的时候,遇到这样的技术诉求:如智能硬件场景下(比如操控智能硬件),纯内网环境,如何不要单独部署RTMP或类似流媒体服务,实现一对一音视频互动。
|
5月前
|
UED 内存技术
AS3多人游戏开发—同步人物移动
AS3多人游戏开发—同步人物移动
39 0
|
8月前
|
编解码 NoSQL 关系型数据库
五脏俱全,搭建部署多人语音厅源码功能分析
首先,要搭建部署一个稳定成熟的多人语音厅源码,具体的实现方式可能因项目需求以及使用的工具而有所不同,下边来简单分析下。
五脏俱全,搭建部署多人语音厅源码功能分析
|
8月前
|
开发框架 JavaScript 数据库
站在源码的角度看多人语音厅房间系统
关于多人语音厅房间系统的管理以及权限,今天来整合交流下。
站在源码的角度看多人语音厅房间系统
|
9月前
|
编解码 监控 C++
H264音视频直播系统 服务器端+客户端源码 可用于视频聊天、视频会议
H264音视频直播系统 服务器端+客户端源码 可用于视频聊天、视频会议
107 0
|
10月前
|
网络协议 网络虚拟化
局域网游戏联机原理解析
局域网游戏联机原理解析
233 1
|
编解码 移动开发 小程序
浅尝云游戏音视频小程序
云电脑、云办公、云会议、云游戏等等云概念,在不平凡的 2020,依然此起彼伏,你方唱罢我登场!它们似乎在告诉着我们什么:5G 时代不会平凡?还是说正为某项能颠覆产业的技术应用而蛰伏着?
|
安全 Ubuntu Java
记第一次使用服务器实现Minecraft联机游戏
通过阿里云提供的免费初次申领服务器,本人有幸参与了自己第一次对服务器开发运营的实践,并成功通过服务器进行了多次联机游戏
439 0
记第一次使用服务器实现Minecraft联机游戏
|
存储 网络协议 安全
使用WebSocket实现实时多人答题对战游戏
在本文中,我将介绍如何使用WebSocket向实时多人答题对战游戏提供服务端,并详细介绍通接口的设计。
759 0
|
Java Linux Windows
利用阿里云进行多人交互服务器的搭建
以Minecraft服务器的搭建为例,简要介绍如何利用阿里云,搭建Windows平台下的窗口型多人远程交互服务器
利用阿里云进行多人交互服务器的搭建