基于 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

 

时间: 2024-10-27 14:00:42

基于 WebRTC 创建一款多人联机游戏的相关文章

Konami暗示X360版《寂静岭》有多人联机章节

近日在接受意大利版Xbox 360官方杂志时,<寂静岭:大雨>的艺术总监拉狄克·马拉克(Radek Marek)透露,Konami正考虑为这款游戏提供专门的对应Xbox Live的多人联机版本. 马拉克在接受采访时确认,现阶段马拉克<寂静岭:大雨(Silent Hill: Downpour)>将不会有多人联机模式,但他同时暗示Konami"正在考虑制作一个专门面向多人联机游戏的独立章节". 马拉克表示,他的开发小组目前还没有得到有关这个多人联机版的详细资料,但透

Python 和 Asyncio 编写多人在线游戏

之前 PythonTG 翻译组分享过一篇 Pygame 的入门教程,教的是单机游戏开发.今天和大家分享如何编写一个多人在线游戏,此教程共分为三部分,今天是第一篇,为大致概述.有兴趣的朋友请继续关注后续文章. 本文作者为 Kyrylo Subbotin,是一家 IT 咨询公司的 Python 工程师.本文译者为 sleepyjoker,由编程派作者 EarlGrey 校对. 译者简介:sleepyjoker,东南大学电子科学与工程专业大二学生.虽然暂时还是python菜鸟,但喜欢通过代码完成各种有

玩家心目中的五款最差结局游戏

最差游戏结局一直是玩家们经常谈及的话题之一.有些电子游戏甚至因为非常糟糕的剧情内容而饱受恶评.虽然我们也见过了许多剧情超赞的游戏,但肯定你也见过不少很烂的.这里我们来分享一下国外一些玩家评选出五款最差结局的游戏.部分玩家可能会从中获得一些共鸣. 请注意的是,如果你还没有打穿这款游戏,那么下面的内容可能包含一定的剧透.值得一提的是尽管游戏的结局都公认的非常烂,游戏本身的素质却非常出色,这里所谈及的最差也仅限游戏的结局部分. <光环2>(Halo 2) 这是许多 Xbox 玩家公认的最烂结局游戏之

《无冬之夜》网游采用多人联机模式类似暗黑

之前,曾有消息称,龙与地下城OL.博德之门系列.无冬之夜系列的开发商Cryptic Studio可能会开发无冬之夜的网游版,而近日,又有消息爆出,无冬之夜网游版名为<Neverwinter>,隶属于龙与地下城系列,并且,这款游戏并不是走MMO形式,而是多人联机RPG模式,游戏很可能更偏向于暗黑破坏神类型. 在年初就不断有流言指出,Cryptic Studio即将推出一款龙与地下城系列的MMO,与现在Turbine的DDO分庭抗礼,而这款游戏将会是<Neverwinter>,笔者只能

推荐一个基于webrtc实现语音通讯的课程

问题描述 该课程由环信资深音视频技术专家 **彭祖元** 老师主讲 http://edu.csdn.net/course/detail/320**特别提示:想看课程但没有C币?社区近期将要推出C币大放送活动,****敬请期待!**:基于webrtc的跨平台实时语音通信解决方案:课程介绍基于webrtc实现多平台实时语音通信的解决方案,包括音频处理流程,各模块功能,对外接口,实时语音demo示例,如何拓展多人语音通信功能等.:让学员通过学习本课程能基于webrtc实现一对一实时语音通话和以及多人语

基于catalog 创建RMAN存储脚本

--============================== -- 基于catalog 创建RMAN存储脚本 --==============================       简言之,将rman的备份恢复命令写成脚本并保存在恢复目录内,恢复目录内的脚本可用性及通用性高于基于文件系统的脚本. 客户端只要能够登录到RMAN恢复目录,则这些脚本可用,尤其对于global脚本,可以被任意注册的数据库调度.   一.脚本的分类     local : 在rman连接的目标数据库下创建的脚本

与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室

原文:与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phone (30) - Communication(通信)之基于 Socket TCP 开发一个多人聊天室 作者:webabcd 介绍与众不同 windows phone 7.5 (sdk 7.1) 之通信 实例 - 基于 Socket TCP 开发一个多人聊天室 示例1.服务端ClientSocketP

与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室

原文:与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 [索引页][源码下载] 与众不同 windows phone (31) - Communication(通信)之基于 Socket UDP 开发一个多人聊天室 作者:webabcd 介绍与众不同 windows phone 7.5 (sdk 7.1) 之通信 实例 - 基于 Socket UDP 开发一个多人聊天室 示例1.服务端Main.cs /* *

基于jQuery创建鼠标悬停效果的方法_jquery

本文实例讲述了基于jQuery创建鼠标悬停效果的方法.分享给大家供大家参考.具体实现方法如下: 1. 创建HTML: <ul> <li><a href="/tv"><img src="images/tv_off.gif" class="mainnav"></a></li> </ul> 2. 选择.mainnav的class: $(".mainnav&qu