从2010年至2013年开始,在Android系统以及iOS系统的加持下,手游产业开始迎来了爆发期,各式各样的手游开始出现。2015年后,随着移动游戏的爆发增长,手机联机对战游戏也开始异军突起,此后手机电竞开始越来越白热化,语音也跟移动游戏的结合更紧密,满足游戏的多种交流需求。
1/4 为何使用GME游戏语音?
使用GME游戏语音,有以下三个理由:
流畅
游戏语音1.0时代,行业大佬李学凌就喊出了“不卡不掉不延迟”这一语音体验经典总结。游戏语音2.0时代,语音是以插件形式内嵌至移动游戏中,就会涉及至API交互,游戏开发者比较关注GameClient与语音的交互是否顺畅,因此“不卡”除了语音本身不卡(网络抗性决定),还包括了语音与游戏的交互不卡。GME实时语音结合应用场景的深度优化,普通音质语音时延低至200ms,50%以上丢包、1000ms网络抖动下仍能顺畅沟通。
清晰
体验是挂在产品经理嘴边的口头禅,语音清晰性又是游戏语音体验最重要的指标,语音场景体验包括两个方向,一是声音的清晰,二是声音的流畅。手游场景,玩家随时随地可能拉起开黑,戴耳机的比例较低(约15%),而手游场景语音的同时大家又还要听游戏背景音效(脚步声可以说是“活命线索”),而手机扬声器离麦克风距离是厘米级,背景音对人声干扰很大,很容易产生噪声,背景音透传,是影响语音清晰度的最大的因素。不流畅主要是网络丢包,抖动导致。GME 高清音质利用回声消除、环境音消除、噪声抑制、啸叫抑制专利算法还原清晰听感。
游戏开发适配
现在的手机游戏,国内开发团队用的很多都是Unity引擎,但越来越多的对画质更加追求的工作室开始使用更加复杂的游戏引擎,例如Unreal。甚至现在的游戏厂商会多平台发布游戏,比如堡垒之夜,移动端和主机端均发布了游戏。 我们GME提供主机端 SDK,支持 PS4、Xbox、Switch 游戏集成,与 Wwise 音频引擎深度融合,独创性地解决了开语音时游戏背景音效丢失的问题。针对主流游戏引擎 Unity、Cocos、UE 深度优化,跨平台支持 MacOS、Windows、iOS、Android 系统。依托 QQ 亿级用户,适配全部 iOS 设备和 2000 Android 终端设备,保证用户体验一致,节省开发时间。
2/4 接入GME语音
在此演示中,我们将结合MGOBE演示Demo实现一个可以运行的游戏中接入游戏语音GME。
本文使用的环境:
GME SDK 版本为 2.7.0;【点击到达2.7.0发布公告】
MGOBE SDK版本为 v1.2.8;
Unity 版本为 2019.4.7f1。
另外我们在 Unity 的官方网站上下载到MGOBE的DEMO,我们会将 GME 游戏语音集成在这个 DEMO 里面。
1、下载SDK
在游戏多媒体引擎官网【跳转后点击原文链接】可以下载到最新版的 SDK, 目前我们只需要下载 Unity 版本的 SDK 即可。
2、导入SDK
将下载好的 SDK 文件解压后,拷贝到 Unity 工程中,删除 Plugin 中的平台文件夹,只保留 Android、gmesdk.bundle 以及 x86_64。详细参考游戏多媒体引擎Unity工程配置。
3、接入SDK
3.1 初始化SDK
就像其他联机对战游戏一样,一开始会有一个登录界面,这里会分配给每一个用户一个独立的标识码。我们在界面上随机一个大于 10000 小于 20000 的数字。因为如果要初始化 GME SDK,需要这个数字(数值大于 10000 的 int64 位数字转 string)作为参数 OpenId 传入接口 Init,接口的调用及参数类型请参考Unity接入文档-Init接口。
当点击登录之后,我们在按钮的响应事件中进行初始化的操作,即在【登录】按钮事件中调用以下代码:
代码语言:javascript复制//获取面板上的数字作为参数
GME_OpenId = loginPanel.myOpenId.text;
Debug.Log("GME_OpenId" GME_OpenId);
int ret = ITMGContext.GetInstance().Init("1400089356", GME_OpenId);
//通过返回值判断是否初始化成功
if (ret != QAVError.OK)
{
Debug.Log("SDK初始化失败:" ret);
return;
}
接下来在 Unity Update 方法中我们添加 GME 的 Poll 接口。
3.2 进入房间
点击【登录】按钮之后,会出现一个游戏大厅的界面。
在这个界面上有两个按钮,一个是【自动匹配】,另一个是【创建房间】,如果已经有房间的话,界面上还会显示房间列表。
我们的逻辑是,在进入联机对战房间的时候,同时也进入语音房间。先在 Init SDK 成功后,监听进入语音房间事件的回调以及退出语音房间事件的回调。
代码语言:javascript复制ITMGContext.GetInstance().OnEnterRoomCompleteEvent = new QAVEnterRoomComplete(OnEnterRoomComplete);
ITMGContext.GetInstance().OnExitRoomCompleteEvent = new QAVExitRoomComplete(OnExitRoomComplete);
然后我们继续写一个 GME 实时语音进房方法EnterRoom:
代码语言:javascript复制public void EnterVoiceRoom() {
byte[] byteAuthbuffer;
GME_RoomId = Global.Room.RoomInfo.Id;
if (GME_OpenId != null)
{
byteAuthbuffer = GetAuthBuffer("1400089356", GME_RoomId, GME_OpenId, "请从GME控制台获取");
ITMGContext.GetInstance().EnterRoom(GME_RoomId, ITMGRoomType.ITMG_ROOM_TYPE_FLUENCY, byteAuthbuffer);
}
}
其中进房的时候需要房间号。使用联机对战引擎需要实例化 Room 对象,Room 对象会自动维护内部的 roomInfo 属性保持最新,我们可以直接通过访问该属性获得最新的房间信息。即通过 Global.Room.RoomInfo.Id 获取房间 Id,这个 Id 作为进语音房间的房间号。
房间类型由于我们现在是联机游戏,所以选择适用于游戏的流畅音质。
最后我们再写 GME 进房回调事件的处理。
代码语言:javascript复制void OnEnterRoomComplete(int err, string errInfo)
{
if (err != 0)
{
Debug.Log("错误码:" err " 错误信息:" errInfo);
return;
}
else
{
//进房成功
Debug.Log("进房成功");
isMicOpen = false;
isSpeakerOpen = false;
}
}
3.2.1 创建对战房间
我们先从创建房间说起,创建房间会有一个创建房间的界面。
找到【创建房间】按钮,可以找到绑定的事件为 Client.CreateRoom,里面可以看到创建联机的相关代码,如果联机对战的 CreateTeamRoom 接口返回成功,即成功创建了联机房间,我们便同时进入语音房间:
代码语言:javascript复制Global.Room.CreateTeamRoom
Global.Room.CreateTeamRoom (para, eve => {
if (eve.Code == 0) {
RefreshRoomList ();
//如果联机对战的 CreateTeamRoom 接口返回成功,即成功创建了联机房间,我们便同时进入语音房间。
AddAction (() => { onRoomInPanel ();
EnterVoiceRoom(); });
} else {
Debug.Log ("create Team Room Fail: {0}" eve.Code);
AddAction (() => { HideRoomPanel (); });
}
});
3.2.2 自动匹配
联机对战更多的是使用自动匹配去让玩家进行组队联机对战,联机对战的匹配接口为 matchPlayers,匹配结果将在 callback 中异步返回。操作成功后,Room 对象内部 roomInfo 属性将更新。如果匹配成功,那么我们就根据匹配的房间名字进入相应的语音房间。房间匹配的代码如下,我们在匹配成功之后调用进入 GME 语音房间的方法:
代码语言:javascript复制public void MatchRoom () {
CreateWaitForMatchRoomPanel ();
MatchRoomPara para = new MatchRoomPara {
RoomType = "Battle",
MaxPlayers = 2,
PlayerInfo = this.playerInfoPara
};
// 进行房间匹配
Global.Room.MatchRoom (para, eve => {
if (eve.Code != 0) {
RefreshRoomList ();
AddAction (() => { onRoomInPanel (); });
} else {
RefreshRoomList ();
//我们在匹配成功之后调用进入 GME 语音房间的方法
AddAction (() => { onRoomInPanel ();
EnterVoiceRoom();
});
}
});
}
【注意】不能在异步回调中调用 GME 相关接口,必须保证所有接口都在主线程调用。
3.2.3 加入房间
如果第一个用户以及创建了房间,那么第二个用户没进房的时候,可以在面板看到第一个用户创建的房间。
4、打开麦克风扬声器
需要在进房成功之后才能打开麦克风及扬声器。所以我们在进入匹配房间后的界面新增设备操作按钮。
加入房间之后的界面如下,我们添加两个按钮,一个用来开启麦克风,一个用来开启扬声器:
接下来我们为麦克风按钮写相应事件,通过一个 bool 变量来表示是否开关麦克风,当进入房间的时候我们把这个变量设为 false,因为进入语音房间默认不打开麦克风及扬声器。
代码语言:javascript复制public void OnMicButton() {
ITMGContext.GetInstance().GetAudioCtrl().EnableMic(!isMicOpen);
if (!isMicOpen)
{
GME_MicButton.GetComponentInChildren<Text>().text = "麦克风开";
}
else {
GME_MicButton.GetComponentInChildren<Text>().text = "麦克风关";
}
isMicOpen = !isMicOpen;
}
5、退出房间
当点击界面上的后退按钮的时候,会退出对战房间,此时我们也相应的退出语音房间。
退出语音房间需要调用 ExitRoom 接口,同时需要监听退房回调。
代码语言:javascript复制public void LeaveRoom () {
// 离开房间
Global.Room.LeaveRoom (eve => {
RefreshRoomList ();
isReadyToBattle = false;
});
ITMGContext.GetInstance().ExitRoom();
}
退出房间完成后,我们把 UI 进行更新,回到游戏大厅界面。
代码语言:javascript复制void OnExitRoomComplete() {
roomInPanel.gameObject.SetActive(false);
canRefreshPlayer = false;
canRefreshRoom = true;
roomsPanel.gameObject.SetActive(true);
waitForMatchRoomPanel.isActive = true;
roomInPanel.setReadyBtn.interactable = true;
roomInPanel.setReadyBtn.gameObject.SetActive(true);
roomInPanel.cancelReadyBtn.gameObject.SetActive(false);
isMicOpen = false;
isSpeakerOpen = false;
}
6、反初始化
确定退出的时候,需要反初始化 SDK。
代码语言:javascript复制private void OnDestroy () {
Global.UnInit ();
ITMGContext.GetInstance().OnEnterRoomCompleteEvent -= new QAVEnterRoomComplete(OnEnterRoomComplete);
ITMGContext.GetInstance().OnExitRoomCompleteEvent -= new QAVExitRoomComplete(OnExitRoomComplete);
ITMGContext.GetInstance().Uninit();
}
3/4 整体流程
回顾一下上文的整体流程,大致如下:
4/4 导出测试
选择 File-Build Setting,选择【Build】按钮,导出 windows 系统可执行文件。
导出之后双击【NetSdkDev.exe】可执行文件便可进入游戏进行测试。