我们实际使用GME SDK完成相关的开发,一起来看下代码是如何运行的。本篇是基于Google开源的CardBoard SDK进行的示例程序。
GoogleVR SDK 官网点击下载SDK,我们演示使用的版本为GVR SDK for Unity v1.200.1。下载完成之后,新建一个 Unity 工程,我们演示使用的版本为 Unity 2018.4.23f1。双击 GoogleVRForUnity_1.200.1.unitypackage 导入SDK。
点击下载指引,找到 【SDK v2.7.1 版本】,点击下载 Unity SDK。解压后将文件拷贝到Unity工程中,删除 Plugin 中的平台文件夹,只保留 Android、gmesdk.bundle以及x86_64。详细参考游戏多媒体引擎Unity工程配置。
找到 GoogleVR->Demos->Scenes 下的HelloVR场景,双击打开。
在PlayerSettings中的XRSettings一栏,勾选Virtual Reality Supported,再添加 Cardboard 组件。
在 Prefernce 中的 External Tools下设置好 Android SDK 的路径及 Android NDK 的路径。
将场景HelloVR添加到 Scene In Build,将 Platform 切换到 Android,设置好导出时候的 Package Name,便可以导出验证。
游戏多媒体引擎Unity接入文档首先创建一个代码文件,名字为 GMEVoice,在工程中新建一个空物体,将代码挂载在空物体上。
双击代码文件 GMEVoice,在代码中引入GME。
using TencentMobileGaming;
初始化GME SDK需要 Appid,请在腾讯云游戏多媒体引擎控制台申请,此处我们使用官方提供的测试 Appid。另外还需要自定义一个OpenId,数值大于10000,我们使用随机数来产生。
int int_OpenId = UnityEngine.Random.Range(12345, 22345);
string str_OpenId = int_OpenId.ToString();
int isInit = ITMGContext.GetInstance().Init("1400089356", str_OpenId);
在 Update 中我们调用 Poll 函数。
void Update()
{
ITMGContext.GetInstance().Poll();
}
进入语音房间需要鉴权,鉴权需要的AuthKey在腾讯云游戏多媒体引擎控制台上获取,与AppID对应。鉴权的具体参数及获取方法参考游戏多媒体引擎Unity接入文档鉴权部分。此处我们使用官方给的测试账号的AppID、AuthKey,进入的房间为 20200601,OpenId为随机出来的数字转string类型。
byte[] authBuffer = GetAuthBuffer("1400089356", "20200601", str_OpenId, "1cfbfd2a1a03a53e");
public static byte[] GetAuthBuffer(string AppID, string RoomID, string OpenId, string AuthKey)
{
return QAVAuthBuffer.GenAuthBuffer(int.Parse(AppID), RoomID, OpenId, AuthKey);
}
接下来我们对进房事件进行监听,然后在 Unity 的Start 中调用进房函数。
ITMGContext.GetInstance().OnEnterRoomCompleteEvent += new QAVEnterRoomComplete(OnEnterRoomComplete);
ITMGContext.GetInstance().EnterRoom("20200601",ITMGRoomType.ITMG_ROOM_TYPE_FLUENCY, authBuffer);
调用进房接口之后,需要监听回调并在回调中处理进房结果。如果进房成功便打开麦克风及扬声器。
void OnEnterRoomComplete(int err, string errInfo)
{
if (err != 0)
{
Debug.Log("进房失败,错误码为:" + err);
return;
}
else
{
//进房成功
//打开麦克风
ITMGContext.GetInstance().GetAudioCtrl().EnableMic(true);
//打开扬声器
ITMGContext.GetInstance().GetAudioCtrl().EnableSpeaker(true);
}
}
我们需要在销毁代码的时候反初始化整个SDK。
void OnDestroy()
{
ITMGContext.GetInstance().Uninit();
}
这时候我们可以先尝试一下导出两个windows可执行文件,验证是否进房成功,是否能在进入游戏后就打开麦克风及扬声器进行实时语音对话。
如果以上步骤完成后,能够进入游戏后进行实时语音通话,那么我们接下来开始接入3D音效效果。游戏多媒体引擎3D音效文档
点击下载地址下载音效文件,此文件为官方提供。我们把这个模型文件命名为3d_model,并放在Unity工程目录StreamingAssets下,确保能随可执行文件一同导出。我们写一个协程,用来将这个音效文件拷贝到Application.persistentDataPath下,方便引用。
StartCoroutine(copyFileFromAssetsToPersistent("3d_model"));
public IEnumerator copyFileFromAssetsToPersistent(string fileName)
{
string fromPath = Application.streamingAssetsPath + "/" + fileName;
string toPath = Application.persistentDataPath + "/" + fileName;
if (!File.Exists(toPath))
{
Debug.Log("copying from " + fromPath + " to " + toPath);
#if UNITY_ANDROID && !UNITY_EDITOR
WWW www1 = new WWW(fromPath);
yield return www1;
File.WriteAllBytes(toPath, www1.bytes);
Debug.Log("file copy done");
www1.Dispose();
www1 = null;
#else
File.WriteAllBytes(toPath, File.ReadAllBytes(fromPath));
#endif
}
yield return null;
}
我们在进房成功的回调中,初始化3D音效,路径为我们协程中写的路径。
string filePath = Application.persistentDataPath + "/3d_model";
int ret = QAVError.OK;
ret = ITMGContext.GetInstance().GetAudioCtrl().InitSpatializer(filePath);
使用接口EnableSpatializer开启3D音效,在这里我们进房成功后,初始化3D音效成功后就启动3D音效。第二个参数与范围语音有关,此处不需关注。
ITMGContext.GetInstance().GetAudioCtrl().EnableSpatializer(true, false);
设置语音的接收范围,此处的范围参数涉及到衰减,单位为Unity中的距离单位,为了突出效果,此处我们设置为100000。
ITMGContext.GetInstance().GetRoom().UpdateAudioRecvRange(100000);
通过更新坐标到服务器,游戏多媒体引擎服务器会根据房间内成员的坐标将声音进行3D处理,为了保证3D音效的实时性,需要在Update函数中每帧更新自身坐标。
在此Demo中,由于我们的代码挂载在另一个空物体上,所以我们需要将摄像机的位置实时传到接口中,我们声明一个GameObject,用于传递Demo中Player的坐标。
public GameObject currentPlayer;
在Unity编辑器中,我们将Player附给currentPlayer。
此处为了使3D效果明显,我们更新坐标时,将position都乘以一个100的系数进行放大。
void Update()
{
ITMGContext.GetInstance().Poll();
if (isRoomEntered)
{
Transform selftrans = currentPlayer.gameObject.transform;
Matrix4x4 matrix = Matrix4x4.TRS(Vector3.zero, selftrans.rotation, Vector3.one);
int[] position = new int[3] {
(int)selftrans.position.z*100,
(int)selftrans.position.x*100,
(int)selftrans.position.y*100
};
float[] axisForward = new float[3] { matrix.m22, matrix.m02, matrix.m12 };
float[] axisRight = new float[3] { matrix.m20, matrix.m00, matrix.m10 };
float[] axisUp = new float[3] { matrix.m21, matrix.m01, matrix.m11 };
ITMGContext.GetInstance().GetRoom().UpdateSelfPosition(position,
axisForward,
axisRight,
axisUp);
}
}
最终代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using TencentMobileGaming;
public class GMEVoice : MonoBehaviour
{
// 用于传递摄像机位置
public GameObject currentPlayer;
// 用于判断进房状态
private bool isRoomEntered = false;
// Start is called before the first frame update
void Start()
{
// 初始化
int int_OpenId = UnityEngine.Random.Range(12345, 22345);
string str_OpenId = int_OpenId.ToString();
int isInit = ITMGContext.GetInstance().Init("1400089356", str_OpenId);
// 拷贝3D音效文件
StartCoroutine(copyFileFromAssetsToPersistent("3d_model"));
// 进房
byte[] authBuffer = GetAuthBuffer("1400089356", "20200601",
str_OpenId, "1cfbfd2a1a03a53e");
if (isInit == 0) {
ITMGContext.GetInstance().OnEnterRoomCompleteEvent +=
new QAVEnterRoomComplete(OnEnterRoomComplete);
ITMGContext.GetInstance().EnterRoom("20200601", ITMGRoomType.ITMG_ROOM_TYPE_FLUENCY, authBuffer);
}
}
// Update is called once per frame
void Update()
{
ITMGContext.GetInstance().Poll();
// 如果进房成功,开始更新自身位置
if (isRoomEntered)
{
Transform selftrans = currentPlayer.gameObject.transform;
Matrix4x4 matrix = Matrix4x4.TRS(Vector3.zero, selftrans.rotation, Vector3.one);
int[] position = new int[3] {
(int)selftrans.position.z*100,
(int)selftrans.position.x*100,
(int)selftrans.position.y*100 };
float[] axisForward = new float[3] { matrix.m22, matrix.m02, matrix.m12 };
float[] axisRight = new float[3] { matrix.m20, matrix.m00, matrix.m10 };
float[] axisUp = new float[3] { matrix.m21, matrix.m01, matrix.m11 };
ITMGContext.GetInstance().GetRoom().UpdateSelfPosition(position,
axisForward,
axisRight,
axisUp);
}
}
/// <summary>
/// 鉴权
/// </summary>
/// <param name="AppID"></param>
/// <param name="RoomID"></param>
/// <param name="OpenId"></param>
/// <param name="AuthKey"></param>
/// <returns></returns>
public static byte[] GetAuthBuffer(string AppID, string RoomID,
string OpenId, string AuthKey)
{
return QAVAuthBuffer.GenAuthBuffer(int.Parse(AppID), RoomID, OpenId, AuthKey);
}
/// <summary>
/// 进房成功回调处理
/// </summary>
/// <param name="err"></param>
/// <param name="errInfo"></param>
void OnEnterRoomComplete(int err, string errInfo)
{
if (err != 0)
{
Debug.Log("进房失败,错误码为:" + err);
return;
}
else
{
isRoomEntered = true;
//进房成功
//打开麦克风
ITMGContext.GetInstance().GetAudioCtrl().EnableMic(true);
//打开扬声器
ITMGContext.GetInstance().GetAudioCtrl().EnableSpeaker(true);
// 初始化3D音效
string filePath = Application.persistentDataPath + "/3d_model";
Debug.Log("3D音效文件路径:"+ filePath);
int ret_Init = ITMGContext.GetInstance().GetAudioCtrl().InitSpatializer(filePath);
Debug.Log("初始化3D音效是否成功:"+ ret_Init);
int ret_Enable = ITMGContext.GetInstance().GetAudioCtrl().EnableSpatializer(true,
false);
Debug.Log("开启3D音效是否成功:" + ret_Enable);
if (ret_Enable != QAVError.OK)
{
return;
}
ITMGContext.GetInstance().GetRoom().UpdateAudioRecvRange(100000);
}
}
/// <summary>
/// 反初始化
/// </summary>
void OnDestroy()
{
ITMGContext.GetInstance().Uninit();
}
/// <summary>
/// 用于从streamingAssets拷贝文件
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public IEnumerator copyFileFromAssetsToPersistent(string fileName)
{
string fromPath = Application.streamingAssetsPath + "/" + fileName;
string toPath = Application.persistentDataPath + "/" + fileName;
if (!File.Exists(toPath))
{
Debug.Log("copying from " + fromPath + " to " + toPath);
#if UNITY_ANDROID && !UNITY_EDITOR
WWW www1 = new WWW(fromPath);
yield return www1;
File.WriteAllBytes(toPath, www1.bytes);
Debug.Log("file copy done");
www1.Dispose();
www1 = null;
#else
File.WriteAllBytes(toPath, File.ReadAllBytes(fromPath));
#endif
}
yield return null;
}
}
我们将导出Android可执行文件apk文件进行验证。我们将Player的坐标x设置为3,导出一个apk,之后调整x的数值为-3,再导出一个apk,这样两个player的位置便是不同的。
进入VR游戏后,我们可以听到3D效果的实时语音。
技术创作101训练营
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。