一、游戏帧同步

1.简介

·现代多人游戏中,多个客户端之间的通讯大多以同步多方状态为主要目标,为了实现这一目标,主要有两个技术方向:状态同步、帧同步。

·状态同步的思想中不同玩家屏幕上的一致性的表现并不是重要指标,只要每次操作的结果相同即可。所以状态同步对网络延迟的要求并不高。

·帧同步主要依赖客户端的能力,服务器仅仅是做一个转发,甚至客户端可以无需服务器,通过P2P方式来转发数据。由于只是转发游戏的行为,所以广播的数据量比状态同步要小很多。

本文将以帧同步技术为主来介绍如何实现一款联机游戏。

2.小游戏案例

·本次我们在《街霸小游戏》中利用腾讯云的游戏联机对战引擎实现了玩家之间的PVP玩法。

感兴趣的同学可以扫码体验:

二、游戏联机对战引擎:Mgobe

1.引擎简介

·Mgobe是由腾讯云提供的游戏联机对战引擎,可以为游戏提供房间管理、在线匹配、帧同步、状态同步等网络通信服务,帮助开发者快速搭建多人交互游戏。

·Mgobe可以让我们在没有后台开发人力的情况下也能实现游戏的帧同步。

Unity Editor也嵌入了MGOBE,在Unity Editor 2019.1.9及以上版本,各位开发者可以通过服务面板,一键开通腾讯云服务MGOBE。

Cocos Creator嵌入了MGOBE,在v2.3.4及以上版本,各位开发者可以通过Cocos Service服务面板,一键开通腾讯云服务MGOBE。

·官网:https://cloud.tencent.com/product/mgobe

2.开发语言

·Mgobe支持使用 JavaScript 或 TypeScript 来进行前端开发。

3.支持平台

·Mgobe目前支持:微信小游戏、QQ小游戏、百度小游戏、OPPO小游戏、vivo小游戏、字节小游戏;H5小游戏和手游。

三、纯前端打造帧同步实现联机对战

·接下来会从前端的角度来一步一步讲解使用Mgobe的方法,借助Mgobe我们可以不用知晓后台和运维知识,就可以构建起一套性能优越的帧同步游戏。

1.控制台配置

·首先我们需要在Mgobe的控制台中创建游戏实例,以获取游戏ID、游戏Key和域名等信息,我们会在初始化SDK时使用到游戏ID和游戏Key。

·出于安全考虑,微信小游戏会限制请求域名,所有的 HTTPS、WebSocket、上传、下载请求域名都需要在微信公众平台进行配置。因此,在正式接入游戏联机对战引擎 SDK 前,还需要开发者在微信公众平台配置合法域名。 ·需要配置的域名包含一条 request 域名和两条 socket 域名记录,配置如下:

// request 域名

report.wxlagame.com

// socket 域名

xxx.wxlagame.com

xxx.wxlagame.com:5443

2.SDK

2.1.下载

·SDK下载地址:https://cloud.tencent.com/document/product/1038/33406

2.2.引入SDK

·SDK文件包含 MGOBE.js 和 MGOBE.d.ts,即源代码文件和定义文件。在 MGOBE.js 中,SDK接口被全局注入到 window 对象下。因此,只需要在使用SDK接口之前执行 MGOBE.js 文件即可。

·以微信为例,只需将 MGOBE.js 放到项目下任意位置,在 game.js 中 import SDK 文件后即可使用 MGOBE 的方法。当然也可以使用 import/from、require 语法显式导入 MGOBE 模块。

2.3.直接使用密钥进行初始化

·用这种方式可以快速初始化SDK,可以最快的速度使用引擎的帧同步功能,但这种方式会在前端暴露游戏Key。

var gameInfo = {

openId: 'xxxxxx', //玩家的openID

gameId: "xxxxxx", //游戏id,在控制台中的“游戏ID”中获取

secretKey: 'xxxxxx' //游戏密钥,在控制台中的“游戏key”获取

};

