ios开发 多人语音聊天_在 Unity 多人游戏中实现语音对话
我們曾經(jīng)不止一次為大家分享過游戲中的實(shí)時音視頻,例如怎么實(shí)現(xiàn)游戲中的聽聲辨位、狼人殺游戲中的語音聊天挑戰(zhàn)等。基本上,都是從技術(shù)原理和 Agora SDK 出發(fā)來分享的。這次我們換一個角度。我們將從 Unity 開發(fā)者的角度分享一下,在 Unity 中如何給自己的多人在線游戲增加實(shí)時語音通話功能。
我們在這里利用了 Unity 上流行的 “Tanks!!! asset reference” 坦克游戲作為多人在線游戲作為基礎(chǔ),相信很多人都不會陌生。大家可以在 Unity Asset Store 中搜到它。然后,我們會利用 Unity Asset Store 中的 Agora Voice SDK 為它增加多人語音聊天功能。
在開始前,你需要做以下準(zhǔn)備:
安裝 Unity 并注冊 Unity 賬號
了解如果在 Unity 中創(chuàng)建 iOS、Android 項(xiàng)目
一款跨移動平臺多玩家的 Unity 游戲(本文中我們選擇的是 Tanks)
了解 C# 和 Unity 腳本
注冊一個 Agora 開發(fā)者賬戶
至少兩個移動設(shè)備(如果有一個 iOS 設(shè)備,一個 Android 設(shè)備就再理想不過了)
安裝 Xcode
新建 Unity 項(xiàng)目
我們默認(rèn)大家都是用過 Unity 的開發(fā)者,但是為了照顧更多的人。我們還是要從頭講起。當(dāng)然,開始的操作步驟很簡單,所以我們會盡量以圖片來說明。
首先,打開 Unity 后,讓我們先創(chuàng)建一個新的項(xiàng)目。
如果你之前已經(jīng)下載過 Tanks!!! ,那么我們點(diǎn)擊頁面旁邊的“Add Asset Package”按鈕,選擇添加它即可。
如果你還未下載過 Tanks!!! 那么可以在 Unity Store 中下載它。
在將 Tanks!!! 參考項(xiàng)目部署到手機(jī)之前,還有幾步需要做。首先,我們需要在 Unity Dashboard 中,為這個項(xiàng)目開啟 Unity Live Mode。該設(shè)置的路徑是:project → Multiplayer → Unet Config。盡管 Tanks!!! 只支持最多四個玩家4,但我們在將“Max Player per room”設(shè)置為6。
圖:這個界面說明 Unity Live Mode 已經(jīng)開啟
Building for iOS
現(xiàn)在我們已經(jīng)準(zhǔn)備好來創(chuàng)建 iOS 版本了。打開 Build Setting,將系統(tǒng)平臺切換到 iOS,然后 Build。在切換系統(tǒng)平臺后,請記得更新 Bundle Identifier(如下圖所示)。
圖:創(chuàng)建了一個“Build”文件夾用于儲存 iOS 項(xiàng)目
圖:Build 完成
讓我們打開 Unity-iPhone.xcodeproj,sign 并讓它在測試設(shè)備上運(yùn)行。
現(xiàn)在我們已經(jīng)完成了 iOS 項(xiàng)目的創(chuàng)建。接下來我們要創(chuàng)建 Android 項(xiàng)目了。
Building for Android
Android 項(xiàng)目相比 iOS 來講要更簡單一些。因?yàn)?Unity 可以直接創(chuàng)建、sign 和部署運(yùn)行,無需借助 Android Studio。我默認(rèn)大家已經(jīng)將 Unity 與 Android SDK 文件夾關(guān)聯(lián)起來了。現(xiàn)在我們要打開 Build Setting,然后將系統(tǒng)平臺切換到 Android。
在我們創(chuàng)建并運(yùn)行之前,我們還需要對代碼做出一些簡單的調(diào)整。我們只需要注釋掉幾行代碼,加一個簡單的返回聲明,再替換一個文件。
背景信息:Tanks!!! Android 包含了 Everyplay 插件,用以實(shí)現(xiàn)游戲屏幕錄制和分享。問題是,Everyplay 在2018年十月停止了服務(wù),而插件仍然存在一些未解決的問題,如果我們不對其進(jìn)行處理會導(dǎo)致編譯失敗。
首先,我們要糾正一下 Everyplay 插件 build.gradle 文件中的語法錯誤。該文件的路徑是:Plugins → Android → everyplay → build.gradle。
現(xiàn)在,我們打開了 gradle 文件,全選所有代碼,然后將下方的代碼替換上去。Tanks!!! 團(tuán)隊(duì)在 Github 上更新了代碼,但是不知道為什么并沒能更新到插件中。
// UNITY EXPORT COMPATIBLE
apply plugin: 'com.android.library'
repositories {
mavenCentral()
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
android {
compileSdkVersion 23
buildToolsVersion "25.0.3"
defaultPublishConfig "release"
defaultConfig {
versionCode 1600
versionName "1.6.0"
minSdkVersion 16
}
buildTypes {
debug {
debuggable true
minifyEnabled false
}
release {
debuggable false
minifyEnabled true
proguardFile getDefaultProguardFile('proguard-android.txt')
proguardFile 'proguard-project.txt'
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
jniLibs.srcDirs = ['libs']
}
}
lintOptions {
abortOnError false
}
}
最后我們要做的修改就是關(guān)閉 Everyplay。你可能想問:為什么我們要關(guān)閉 Everyplay 呢?因?yàn)楫?dāng)插件初始化時會導(dǎo)致 Android 應(yīng)用崩潰。我發(fā)現(xiàn)最快速的方法就是在 EveryPlaySettings.cs 文件中更新幾行代碼(該文件的路徑:Assets → Plugins → EveryPlay → Scripts),如此一來,每當(dāng) Everyplay 視圖檢測自身是否處于開啟狀態(tài)時,我們都會給它返回“false”。
public class EveryplaySettings : ScriptableObject
{
public string clientId;
public string clientSecret;
public string redirectURI = "https://m.everyplay.com/auth";
public bool iosSupportEnabled;
public bool tvosSupportEnabled;
public bool androidSupportEnabled;
public bool standaloneSupportEnabled;
public bool testButtonsEnabled;
public bool earlyInitializerEnabled = true;
public bool IsEnabled
{
get
{
return false;
}
}
#if UNITY_EDITOR
public bool IsBuildTargetEnabled
{
get
{
return false;
}
}
#endif
public bool IsValid
{
get
{
return false;
}
}
}
現(xiàn)在我們已經(jīng)準(zhǔn)備好 Build 了。在 Unity 中打開 Build Settings,選擇 Android 平臺,然后按下“Switch Platform”按鈕。隨后,在 Player Settings 中為 Android App 修改 bundle id。在這里,我使用的是 com.agora.tanks.voicedemo。
集成語音聊天功能
接下來,我們要利用 Unity 中的 Agora voice SDK for Unity 來給跨平臺項(xiàng)目增加語音聊天功能了。我們打開 Unity Asset Store ,搜索 Agora Voice SDK for Unity。
當(dāng)插件頁面完成加載后,點(diǎn)擊“Download”開始下載。下載完成后,選擇“Import”,將它集成到你的項(xiàng)目中。
我們需要創(chuàng)建一個腳本來讓游戲與 Agora Voice SDK 進(jìn)行交互。我們在項(xiàng)目中新建一個 C# 文件(AgoraInterface.cs),然后在 Visual Studio 中打開它。
在這個腳本中有兩個很重要的變量:
static IRtcEngine mRtcEngine;
public static string appId = "Your Agora AppId Here";
先要將“Your Agora AppId Here” 替換成 App ID,我們可在登錄 Agora.io ,進(jìn)入 Agora Dashboard 獲取。mRtcEngine是靜態(tài)的,這樣在OnUpdate 調(diào)用的時候,才不會丟失。由于游戲中的其它腳本可能會引用 App ID,所以它是public static。
考慮到節(jié)省時間,我已經(jīng)將AgoraInterface.cs的代碼寫好了(如下所示)。大家可以直接使用,避免重復(fù)造車輪。
在這里簡單解釋一下代碼。首先,我們在開頭有一些邏輯,用于 check/requset Android Permission。然后我們用 App ID 初始化 Agora RTC Engine,然后我們附加了一些事件回調(diào),這部分很簡單易懂。
mRtcEngine.OnJoinChannelSuccess表示用戶已經(jīng)成功加入指定頻道。
最后一個重要功能就是update,當(dāng)啟用了 Agora RTC Engine 時,我們想要調(diào)用引擎的.Pull()方法,它對于插件是否能運(yùn)行起來很關(guān)鍵。
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
using agora_gaming_rtc;
#if(UNITY_2018_3_OR_NEWER)
using UnityEngine.Android;
#endif
public class AgoraInterface : MonoBehaviour
{
static IRtcEngine mRtcEngine;
// PLEASE KEEP THIS App ID IN SAFE PLACE
// Get your own App ID at https://dashboard.agora.io/
// After you entered the App ID, remove ## outside of Your App ID
public static string appId = "Your Agora AppId Here";
void Awake()
{
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = 30;
}
// Start is called before the first frame update
void Start()
{
#if (UNITY_2018_3_OR_NEWER)
if (Permission.HasUserAuthorizedPermission(Permission.Microphone))
{
}
else
{
Permission.RequestUserPermission(Permission.Microphone);
}
#endif
mRtcEngine = IRtcEngine.GetEngine(appId);
Debug.Log("Version : " + IRtcEngine.GetSdkVersion());
mRtcEngine.OnJoinChannelSuccess += (string channelName, uint uid, int elapsed) => {
string joinSuccessMessage = string.Format("joinChannel callback uid: {0}, channel: {1}, version: {2}", uid, channelName, IRtcEngine.GetSdkVersion());
Debug.Log(joinSuccessMessage);
};
mRtcEngine.OnLeaveChannel += (RtcStats stats) => {
string leaveChannelMessage = string.Format("onLeaveChannel callback duration {0}, tx: {1}, rx: {2}, tx kbps: {3}, rx kbps: {4}", stats.duration, stats.txBytes, stats.rxBytes, stats.txKBitRate, stats.rxKBitRate);
Debug.Log(leaveChannelMessage);
};
mRtcEngine.OnUserJoined += (uint uid, int elapsed) => {
string userJoinedMessage = string.Format("onUserJoined callback uid {0} {1}", uid, elapsed);
Debug.Log(userJoinedMessage);
};
mRtcEngine.OnUserOffline += (uint uid, USER_OFFLINE_REASON reason) => {
string userOfflineMessage = string.Format("onUserOffline callback uid {0} {1}", uid, reason);
Debug.Log(userOfflineMessage);
};
mRtcEngine.OnVolumeIndication += (AudioVolumeInfo[] speakers, int speakerNumber, int totalVolume) => {
if (speakerNumber == 0 || speakers == null)
{
Debug.Log(string.Format("onVolumeIndication only local {0}", totalVolume));
}
for (int idx = 0; idx < speakerNumber; idx++)
{
string volumeIndicationMessage = string.Format("{0} onVolumeIndication {1} {2}", speakerNumber, speakers[idx].uid, speakers[idx].volume);
Debug.Log(volumeIndicationMessage);
}
};
mRtcEngine.OnUserMuted += (uint uid, bool muted) => {
string userMutedMessage = string.Format("onUserMuted callback uid {0} {1}", uid, muted);
Debug.Log(userMutedMessage);
};
mRtcEngine.OnWarning += (int warn, string msg) => {
string description = IRtcEngine.GetErrorDescription(warn);
string warningMessage = string.Format("onWarning callback {0} {1} {2}", warn, msg, description);
Debug.Log(warningMessage);
};
mRtcEngine.OnError += (int error, string msg) => {
string description = IRtcEngine.GetErrorDescription(error);
string errorMessage = string.Format("onError callback {0} {1} {2}", error, msg, description);
Debug.Log(errorMessage);
};
mRtcEngine.OnRtcStats += (RtcStats stats) => {
string rtcStatsMessage = string.Format("onRtcStats callback duration {0}, tx: {1}, rx: {2}, tx kbps: {3}, rx kbps: {4}, tx(a) kbps: {5}, rx(a) kbps: {6} users {7}",
stats.duration, stats.txBytes, stats.rxBytes, stats.txKBitRate, stats.rxKBitRate, stats.txAudioKBitRate, stats.rxAudioKBitRate, stats.users);
Debug.Log(rtcStatsMessage);
int lengthOfMixingFile = mRtcEngine.GetAudioMixingDuration();
int currentTs = mRtcEngine.GetAudioMixingCurrentPosition();
string mixingMessage = string.Format("Mixing File Meta {0}, {1}", lengthOfMixingFile, currentTs);
Debug.Log(mixingMessage);
};
mRtcEngine.OnAudioRouteChanged += (AUDIO_ROUTE route) => {
string routeMessage = string.Format("onAudioRouteChanged {0}", route);
Debug.Log(routeMessage);
};
mRtcEngine.OnRequestToken += () => {
string requestKeyMessage = string.Format("OnRequestToken");
Debug.Log(requestKeyMessage);
};
mRtcEngine.OnConnectionInterrupted += () => {
string interruptedMessage = string.Format("OnConnectionInterrupted");
Debug.Log(interruptedMessage);
};
mRtcEngine.OnConnectionLost += () => {
string lostMessage = string.Format("OnConnectionLost");
Debug.Log(lostMessage);
};
mRtcEngine.SetLogFilter(LOG_FILTER.INFO);
// mRtcEngine.setLogFile("path_to_file_unity.log");
mRtcEngine.SetChannelProfile(CHANNEL_PROFILE.GAME_FREE_MODE);
// mRtcEngine.SetChannelProfile (CHANNEL_PROFILE.GAME_COMMAND_MODE);
// mRtcEngine.SetClientRole (CLIENT_ROLE.BROADCASTER);
}
// Update is called once per frame
void Update ()
{
if (mRtcEngine != null) {
mRtcEngine.Poll ();
}
}
}
注意,以上代碼可復(fù)用于所有 Unity 項(xiàng)目。
離開頻道
如果你曾經(jīng)使用過 Agora SDK,你可能注意到了,這里沒有加入頻道和離開頻道。讓我們先從“離開頻道”開始動手,創(chuàng)建一個新的 C# 腳本LeaveHandler.cs,我們需要在用戶返回到主菜單的時候調(diào)用 theleaveHandler。最簡單的方法就是在 LobbyScene 打開后,為特定游戲?qū)ο箝_啟該方法。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using agora_gaming_rtc;
public class LeaveHandler : MonoBehaviour
{
// Start is called before the first frame update
void OnEnable()
{
// Agora.io Implimentation
IRtcEngine mRtcEngine = IRtcEngine.GetEngine(AgoraInterfaceScript.appId); // Get a reference to the Engine
if (mRtcEngine != null)
{
Debug.Log("Leaving Channel");
mRtcEngine.LeaveChannel();// leave the channel
}
}
}
在這里,我們要找的游戲?qū)ο笫?LeftSubPanel (如下圖,MainPanel → MenuUI → LeftSubPanel )。
Tanks!!! 中有兩種方法加入多人游戲,一種是創(chuàng)建新游戲,另一種是加入游戲。所以有兩個地方,我們需要增加“加入頻道”的命令。
讓我們先找到 UI Script Asset 文件夾(該文件夾路徑:Assets → Scripts → UI),然后打開CreateGame.cs文件。在第61行,你會找到游戲用于匹配玩家的方法,在這里我們可以加入一些邏輯用于加入頻道。首先我們要做的就是應(yīng)用 Agora SDK 庫。
using agora_gaming_rtc;
在StartMatchmakingGame()的第78行,我們需要加入一些邏輯來獲取正在運(yùn)行中的Agora RTC Engine,然后將“用戶輸入的內(nèi)容”作為頻道名稱(m_MatchNameInput.text)。
private void StartMatchmakingGame()
{
GameSettings settings = GameSettings.s_Instance;
settings.SetMapIndex(m_MapSelect.currentIndex);
settings.SetModeIndex(m_ModeSelect.currentIndex);
m_MenuUi.ShowConnectingModal(false);
Debug.Log(GetGameName());
m_NetManager.StartMatchmakingGame(GetGameName(), (success, matchInfo) =>
{
if (!success)
{
m_MenuUi.ShowInfoPopup("Failed to create game.", null);
}
else
{
m_MenuUi.HideInfoPopup();
m_MenuUi.ShowLobbyPanel();
// Agora.io Implimentation
var channelName = m_MatchNameInput.text; // testing --> prod use: m_MatchNameInput.text
IRtcEngine mRtcEngine = IRtcEngine.GetEngine(AgoraInterfaceScript.appId); // Get a reference to the Engine
mRtcEngine.JoinChannel(channelName, "extra", 0); // join the channel with given match name
Debug.Log("joining channel:" + channelName);
}
});
}
StartMatchmakingGame()包含了加入頻道
現(xiàn)在我們需要打開LobbyServerEntry.cs(Assets → Scripts → UI),然后加入一些邏輯,以實(shí)現(xiàn)讓用戶可以通過“Find a Game”來加入其他人的房間。
在 Visual Studio 打開 LobbyServerEntry.cs,然后找到第63行,這里有一個 JoinMatch()。我們在第80行增加幾行代碼。
private void JoinMatch(NetworkID networkId, String matchName)
{
MainMenuUI menuUi = MainMenuUI.s_Instance;
menuUi.ShowConnectingModal(true);
m_NetManager.JoinMatchmakingGame(networkId, (success, matchInfo) =>
{
//Failure flow
if (!success)
{
menuUi.ShowInfoPopup("Failed to join game.", null);
}
//Success flow
else
{
menuUi.HideInfoPopup();
menuUi.ShowInfoPopup("Entering lobby...");
m_NetManager.gameModeUpdated += menuUi.ShowLobbyPanelForConnection;
// Agora.io Implimentation
var channelName = matchName; // testing --> prod use: matchName
IRtcEngine mRtcEngine = IRtcEngine.GetEngine(AgoraInterfaceScript.appId); // Get a reference to the Engine
mRtcEngine.JoinChannel(channelName, "extra", 0); // join the channel with given match name
// testing
string joinChannelMessage = string.Format("joining channel: {0}", channelName);
Debug.Log(joinChannelMessage);
}
}
);
}
完成了!
現(xiàn)在我們已經(jīng)完成了Agora SDK 的集成,并且已經(jīng)準(zhǔn)備好進(jìn)行 iOS 端和 Android 端的 Build 與測試。我們可以參照上述內(nèi)容中的方法來進(jìn)行 Building 與部署。
為了便于大家參考,我已經(jīng)將這份 Tutorial 中的腳本上傳了一份到 Github:
https://github.com/digitallys...
如果你遇到 Agora SDK API 調(diào)用問題,可以參考我們的官方文檔(docs.agora.io),也歡迎在 RTC 開發(fā)者社區(qū) 的 Agora 版塊與我們的工程師和更多同行交流、分享。
總結(jié)
以上是生活随笔為你收集整理的ios开发 多人语音聊天_在 Unity 多人游戏中实现语音对话的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机怎样旋转桌面,win7电脑怎么设置
- 下一篇: java数据结构期末复习_java数据结