基于 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
基于 WebRTC 创建一款多人联机游戏的更多相关文章
- 从零开始ming的多人联机游戏--游戏客户端(1)六边形地图
打算做的小游戏是一个多人联机的策略类游戏,类似于<文明>那种 游戏的玩法并不确定,开这个坑主要是为了入门后端开发,顺便熟悉下游戏开发 这篇文章使用unity,实现了六边形单元地图的创建.后 ...
- 从零开始ming的多人联机游戏(3)为socket通讯添加mysql数据库
macOS下visual studio C#加载mySql 本文在上一节的基础上,添加了mysql数据库的功能.client发送信息给服务器后,服务器将收到的消息保存在数据库中. 如果client发送 ...
- 转-基于NodeJS的14款Web框架
基于NodeJS的14款Web框架 2014-10-16 23:28 作者: NodeJSNet 来源: 本站 浏览: 1,399 次阅读 我要评论暂无评论 字号: 大 中 小 摘要: 在几年的时间里 ...
- 关于基于webrtc的android-apk 和 webrtc-brows
这一段时间我在做一些关于基于webrtc应用的一些研究,做个一个android的demo,详情如下: 手机客户端: 基于webrtc的 android apk (webrtc 代码版本 R67 ...
- C#/.NET基于Topshelf创建Windows服务程序及服务的安装和卸载(极速,简洁)
本文首发于:码友网--一个专注.NET/.NET Core开发的编程爱好者社区. 文章目录 C#/.NET基于Topshelf创建Windows服务的系列文章目录: C#/.NET基于Topshelf ...
- 基于tcp和多线程的多人聊天室-C语言
之前在学习关于网络tcp和多线程的编程,学了知识以后不用一下总绝对心虚,于是就编写了一个基于tcp和多线程的多人聊天室. 具体的实现过程: 服务器端:绑定socket对象->设置监听数-> ...
- 7款让人惊叹的HTML5粒子动画特效
HTML5的很大一个优势就是可以更加便捷高效地制作网页粒子动画特效,特别是Canvas特性,可以实现在网页上绘制任何图形和动画.本文要分享7款让人惊叹的HTML5粒子动画特效,这些粒子特效都提供源代码 ...
- ABP框架使用Mysql数据库,以及基于SQLServer创建Mysql数据库的架构和数据
ABP默认的数据库是SQLServer,不过ABP框架底层是EF框架,因此也是很容易支持其他类型的数据库的,本篇随笔介绍在ABP框架使用Mysql数据库,以及基于SQLServer创建MySql数据库 ...
- 通过自定义特性,使用EF6拦截器完成创建人、创建时间、更新人、更新时间的统一赋值(使用数据库服务器时间赋值,接上一篇)
目录: 前言 设计(完成扩展) 实现效果 扩展设计方案 扩展后代码结构 集思广益(问题) 前言: 在上一篇文章我写了如何重建IDbCommandTreeInterceptor来实现创建人.创建时间.更 ...
随机推荐
- 部署与管理ZooKeepe
1.部署 本章节主要讲述如何部署ZooKeeper,包括以下三部分的内容: 1. 系统环境 2. 集群模式的配置 3. 单机模式的配置 系统环境和集群模式配置这两节内容大体讲述了如何部署一个能够用于生 ...
- HBase Region级别二级索引
我们会经常谈及二级索引,这是对全表数据进行另外一种方式的组织存储,是针对table级别的.如果要为HBase上的表实现一个强一致性的二级索引,那么就无法逃避分布式事务,而这一直是用户最期待的功能. 而 ...
- obj-c编程19:关联对象
对于一些无法子类化的实例对象来说,如果希望将一个对象与其绑定该如何做呢? 以下示例虚构了一个HyConsoleAlert类,User类将会使用该类在控制台显示定制的告警.如果User中包括多个Aler ...
- java基础多线程之共享数据
java基础巩固笔记5-多线程之共享数据 线程范围内共享数据 ThreadLocal类 多线程访问共享数据 几种方式 本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保 ...
- LeetCode(33)-Pascal's Triangle II
题目: Given an index k, return the kth row of the Pascal's triangle. For example, given k = 3, Return ...
- rails将类常量重构到数据库对应的表中之三
经过博文之一和之二的重构,貌似代码表现的还不错,正常运行和test都通过鸟,但是,感觉告诉我们还是有什么地方不对劲啊!究竟是哪里不对劲呢?我们再来好好看一下. 我们把数据库表中的支付方式集合直接放在实 ...
- ZeroMQ 教程 001 : 基本概览
介绍性的话我这里就不翻译了, 总结起来就是zmq很cool, 你应该尝试一下. 如何安装与使用zmq 在Linux和Mac OS上, 请通过随机附带的包管理软件, 或者home brew安装zmq. ...
- Ubuntu18.04教程
pre.ctl { font-family: "Liberation Mono", monospace } h1 { margin-bottom: 0.21cm } h1.west ...
- 爬虫Scrapy框架运用----房天下二手房数据采集
在许多电商和互联网金融的公司为了更好地服务用户,他们需要爬虫工程师对用户的行为数据进行搜集.分析和整合,为人们的行为选择提供更多的参考依据,去服务于人们的行为方式,甚至影响人们的生活方式.我们的scr ...
- VueJs(8)---组件(注册组件)
组件(注册组件) 一.介绍 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树 那么什么是组件呢? 组 ...