var config = {

url: 'xxx.wxlagame.com',//游戏域名,在控制台中的“域名”获取

reconnectMaxTimes: 5, //重连接次数

reconnectInterval: 1000, //重连接时间间隔

resendInterval: 1000, //消息重发时间间隔

resendTimeout: 10000 //消息重发超时时间

};

Listener.init(gameInfo, config, function() {

if (event.code === 0) {

// 初始化成功

}

});

复制代码

·Listener 对象为 MGOBE 的子属性,该对象方法全为静态方法,不需要实例化。Listener对象主要用于给 Room 对象的实例绑定广播事件监听。

·初始化 Listener 成功后才能继续调用 Mgobe 引擎的其他接口。

2.4.利用签名来进行初始化(在前端隐藏游戏Key)

·用2.3的方法初始化 SDK 时,会在前端暴露游戏的密钥,为了避免在客户端泄露游戏的密钥,我们也可以使用签名的方式来初始化 SDK。

·在开发者服务器通过游戏 ID、游戏 Key、玩家 openId 等信息计算出游戏签名,然后再下发给客户端。客户端在初始化 SDK 时,需要实现一个 createSignature 签名函数,从服务端获取签名信息然后回调给 SDK。也就是在 gameInfo 中,将2.3中 的 secretKey 字段改为 createSignature 字段。

//这里仅列出与2.3不同的gameInfo, config和Listener.init与2.3一致,不再赘述。 var gameInfo = {

gameId: "xxxxx", //游戏id,在控制台中的“游戏ID”中获取

openId: "xxxxxx", //玩家的openID

// 实现签名函数

createSignature: callback => { //假设https://example.com/sign就是我们后台计算签名的接口

fetch("https://example.com/sign").then(rsp => rsp.json()).then(json => {

const sign = json.sign;

const nonce = json.nonce;

const timestamp = json.timestamp;

return callback({ sign, nonce, timestamp });

});

},

};

复制代码

·签名过程详见:https://cloud.tencent.com/document/product/1038/38863

3.房间

·在开发游戏的过程中,大部分接口都位于 Room 对象中。由于每个玩家只能加入一个房间,在游戏生命周期中可以只实例化一个 Room 对象来进行接口的调用。

3.1.实例化Room

