ios开发 多人语音聊天_在 Unity 多人游戏中实现语音对话
我們曾經不止一次為大家分享過游戲中的實時音視頻,例如怎么實現游戲中的聽聲辨位、狼人殺游戲中的語音聊天挑戰等。基本上,都是從技術原理和 Agora SDK 出發來分享的。這次我們換一個角度。我們將從 Unity 開發者的角度分享一下,在 Unity 中如何給自己的多人在線游戲增加實時語音通話功能。
我們在這里利用了 Unity 上流行的 “Tanks!!! asset reference” 坦克游戲作為多人在線游戲作為基礎,相信很多人都不會陌生。大家可以在 Unity Asset Store 中搜到它。然后,我們會利用 Unity Asset Store 中的 Agora Voice SDK 為它增加多人語音聊天功能。
在開始前,你需要做以下準備:
安裝 Unity 并注冊 Unity 賬號
了解如果在 Unity 中創建 iOS、Android 項目
一款跨移動平臺多玩家的 Unity 游戲(本文中我們選擇的是 Tanks)
了解 C# 和 Unity 腳本
注冊一個 Agora 開發者賬戶
至少兩個移動設備(如果有一個 iOS 設備,一個 Android 設備就再理想不過了)
安裝 Xcode
新建 Unity 項目
我們默認大家都是用過 Unity 的開發者,但是為了照顧更多的人。我們還是要從頭講起。當然,開始的操作步驟很簡單,所以我們會盡量以圖片來說明。
首先,打開 Unity 后,讓我們先創建一個新的項目。
如果你之前已經下載過 Tanks!!! ,那么我們點擊頁面旁邊的“Add Asset Package”按鈕,選擇添加它即可。
如果你還未下載過 Tanks!!! 那么可以在 Unity Store 中下載它。
在將 Tanks!!! 參考項目部署到手機之前,還有幾步需要做。首先,我們需要在 Unity Dashboard 中,為這個項目開啟 Unity Live Mode。該設置的路徑是:project → Multiplayer → Unet Config。盡管 Tanks!!! 只支持最多四個玩家4,但我們在將“Max Player per room”設置為6。
圖:這個界面說明 Unity Live Mode 已經開啟
Building for iOS
現在我們已經準備好來創建 iOS 版本了。打開 Build Setting,將系統平臺切換到 iOS,然后 Build。在切換系統平臺后,請記得更新 Bundle Identifier(如下圖所示)。
圖:創建了一個“Build”文件夾用于儲存 iOS 項目
圖:Build 完成
讓我們打開 Unity-iPhone.xcodeproj,sign 并讓它在測試設備上運行。
現在我們已經完成了 iOS 項目的創建。接下來我們要創建 Android 項目了。
Building for Android
Android 項目相比 iOS 來講要更簡單一些。因為 Unity 可以直接創建、sign 和部署運行,無需借助 Android Studio。我默認大家已經將 Unity 與 Android SDK 文件夾關聯起來了。現在我們要打開 Build Setting,然后將系統平臺切換到 Android。
在我們創建并運行之前,我們還需要對代碼做出一些簡單的調整。我們只需要注釋掉幾行代碼,加一個簡單的返回聲明,再替換一個文件。
背景信息:Tanks!!! Android 包含了 Everyplay 插件,用以實現游戲屏幕錄制和分享。問題是,Everyplay 在2018年十月停止了服務,而插件仍然存在一些未解決的問題,如果我們不對其進行處理會導致編譯失敗。
首先,我們要糾正一下 Everyplay 插件 build.gradle 文件中的語法錯誤。該文件的路徑是:Plugins → Android → everyplay → build.gradle。
現在,我們打開了 gradle 文件,全選所有代碼,然后將下方的代碼替換上去。Tanks!!! 團隊在 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
}
}
最后我們要做的修改就是關閉 Everyplay。你可能想問:為什么我們要關閉 Everyplay 呢?因為當插件初始化時會導致 Android 應用崩潰。我發現最快速的方法就是在 EveryPlaySettings.cs 文件中更新幾行代碼(該文件的路徑:Assets → Plugins → EveryPlay → Scripts),如此一來,每當 Everyplay 視圖檢測自身是否處于開啟狀態時,我們都會給它返回“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;
}
}
}
現在我們已經準備好 Build 了。在 Unity 中打開 Build Settings,選擇 Android 平臺,然后按下“Switch Platform”按鈕。隨后,在 Player Settings 中為 Android App 修改 bundle id。在這里,我使用的是 com.agora.tanks.voicedemo。
集成語音聊天功能
接下來,我們要利用 Unity 中的 Agora voice SDK for Unity 來給跨平臺項目增加語音聊天功能了。我們打開 Unity Asset Store ,搜索 Agora Voice SDK for Unity。
當插件頁面完成加載后,點擊“Download”開始下載。下載完成后,選擇“Import”,將它集成到你的項目中。
我們需要創建一個腳本來讓游戲與 Agora Voice SDK 進行交互。我們在項目中新建一個 C# 文件(AgoraInterface.cs),然后在 Visual Studio 中打開它。
在這個腳本中有兩個很重要的變量:
static IRtcEngine mRtcEngine;
public static string appId = "Your Agora AppId Here";
先要將“Your Agora AppId Here” 替換成 App ID,我們可在登錄 Agora.io ,進入 Agora Dashboard 獲取。mRtcEngine是靜態的,這樣在OnUpdate 調用的時候,才不會丟失。由于游戲中的其它腳本可能會引用 App ID,所以它是public static。
考慮到節省時間,我已經將AgoraInterface.cs的代碼寫好了(如下所示)。大家可以直接使用,避免重復造車輪。
在這里簡單解釋一下代碼。首先,我們在開頭有一些邏輯,用于 check/requset Android Permission。然后我們用 App ID 初始化 Agora RTC Engine,然后我們附加了一些事件回調,這部分很簡單易懂。
mRtcEngine.OnJoinChannelSuccess表示用戶已經成功加入指定頻道。
最后一個重要功能就是update,當啟用了 Agora RTC Engine 時,我們想要調用引擎的.Pull()方法,它對于插件是否能運行起來很關鍵。
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 ();
}
}
}
注意,以上代碼可復用于所有 Unity 項目。
離開頻道
如果你曾經使用過 Agora SDK,你可能注意到了,這里沒有加入頻道和離開頻道。讓我們先從“離開頻道”開始動手,創建一個新的 C# 腳本LeaveHandler.cs,我們需要在用戶返回到主菜單的時候調用 theleaveHandler。最簡單的方法就是在 LobbyScene 打開后,為特定游戲對象開啟該方法。
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
}
}
}
在這里,我們要找的游戲對象是 LeftSubPanel (如下圖,MainPanel → MenuUI → LeftSubPanel )。
Tanks!!! 中有兩種方法加入多人游戲,一種是創建新游戲,另一種是加入游戲。所以有兩個地方,我們需要增加“加入頻道”的命令。
讓我們先找到 UI Script Asset 文件夾(該文件夾路徑:Assets → Scripts → UI),然后打開CreateGame.cs文件。在第61行,你會找到游戲用于匹配玩家的方法,在這里我們可以加入一些邏輯用于加入頻道。首先我們要做的就是應用 Agora SDK 庫。
using agora_gaming_rtc;
在StartMatchmakingGame()的第78行,我們需要加入一些邏輯來獲取正在運行中的Agora RTC Engine,然后將“用戶輸入的內容”作為頻道名稱(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()包含了加入頻道
現在我們需要打開LobbyServerEntry.cs(Assets → Scripts → UI),然后加入一些邏輯,以實現讓用戶可以通過“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);
}
}
);
}
完成了!
現在我們已經完成了Agora SDK 的集成,并且已經準備好進行 iOS 端和 Android 端的 Build 與測試。我們可以參照上述內容中的方法來進行 Building 與部署。
為了便于大家參考,我已經將這份 Tutorial 中的腳本上傳了一份到 Github:
https://github.com/digitallys...
如果你遇到 Agora SDK API 調用問題,可以參考我們的官方文檔(docs.agora.io),也歡迎在 RTC 開發者社區 的 Agora 版塊與我們的工程師和更多同行交流、分享。
總結
以上是生活随笔為你收集整理的ios开发 多人语音聊天_在 Unity 多人游戏中实现语音对话的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机怎样旋转桌面,win7电脑怎么设置
- 下一篇: java数据结构期末复习_java数据结