MGOBE(Mini Game Online Battle Engine)
注:文章会把实际使用中需要的细节做说明(以帧同步为例)
一、为何使用他
由于io游戏的流行,大部分开发io游戏的团队都需要实现一套帧同步的服务端和客户端的逻辑,开发成本较大,而且由于是实时对战,对网络要求也高,腾讯云对这方面有针对性的优化,可以满足国内绝大多数网络下的玩家。对于客户端程序来讲,可以无需搭建这部分服务器内容,而且不用考虑网络,并发等情况,即可搭建起一个帧同步的游戏。协议使用的是pb,通信支持tcp,udp,但是小游戏平台暂时只有微信支持udp
他有一套完整的后台管理和各模块的设置,此处需要去后台详细的阅读使用说明
二、MGOBE的基础概念
房间:整套框架均是以房间(Room)作为基础单位,进行各协议的通信,房间的
玩家:用户
匹配:
1.按房间条件匹配,如:1v1房间,低中高等级房间,游戏状态(未开始游戏,游戏中),加入指定房间(邀请码)
2.单个玩家快速匹配
3.组队玩家匹配,如:微信小程序,先邀请几个好友,去跟别人玩多人运动
数据同步:
1.帧同步,适用于IO类型游戏
2.状态同步,适用于棋牌,回合类游戏
3.实时服务器,这个比较好,可以在服务端加入自己的服务器代码,这个我还没去看,如果可以跟自己的云服务器通信,那就太完美了。官方写的是:目前提供的功能是 云数据库,云存储,云函数
大概基础内容简单概括就这些,以上模块了解完,就可以搭建一个完整的IO游戏的对战模块了。不过这套引擎的模块设置内容是非常多的,为了满足不同的需求,具体可以看官方文档-Room
三、实际使用
先简单说下我遇到的坑:
上面说到的快速匹配(2.单个玩家快速匹配),发现个问题,这个模式匹配他自己内部处理的流程如下:
1.开始匹配(这里需要在后台设置匹配规则,比如 2v2)
2.等待有4个人在匹配的时候,大家统一进入到房间
到这里似乎没有问题,但是接下来如果有一个人退出了房间,那在房间外面的人是无法通过快速匹配进入到此房间,也就是说这个房间会一直只有3个人,除非其他人去按房间查找才能加入到这里。
这里不知道是不是我哪里设置的问题,导致别人匹配不到这个房间。或者是房间的一些设置问题,由于配置太多,就没有去挨个实验。这里比较适合游戏分级和内容设置较多的游戏。
所以后面我统一用房间匹配的模式进行匹配,可以满足需求。
四、Unity-示例
先按照官方文档,将sdk集成到项目中。
开始我自己例子流程(文章结尾有git地址):
1.初始化
一切的基础
2.开始匹配房间
这里引擎的处理流程是先找满足条件的房间,如果不存在则新创建一个房间然后加入进去,等待其他玩家
3.同步随机数种子
这里将种子下发给各个玩家,初始化随机种子用,为了达到后续客户端的随机结果一致
4.游戏开始,开始帧同步
一个客户端调用开始帧同步,其他客户端会收到开始标识,可用作游戏开始的触发条件
5.发送同步帧消息
这里我是自己定义了一个类,用作存储需要同步的帧类型
五、没有提到的事情
由于帧同步在不同游戏下的实现有着极大的不同,简单的涉及内容太少,复杂的又太耗时间,而且针对性太强,所以没有做对应实战的项目例子,这里简单提下实际开发中可能会遇到的问题:
六、代码
要替换你自己的 GameID,OpenID
Gitee地址
如果不想下载工程,可以直接看代码,复制到你自己的unity工程就可以
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using com.unity.mgobe.src;
using com.unity.mgobe.src.SDK;
using com.unity.mgobe;
using System;
using TMPro;
using UnityEngine.UI;
using com.unity.mgobe.src.EventUploader;
public class main : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
//mgobe
public Room room;
public void StartSDK()
{
AppendTxt("开始初始化\r\n");
GameInfoPara gameInfo = new GameInfoPara
{
// 替换 为控制台上的“游戏ID”
GameId = "",
// 玩家 openId
OpenId = UnityEngine.Random.Range(1, 10000).ToString(),
//替换 为控制台上的“游戏Key”
SecretKey = ""
};
ConfigPara config = new ConfigPara
{
// 替换 为控制台上的“域名”
Url = "dvjpnhh6.wxlagame.com",
ReconnectMaxTimes = 5,
ReconnectInterval = 1000,
ResendInterval = 1000,
ResendTimeout = 10000
};
// 初始化监听器 Listener
Listener.Init(gameInfo, config, (ResponseEvent eve) =>
{
if (eve.Code == 0)
{
AppendTxt("初始化成功 \r\n");
Btn_CreateRoom();
}
else
{
AppendTxt("初始化失败\r\n");
}
});
}
public void Btn_CreateRoom()
{
room = new Room(null);
Listener.Add(room);
room.OnJoinRoom = onJoinIn;
// 广播:房间有玩家退出
room.OnLeaveRoom = onLeaveRoom;
// 广播:房间被解散
room.OnDismissRoom = onDismis;
// update
room.onUpdate = onRoomUpdate;
// recv msg
room.OnRecvFromClient = eve => AppendTxt("同步随机数种子:" +((RecvFromClientBst)eve.Data).Msg + "\r\n");
// start step
room.OnStartFrameSync = eve => AppendTxt("收到开始帧同步!\r\n");
room.OnStopFrameSync = eve => AppendTxt("收到停止帧同步!\r\n");
room.OnRecvFrame = recvFrameStep;
// match
Room.OnMatch = onMatch;
}
private void onJoinIn(BroadcastEvent eve)
{
var data = (JoinRoomBst)eve.Data;
AppendTxt("新玩家加入" + data.JoinPlayerId + "\r\n");
foreach (PlayerInfo player in room.RoomInfo.PlayerList)
{
AppendTxt("当前玩家:" + player.Name + "\r\n");
}
}
private void onLeaveRoom(BroadcastEvent eve)
{
var data = (LeaveRoomBst)eve.Data;
AppendTxt("玩家退出" + data.LeavePlayerId + "\r\n");
}
private void onDismis(BroadcastEvent eve)
{
var data = (JoinRoomBst)eve.Data;
AppendTxt("房间被解散" + "\r\n");
}
private void onRoomUpdate(Room r)
{
AppendTxt("房间更新:" + r.RoomInfo.Id + "|" + r.RoomInfo.PlayerList.Count + "\r\n");
}
void onMatch(BroadcastEvent eve)
{
var data = (MatchBst)eve.Data;
if (data.ErrCode == 0)
{
AppendTxt("onMatch匹配成功\r\n");
}
else
{
AppendTxt("onMatch匹配失败\r\n");
}
}
public void Btn_LeaveRoom()//离开房间
{
room.LeaveRoom(eve =>
{
if (eve.Code == 0)
{
AppendTxt("离开房间" + "\r\n");
}
else
{
AppendTxt("离开失败" + "\r\n");
}
});
}
public void Btn_DismisRoom() //解散房间
{
room.DismissRoom(eve =>
{
Debug.Log(eve.Code);
if (eve.Code == 0)
{
AppendTxt("解散成功" + "\r\n");
}
});
}
public void Btn_MatchRoom()
{
AppendTxt("开始匹配房间\r\n");
MatchRoomPara para = new MatchRoomPara()
{
MaxPlayers = 4,
PlayerInfo = new PlayerInfoPara() { Name = "p" + UnityEngine.Random.Range(1, 100), CustomPlayerStatus = 1, CustomProfile = "" },
RoomType = "1",
};
room.MatchRoom(para, eve =>
{
if (eve.Code != 0)
{
AppendTxt("room匹配失败\r\n");
}
else
{
AppendTxt("room匹配成功...\r\n");
Debug.Log(eve.Data);
}
});
}
public void Btn_MatchPlayer()
{
AppendTxt("开始匹配玩家\r\n");
MatchPlayerInfoPara playerInfo = new MatchPlayerInfoPara()
{
CustomPlayerStatus = 1,
Name = "p" + UnityEngine.Random.Range(1, 100),
MatchAttributes = new List() {
new MatchAttribute(){ Name = "1", Value = 2}
},
};
MatchPlayersPara matchPlayerPara = new MatchPlayersPara()
{
//MatchCode = "match-0bjyargr", //2v2
MatchCode = "match-82hzbk07", // 1v1
PlayerInfoPara = playerInfo,
};
room.MatchPlayers(matchPlayerPara, eve =>
{
if (eve.Code != 0)
{
AppendTxt("player匹配失败" + "\r\n");
}
else
{
AppendTxt("player匹配中..." + "\r\n");
}
});
}
public void Btn_SendMsg()
{
SendToClientPara stcp = new SendToClientPara()
{
RecvType = RecvType.RoomAll,
Msg = "123",
RecvPlayerList = new List(),
};
room.SendToClient(stcp, eve =>
{
});
}
public void Btn_StartStep()
{
room.StartFrameSync(eve => {
if (eve.Code == 0)
{
AppendTxt("开启帧同步成功!\r\n");
}
});
}
public void Btn_StopStep()
{
room.StopFrameSync(eve => {
if (eve.Code == 0)
{
AppendTxt("结束帧同步成功!\r\n");
}
});
}
[Serializable]
internal class XXFrameData
{
[SerializeField]
public string strData;
[SerializeField]
public int intData;
override public string ToString()
{
return strData + "&" + intData;
}
public static XXFrameData init(string data)
{
XXFrameData xfd = new XXFrameData();
xfd.strData = data.Split('&')[0];
xfd.intData = int.Parse(data.Split('&')[1]);
return xfd;
}
}
public void Btn_SendStep()
{
string strTime = DateTime.Now.ToLongTimeString();
int intTime = (int)DateTime.Now.Second;
XXFrameData fd = new XXFrameData();
fd.strData = strTime;
fd.intData = intTime;
SendFramePara para = new SendFramePara()
{
Data = fd.ToString(),
};
room.SendFrame(para, eve =>
{
if (eve.Code == 0)
AppendTxt("发送帧同步成功\r\n");
else
AppendTxt("发送帧同步失败\r\n");
});
}
public void recvFrameStep(BroadcastEvent eve)
{
RecvFrameBst bst = ((RecvFrameBst)eve.Data);
if (bst.Frame.Items.Count != 0)
AppendTxt("----收到帧消息:" + bst.Frame.Id + "\r\n");
foreach (FrameItem fi in bst.Frame.Items)
{
//AppendTxt(fi.PlayerId + "|" + ((XXFrameData)fi.Data).strData + "\r\n");
XXFrameData xfd = XXFrameData.init(fi.Data);
AppendTxt(fi.PlayerId + "|" + xfd.ToString() + "\r\n");
}
}
public GameObject tmp;
public List strlist = new List();
public void AppendTxt(string txt)
{
strlist.Add(txt);
}
public void CleanTxt()
{
tmp.GetComponent().text = "";
}
private void LateUpdate()
{
if (strlist.Count > 0)
{
tmp.GetComponent().text += strlist[0];
strlist.RemoveAt(0);
}
}
}