var roomInfo = { id: "xxx" //房间ID }; var room = new MGOBE.Room(roomInfo);复制代码

创建房间、加入房间、匹配等接口调用直接使用 room 实例即可。但有3个接口例外:getMyRoom、getRoomList、getRoomByRoomId 接口是 Room 对象的静态方法,需要使用 Room.getMyRoom、Room.getRoomList、Room.getRoomByRoomId 来调用。

3.2.几个常用属性

3.2.1.roomInfo 属性

·roomInfo 为 Room 实例的属性,保存房间的相关信息,调用 Room 相关的接口会导致该属性发生变化。可以从 roomInfo 中获得房间的id、名称和玩家列表等。

3.2.2.networkState 属性

·用于获取客户端本地 SDK 的网络状态。注意 networkState 的网络状态与玩家信息 Player 中的网络状态概念不同,room.networkState 表示本地 socket 的状态,而 Player.commonNetworkState 和 Player.relayNetworkState 表示玩家在 Mgobe 后台中的状态。 ·networkState 网络状态发生变化时,room.onUpdate 将被触发。

room.onUpdate = function() {

console.log("房间信息更新:", room.roomInfo);

};

复制代码

3.3.初始化Room

room.initRoom();复制代码

·通过 room.initRoom 方法可以初始化一个房间,同时更新房间信息 roomInfo 。初始化可以更新 WebSocket 连接,这样才能及时收到房间的广播。此外,如果要加入指定ID的房间,也需要先对房间进行初始化,否则将无法使用 room.joinRoom 加入指定ID的房间。

3.4.为Room添加广播侦听

MGOBE.Listener.add(room);复制代码

·一个房间对象会有很多广播事件与其相关,例如该房间有新成员加入、房间属性变化、房间开始对战等广播。Room 实例需要在 Listener 中注册广播监听,之后可以通过 room.xxx 回调函数的形式来使用广播侦听,详见下文。

3.5.创建房间

·通过使用 room 实例的 createRoom 可以创建一个房间,创建成功后创建者会自动进入该房间。

var playerData = {

name:nickname, //玩家昵称

customPlayerStatus:playerStatus, //自定义玩家状态

customProfile:figureURL //自定义玩家信息

};//玩家信息

var createRoomData = {

roomName:"roomName", //房间名称

roomType:"1v1", //房间类型

maxPlayers:2, //房间最大玩家数量

isPrivate:true, //是否为私有房间,属性为 true 表示该房间为私有房间,不能被 matchRoom 接口匹配到

customProperties:roomStatus, //自定义房间属性

playerInfo:playerData //房主信息

};//房间信息

room.createRoom(createRoomData, function(e)

{

if(e.code === 0)

{

//创建房间成功

}

});

复制代码

·注意:创建房间的结果是通过回调异步返回的,而非派发事件。

3.6.加入房间

·通过使用 room 实例的 joinRoom 可以加入一个已经存在的房间。

var playerData =

{

name:nickname, //玩家昵称

customPlayerStatus:playerStatus, //自定义玩家状态

customProfile:figureURL //自定义玩家信息

};//玩家信息

var joinRoomInfo =

{

playerInfo:playerData

};//加入房间的信息

room.initRoom({ id: "xxx" });//加入房间前需要先初始化room实例

room.joinRoom(joinRoomInfo, function(e)

{

if(e.code === 0)

{

console.log("加入房间成功");

}

});

复制代码

·注意:加入房间的结果也是通过回调异步返回的,而非派发事件。加入房间前必须先初始化房间实例。

·对于已经存在于房间中的其他人,可以通过 room.onJoinRoom 来侦听新玩家的加入。

room.onJoinRoom = function(e)

{

console.log("新玩家加入,ID为:", e.data.joinPlayerId);

};

复制代码

3.7.离开房间

·使用 room.leaveRoom 就可以退出房间。

room.leaveRoom({}, function(e)

{

if(e.code === 0)

{

console.log("离开房间成功");

}

});

复制代码

·对于房间中的其他人,可以通过 room.onLeaveRoom 来侦听玩家的离开。

room.onLeaveRoom = function(e)

{

console.log("离开房间的玩家的ID:", e.data.leavePlayerId);

};

复制代码

4.匹配

4.1.匹配规则

·要进行房间匹配,需要先在控制台创建匹配规则,匹配规则既可以满足按人数匹配、按队伍匹配,也可以按段位等特殊方式来匹配。成功创建规则后会获得一个匹配code,匹配code将会用于匹配的相关接口,表示用这个规则来匹配符合条件的玩家。

规则创建之后还需要将规则绑定到服务器中才能生效,在“新建匹配”中选择上一步创建的匹配集即可。

4.2.匹配玩家

·有了匹配code后我们就可以在前端进行玩家匹配了,只要是符合规则中定义的条件的玩家,就会被匹配进同一个房间中。

var matchPlayersData = {

playerInfo:playerData, //发起匹配的玩家的信息,playerData在上文已多次出现,这里不再赘述

matchCode:matchCode //匹配code,在4.1中获得

};//玩家匹配信息

room.matchPlayers(matchPlayersData, function(e)

{

if(e.code === 0)

{

console.log("匹配请求成功");

}

});

复制代码

4.3.匹配房间

·matchPlayers 配合匹配code可以用来匹配玩家,那么通过使用 room.matchRoom 则可以进行房间的匹配。房间匹配是指按照传入的参数搜索现存的房间,如果存在,则将玩家加入该房间,如果不存在,则为玩家创建并加入一个新房间。

·matchRoom 不需要使用匹配code。

var playerInfo = {

name: "Tom",

customPlayerStatus: 1,

customProfile: "https://xxx.com/icon.png",

};//发起匹配者的信息

const matchRoomPara = {

playerInfo,

maxPlayers: 5,

roomType: "1",

};//房间匹配信息

room.matchRoom(matchRoomPara, function(e) {

if (event.code === 0) {

console.log("匹配成功");

}

});

复制代码

·matchRoom 与 matchPlayers 最大的不同就是:matchRoom 一定会让匹配发起人进入一个房间,但 matchPlayers 则不一定,如果当前没有符合匹配规则的玩家,则 matchPlayers 会返回失败。

5.帧同步

·终于来到这一步了,如果玩家已经成功加入房间,就可以通过帧同步功能进行游戏对战。

5.1.开启帧同步

·使用 room.startFrameSync 接口就可以开启帧广播。房间内任意一个玩家成功调用该接口都将导致全部玩家开始接收帧广播。

room.startFrameSync({}, function(e)

{

if(e.code === 0)

{

console.log("开始帧同步成功");

}

});

复制代码

·调用成功后房间内全部成员都将收到 onStartFrameSync 广播。该接口会修改房间帧同步状态为“已开始帧同步”。

room.onStartFrameSync = function()

{

//收到此广播后将持续收到 onRecvFrame 广播 //注意,这里还不是玩家之间相互进行帧同步的信息内容,onRecvFrame 中才是我们拿到帧同步内容的地方,见下文

};

复制代码

5.2.发送帧消息

·玩家收到帧同步开始广播后,才可以发送帧消息,后台会将每个玩家的帧消息组合后再广播给每个玩家。

·帧数据内容 data 类型为普通 Object,由开发者自定义,目前支持最大长度不超过1k。后台将集合全部玩家的帧数据,并以一定时间间隔(由房间帧率定义,可以在控制台配置)通过 onRecvFrame 广播给各客户端。调用结果将在 callback 中异步返回。

var frame = { cmd: "xxxxxxxx", id: "xxxxxxxx" };//一帧的内容,由开发者自定义

var sendFramePara = { data: frame };//发送给Mgobe的帧内容

room.sendFrame(sendFramePara, function(e) { console.log("发送帧同步数据"); });

复制代码

5.3.接收帧广播

·开发者可设置 room.onRecvFrame 广播回调函数来获得帧广播数据。onRecvFrame 广播表示收到一个帧 frame,frame 的内容由多个 MGOBE.types.FrameItem 组成,即一帧时间内房间内所有玩家向服务器发送帧消息的集合。

room.onRecvFrame = function()

{

console.log("收到帧同步消息=", e.data.frame); //我们就是从 e.data.frame.items 这个数组的每个元素的 data 属性来拿到我们在5.2中发送给Mgobe的帧内容的。 //5.2的帧内容:var frame = {cmd: "xxxxxxxx", id:"xxxxxxxx"}

};

复制代码

5.4.停止帧同步

·使用 room.stopFrameSync 接口可以停止帧广播。房间内任意一个玩家成功调用该接口将导致全部玩家停止接收帧广播。

room.stopFrameSync({}, function(e)

{

if(e.code === 0)

{

console.log("停止帧同步成功");

}

});

复制代码

·调用成功后房间内全部成员将收到 onStopFrameSync 广播。该接口会修改房间帧同步状态为“已停止帧同步”。

room.onStopFrameSync = function()

{

//收到该广播后将不再收到 onRecvFrame 广播

};

复制代码

·至此,利用Mgobe来进行帧同步开发的相关主要接口就介绍完毕了。下面将讲一些关于玩家信息的内容。

6.玩家信息

6.1.玩家ID

·玩家信息 Player 对象为 MGOBE 的子属性,用于访问玩家的基本信息,例如玩家 ID、openId 等。该对象记录了玩家的基本信息,默认全部为空。成功初始化 Listener 后,ID、openId 属性才生效。

·Player 中的 玩家 ID 是 MGOBE 后台生成的 ID,而 openId 是开发者初始化时候使用的 ID。需要注意,openId 只有初始化 Listener 的时候才使用,后续其它接口提到的“玩家 ID”均指后台生成的 ID,也就是 Player.id 属性,它不是 openId,切记!

·玩家进入房间后,Player 对象中的属性与 roomInfo.playerList 中的玩家信息是一致,通过两者任何一个都可以获得正确的玩家信息。

6.2.几个常用事件

·这里提两个经常用到的玩家事件:网络状态变化、玩家状态变化。

·在Mgobe中,玩家的网络状态分以下4种,但玩家的网络状态发生变化时均会触发。

room.onChangePlayerNetworkState = function(e)

{

if(e.data.networkState === MGOBE.ENUM.NetworkState.COMMON_OFFLINE)

{

console.log("房间中玩家掉线");

}

else if(e.data.networkState === MGOBE.ENUM.NetworkState.COMMON_ONLINE)

{

console.log("房间中玩家在线");

}

else if(e.data.networkState === MGOBE.ENUM.NetworkState.RELAY_OFFLINE)

{

console.log("帧同步中玩家掉线");

}

else if(e.data.networkState === MGOBE.ENUM.NetworkState.RELAY_ONLINE)

{

console.log("帧同步中玩家在线");

} //通过 e.data.changePlayerId 可以知道是哪个玩家的网络状态发生了变化

};

复制代码

·如果修改了玩家的自定义信息(由开发者自定义的,也即上文多次提到的 playerInfo 中的 customPlayerStatus),则以下事件会被触发:

room.onChangeCustomPlayerStatus = function()

{ //房间内 ID 为 changePlayerId 的玩家状态发生变化。玩家状态由开发者自定义。

console.log("玩家自定义状态变化=", e.data.changePlayerId); console.log("自定义数据=", e.data.customPlayerStatus);

};

复制代码

7.错误处理

·最后,如果在使用Mgobe的过程中如果发生客户端错误、系统逻辑错误、用户信息错误、房间错误、匹配错误、帧同步错误、参数错误、队伍团队错误时,均会发出错误码,可以通过以下文档查阅相关错误码对应的描述信息,以便排除和解决错误。

·错误码说明文档详见:https://cloud.tencent.com/document/product/1038/33317

四、结尾

· 本文仅从前端角度出发,介绍了利用 Mgobe 进行纯前端的帧同步开发,但 Mgobe 的功能远不止这些,Mgobe 也支持在后台编写自定义匹配逻辑来实现更加丰富的帧同步,感兴趣的同学可自行查阅官方文档。也可关注公众号,关注持续技术分享。

来源:腾讯游戏云

原文:https://cloud.tencent.com/developer/article/1747879

html5游戏联机教程,纯前端如何利用帧同步做一款联机游戏?相关推荐

  1. 如何利用状态同步开发一款联机游戏

    游戏状态同步 1.前言 目前市场上单机游戏占比高,因为相对联机游戏开发周期短.成本低,但联机游戏的社交属性强,玩家粘性高.总体来说,开发联机游戏有一定的技术门槛. 2.帧同步和状态同步 •     帧 ...

  2. < 纯前端实现「羊了个羊」小游戏 >

    纯前端实现「羊了个羊」小游戏

  3. 给我一分钟,教你在神奇代码岛做一款跑酷游戏

    目录 介绍 用模型作为存档点 准备 代码 思路 用方块作为存档点 准备 代码 思路 最后总结 介绍 做一款跑酷游戏,是很多学习编程的人一直都拥有的愿望,包括我在内,毕竟跑酷游戏本来就是一款最基础的游戏 ...

  4. html5生成excel,H5纯前端生成Excel表格

    H5纯前端生成Excel表格方法如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 var arr = [ 14 { 15 "姓名":"喵喵喵" ...

  5. 纯前端实现「羊了个羊」小游戏

    点击上方 前端Q,关注公众号 回复加群,加入前端Q技术交流群 作者:QCY https://juejin.cn/post/7143897892531486727 背景 最近简单的「羊了个羊」小游戏火到 ...

  6. 开源情报分析(OSINT)CTF社工类2万字题详细教程,请不要利用本文章做不道德的事,后果概不负责

    简介 现在国内外最新的ctf比赛都有这个项目了,列如给你一个照片找地址或者人名,给你一个名字找他的社交账号什么的,考验选手的信息收集与社工能力,这篇文章对这类题型做一个基础的总结,以后遇到这种题型就知 ...

  7. 微信小游戏设计心得(一)从0到开发一款小游戏教程-岩浆救援,对初学者来说有帮助

    2019年底,因为其他工作失意,我意外来到了,小游戏设计圈子,独立开发游戏,从什么都不会,到发开了一款真正的小游戏 网上很多教程,但是没有一个全面的,都是零零散散的东西,拼凑一起,需要很多时间,我决定 ...

  8. 做一款热门游戏----没有99美元的Impact也行

    Impact 简介 Impact is a JavaScript Game Engine that allows you to develop stunning HTML5 Games for des ...

  9. 用andengine做一款俄罗斯方块游戏

    andengine 入门 AndEngine初步 作者: Me 日期: 2010/12/16 发表评论 (1)查看评论 AndEngine 是一个Android平台下基于OpenGL ES的免费(LG ...

最新文章

  1. 数据分析工具Pandas(3):Pandas的对齐运算
  2. SAP PM 入门系列16 - KO88对维护工单做结算
  3. 新加坡南洋理工 计算机排名6,别踩坑了!这些才是新加坡最好的专业!
  4. 模p加法和模p乘法学习
  5. java nio长连接实现_kio: kio是基于jdk 1.6 nio实现的TCP长连接即时通讯框架。
  6. CSRF的绕过与利用
  7. (二)AS给button添加点击事件
  8. xp变量 java_winxp系统设置java环境变量的详细教程
  9. 操作系统之I/O管理:3、设备的分配与回收(设备控制表DCT、控制器控制表COCT、通道控制表CHCT、系统设备表SDT、逻辑设备表LUT)
  10. 141.4. 分布式安装(CentOS 6 + hadoop-1.1.2)
  11. LeetCode 338. 比特位计数(动态规划)
  12. VS2013——error C4996: 'std::_Uninitialized_copy0':
  13. 类文件Android 代码混淆 以及 反编译 的实现类文件
  14. windows下的gcc编译器
  15. 通过cid获取京东商品分类ID,京东商品分类API接口,京东商品分类详情接口,分类详情API接口,接口接入方案
  16. Intellij IDEA创建Scala项目
  17. uiautomatorviewer报错解决
  18. Java实现窗口框架,转换金额的大小写
  19. PHP判断ip是否在指定IP段内(微信支付回调IP段验证)
  20. TypeScript 中slice(-1)是什么意思?

热门文章

  1. 乐山持点电商:抖音商家群聊群直播通知使用指南
  2. 达梦数据库错误代码[-524]:超出全局hash join空间
  3. 苹果IOS系统各个版本占有率官方统计
  4. 安卓系统新手入门教程
  5. 尚融宝30(终)-资金记录和个人中心展示
  6. C#基础知识回顾(第四部分 共四部分)
  7. 百无聊赖之JavaEE从入门到放弃(十二)数组
  8. 仿360手机卫士首页[android平台]
  9. 31 Circle类
  10. 分销主机成为在家工作的完美商业模式的五个原因