99国产精品欲av蜜臀,可以直接免费观看的AV网站,gogogo高清免费完整版,啊灬啊灬啊灬免费毛片

網易首頁 > 網易號 > 正文 申請入駐

萬字教程,一次學會如何實現游戲內多人實時語音互動

0
分享至

渴望在游戲中與小隊無縫溝通

與好友共享開黑樂趣?

Hello,你的實時互動神器,讓游戲溝通從此無界限!

在激烈的對戰和廣闊的虛擬世界中,實時語音交流是提升體驗的關鍵。本教程將通過 BossRoomHelloDemo 的示例項目工程,展示如何使用 Unity Online Services(UOS)提供的Hello 服務,來實現游戲內的低延時、高品質的多人實時語音互動

教程中涉及 UOS 服務包括:

  • 游戲內語音服務 Hello:助力全球游戲實現超低延時、高品質實時語音互動

游戲內語音服務 Hello

UOS Hello(后續簡稱Hello)是 Unity 官方推出,聲網?提供運營和技術支持的,低延時、高品質全球游戲內置實時語音服務,可以讓玩家在游戲中實現實時、清晰、流暢的語音交流。

全面支持社交、競技、低耗、世界、指揮等五大模式,提供酣暢淋漓的游戲體驗。支持手游、端游、頁游,同時支持這些游戲的多平臺互通。適配各大洲主流硬件機型,保障全球優質網絡覆蓋,游戲語音超低延時、全球同服觸手可及!適用于 FPS、Moba、MMORPG、社交元宇宙等游戲場景的語音功能實現。

為什么選擇 Hello

1. 更全機型適配:各大洲核心地區主力機型深度適配,覆蓋了30+平臺開發框架,30000+移動終端,滿足海外區域復雜的平臺及機型適配要求。

2. 性能開銷低:支持中低端顯卡設備流暢運行,避免發熱發燙、卡頓閃退等不良現象,體驗更具性價比。

3. 全球覆蓋:無論 APP 在歐美、東南亞、中東還是非洲,都能享受卓越的娛樂互動體驗;聲網?是RTC 領域唯一一個全球化服務團隊,在全球超過 10 個區域設有辦公室,可提供就近的本地化專業服務。

教程視頻

教程學習大綱

2. 啟用 Sync Relay & Hello 服務,配置房間 ID 號

3. 初始化 Hello SDK 和 SDK 鑒權

4. 初始化 RTC 語音引擎

5. 加入頻道和檢查設備權限

6. 開啟麥克風和聽筒,開始音頻互動測試

7. 測試開啟和關閉空間音頻的效果

8. 調整空間音頻參數(語音接收范圍、衰減系數、人聲模糊)和通話音量

9. 用戶離開頻道

教程示例程序

教程內學習用到的項目工程,可以通過下面提供的鏈接進行下載:

BossRoomHelloDemo 項目工程下載地址:

https://unitychina.coding.net/p/uos/d/BossRoomHelloDemo/git

教程操作步驟

接下來讓我們跟著教程步驟開始學習吧!

1.1 下載項目工程

我們首先從提供的鏈接下載 BossRoomHelloDemo 的示例項目工程,你可以自行通過 git 的方式將代碼倉庫克隆到本地目錄,也可以直接點擊 “下載 ZIP” 的按鈕來下載項目的壓縮包文件。


教程中我先通過下載項目的壓縮包文件的方式來講解,等下載好壓縮包文件以后,解壓縮項目。

然后通過 Unity hub 打開該項目,推薦大家使用 Unity 2022LTS 以及以上的更高版本。在這里,我先用Unity 2022.3.41版本來打開項目了。


打開項目工程以后,我們會首先看到一個 UOS 服務彈出的歡迎窗口頁面,大家后續可以根據自己制作的游戲的類型,進行選擇對應的游戲模板后,進一步來查看符合自己游戲功能的可使用的 UOS 服務。

1.2 配置 UOS App 信息然后可以直接點擊歡迎頁面的「打開 UOS Launcher」按鈕,或者點擊菜單欄「UOS -> Open Launcher」,就可以打開 UOS 的 App 信息配置窗口了。

在最新版本的 UOS Launcher 中,大家會看到 UI 界面和之前不太一樣了,因為我們更新升級了 Launcher 的功能。我們先點擊 “Link App” 的按鈕:


會彈出下面的窗口,給大家提供了 2 種方式來為你的項目綁定 UOS App信息。


UOS App 信息綁定方式 1

我們先來看第一種信息綁定的方式,“By App ID/Secret”,這種方式和之前是一樣的,需要大家把創建好的 UOS 應用的 App 信息復制后填寫在這里。


點擊「UOS Developer Portal」按鈕,接著會進入 UOS 官網,新建一個 UOS 應用。我們選擇一個創建好的組織,輸入項目的名字后,然后點擊「創建并啟用」。接著在 UOS 網頁的「設置」頁面,復制一下 UOS App 的信息。


然后在 Link App 面板中填寫復制的 App 信息,并點擊「Link App」按鈕,就可以在 UOS Launcher 面板中看到自動關聯的 App Name 和 App ID 信息了。


UOS App 信息綁定方式 2

我們再來看看第二種信息綁定的方式,“By Unity project”。由于「UOS Launcher」功能升級,給我們提供了一種新的方式,不需要手動在 UOS 網頁端復制 App 信息,可以直接選擇創建好的 UOS 應用即可。

在窗口頁面中:

  • 第1步,“Select organization” 這里,先選擇你的一個項目組織;

  • 第2步,“Select project” 這里,有 2 個選項,先點擊按鈕 “Select an exsting project”,然后在 “Project” 選項這里,從項目的下拉菜單中找到剛才已經創建過的項目;

  • 最后點擊【Link App】按鈕,就可以看到 Unity 項目工程自動和 UOS App 進行綁定好了。


第 2 步的時候,如果我們選擇 "Create a new project" 的話,UOS Launcher 會自動幫我們創建一個 UOS 應用的,"Project name" 這里會自動填充為:項目名字 + 當前日期時間的。然后點擊【Link App】按鈕后,也是可以自動關聯創建的 UOS 應用了。


大家可以進入菜單欄 Edit → Project Settings → Player → Product Name 這里,看到剛才使用的默認的項目名 BossRoomHello。


在這里的話,我還是先切回到我準備綁定的 UOS 應用:UOSHello_Demo。

2.啟用 Sync Relay & Hello 服務,配置房間 ID 號

2.1 開啟 Sync Relay 服務

接著在 UOS 的下拉服務列表中,找到 Sync - Relay,點擊 Enable 開啟服務。

2.2 開啟 Hello 服務

繼續在 UOS 的下拉服務列表中,找到 Hello 服務,點擊 Enable 按鈕后會自動鏈接到 UOS 網頁端。

在【概覽】頁面,找到 Hello 服務,點擊【免費試用】的按鈕。

在彈出的窗口中,需要先填寫聯系人信息。

填寫完信息并保存后,可以看到 Hello 服務已經開通了。

2.3 設置 SyncRelay 的房間 ID 號

在 UOS 網頁端找到創建的房間配置 ID 號,先進行復制。

然后再找到 Startup 場景中的【NetworkManager】對象, 將剛才復制的房間配置 ID 號,填入 RelayTransport 組件的RoomProfileUUID 參數這里。為了在 Web 端正常通信,傳輸協議 Transport Type 這里,我們就使用幫我們設置好的 Websocket 協議。

這里參數設置完以后,大家就可以運行游戲開始體驗語音互動的效果了。接下來,就詳細展開來講講如何在項目中接入 UOS 的 Hello 語音功能!

3.初始化 Hello SDK 和 SDK 鑒權3.1 游戲語音基本流程

下圖展示了利用 Hello SDK 實現音頻互動的工作流程。

相關的基本概念:

頻道:由開發者調用 Hello 提供的 API 創建的、用于傳輸實時數據的通道。

發布:指頻道中的用戶將音視頻數據發送到頻道的操作。

訂閱:指頻道中的用戶接收頻道內已發布的音視頻流的操作。

用戶角色:用于定義頻道內用戶是否有發流權限,分別有主播和觀眾兩種用戶角色。

主播:頻道內有發流權限的用戶。

觀眾:頻道內沒有發流權限的用戶。觀眾只能訂閱遠端音視頻流,不能發布音視頻流。

SD-RTN?:是 Software-Defined Real-Time Network 的縮寫,即軟件定義實時網,這是聲網自建的底層實時傳輸網絡。

3.2 初始化 HelloSDK

游戲運行后,可以在 MainMenu 場景中先找到動態生成的的 RTCAudioProxy 游戲對象,它身上掛載了項目中夫的 UOS Hello 語音功能的代碼控制的核心腳本 RTCAudioProxy.cs。

在腳本中,首先封裝了一個 InitHelloSDK 的方法,來實現對 Hello SDK 的實例化。由于項目中已經安裝了 UOS Launcher,所以在 HelloSDK.InitializeAsync 的方法中,會使用 UOS Launcher 關聯的 UOS App 信息來初始化 Hello SDK 的。

private async Task Init()
{
if (RtcEngine == null)
{
await InitHelloSDK();
        //此處省略其它代碼行......
}
}

private async Task InitHelloSDK()
{
try
{
await HelloSDK.InitializeAsync();
}
catch (HelloClientException e)
{
Debug.Log($"failed to initialize sdk, clientEx: {e.Message}");
throw;
}
catch (HelloServerException e)
{
Debug.Log($"failed to initialize sdk, serverEx: {e.Message}");
throw;
}
}

3.3 SDK鑒權

由于當前項目中未使用 UOS Passport,所以在這里我們通過調用外部登錄的方法 AuthTokenManager.ExternalLogin 來獲取 AccessToken,方法內將根據系統分配的客戶端的用戶的 Id 信息來生成用戶的身份憑證。

private uint userId;
private async Task Init()
{
if (RtcEngine == null)
{
await InitHelloSDK();
await AuthTokenManager.ExternalLogin(userId.ToString());
//此處省略其它代碼行......
}
}

可以查看下 AuthTokenManager.cs 腳本中的進行鑒權的 ExternalLogin 方法,會根據當前用戶的 UOS App 信息進行身份驗證的。如果當前項目關聯的 UOS App 信息不為空,則會調用異步方法 GenerateAccessTokenWithUosAppId 來生成訪問的令牌。

public static async Task ExternalLogin(string userID, string personaID = null, string displayName = null)
{
if (string.IsNullOrEmpty(Instance.CurrentUosAppID))
{
throw new AuthException(ErrorCode.NotInitialized, "AuthTokenManager未經初始化");
}
await ExternalLoginWithAppId(Instance.CurrentUosAppID, userID, personaID, displayName);
}

4.初始化 RTC 語音引擎4.1 使用 HelloSDK 初始化 RTC 語音引擎

調用 HelloSDK 封裝的 InitRtcEngine 方法來初始化語音引擎 IRtcEngine 實例。初始化實例時,需要傳入2個參數,分別是 context 和 handler。

在 context 參數中需要同時設置好頻道場景、音頻場景、訪問區域。

CHANNEL_PROFILE_TYPE:指的是頻道場景,默認類型為直播場景(CHANNEL_PROFILE_LIVE_BROADCASTING)。推薦使用默認的直播場景以獲取更好的音頻體驗。

AUDIO_SCENARIO_TYPE:指的是音頻場景,默認類型是自動場景(AUDIO_SCENARIO_DEFAULT),會根據用戶的角色和音頻路由自動匹配合適的音質。

AREA_CODE:指的是 SDK 連接的服務器所在的區域。這里使用的是 AREA_CODE_GLOB,表示全球都可以訪問服務器。

private IRtcEngine RtcEngine;

private async Task Init()
{
if (RtcEngine == null)
{
await InitHelloSDK();
await AuthTokenManager.ExternalLogin(userId.ToString());
await CreateRTCEngine();
}
}

private async Task CreateRTCEngine()
{
var handler = new UserEventHandler();
handler.OnUserJoinedEvent += OnUserJoined;
handler.OnUserOfflineEvent += OnUserOffline;

var context = new RtcEngineContext
{
channelProfile = CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING,
audioScenario = AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_DEFAULT,
areaCode = AREA_CODE.AREA_CODE_GLOB
};

RtcEngine = await HelloSDK.InitRtcEngine(context, handler);
     Debug.Log($"successfully init agora rtc engine");
//此處省略其它代碼行......
}

AUDIO_SCENARIO_TYPE 分為以下幾種類型:

  • AUDIO_SCENARIO_DEFAULT

0: (默認)自動場景,根據用戶角色和音頻路由自動匹配合適的音質。

  • AUDIO_SCENARIO_GAME_STREAMING

3: 高音質場景,適用于音樂為主的場景。例如:樂器陪練。

  • AUDIO_SCENARIO_CHATROOM

5: 聊天室場景,適用于用戶需要頻繁上下麥的場景。例如:教育場景。

  • AUDIO_SCENARIO_CHORUS

7: 合唱場景。適用于網絡條件良好,要求極低延時的實時合唱場景。

  • AUDIO_SCENARIO_MEETING

8: 會議場景,適用于人聲為主的多人會議。

  • AUDIO_SCENARIO_NUM

枚舉的數量。

溫馨提醒:游戲場景推薦大家使用 AUDIO_SCENARIO_DEFAULT、AUDIO_SCENARIO_GAME_STREAMING、AUDIO_SCENARIO_CHATROOM 這三種模式,不推薦使用其他模式。

初始化語音引擎 RtcEngine 實例時,還需要傳入一個 Handle 類型的參數,并提前注冊好了相關事件。這里自定義了一個 UserEventHandler.cs 腳本類,腳本中封裝了幾個方法,后續會使用來處理用戶加入/離開頻道、遠端用戶加入/離線時等等的行為。

internal class UserEventHandler : IRtcEngineEventHandler
{
public event System.Action

 OnJoinChannelSuccessEvent; public event System.Action

 OnUserJoinedEvent; public event System.Action

 OnUserOfflineEvent; public UserEventHandler() { } public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed) { Debug.Log("Join Channel Success " + connection.channelId + " " + connection.localUid); OnJoinChannelSuccessEvent?.Invoke(connection.localUid); } public override void OnLeaveChannel(RtcConnection connection, RtcStats stats) { Debug.Log("Leave Channel Success" + connection.channelId + " " + connection.localUid); } public override void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed) { Debug.Log("OnUserJoined " + connection.channelId + " " + remoteUid); OnUserJoinedEvent?.Invoke(remoteUid); base.OnUserJoined(connection, remoteUid, elapsed); } public override void OnUserOffline(RtcConnection connection, uint remoteUid, USER_OFFLINE_REASON_TYPE reason) { Debug.Log("OnUserOffline " + connection.channelId + " " + remoteUid); OnUserOfflineEvent?.Invoke(remoteUid); base.OnUserOffline(connection, remoteUid, reason); } public override void OnError(int err, string msg) { Debug.LogWarning("######### OnError ###########" + err + " " + msg); } } 



4.2 配置語音引擎

初始化語音引擎示例后,我們調用 EnableAudio 方法來啟用音頻模塊。用戶的角色類型分為主播和觀眾,SDK 默認設置的用戶角色為觀眾。在這里,我們調用 SetClientRole 方法將角色類型修改為主播

private async Task CreateRTCEngine()
{
//此處省略其它代碼行......

RtcEngine = await HelloSDK.InitRtcEngine(context, handler);
Debug.Log($"successfully init agora rtc engine");

RtcEngine.EnableAudio();
    RtcEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
}

通過下面的圖示,我們來看看主播和觀眾兩種角色的區別:

  • 主播:可以在頻道內 發布 音視頻,同時也可以 訂閱 其他主播發布的音視頻。

  • 觀眾:可以在頻道內 訂閱 音視頻,不具備發布 音視頻權限。

4.3 獲取用戶在頻道內的權限憑證 AccessToken

在加入頻道之前,我們還需要先進行鑒權,來獲取用戶在指定頻道內的權限憑證。

腳本中封裝了方法 GenerateAccessToken 來實現這個功能。在方法中根據傳入的頻道名字、語音通話的角色類型是發布者還是訂閱者,來獲取到用戶在指定頻道內的權限憑證后,保存在變量 accessToken 中。這個方法,在后面的加入頻道的方法調用之前,會被執行調用的。

private string accessToken;

private async Task GenerateAccessToken(string channel, HelloOptions options = null)
{
try
{
var tokenInfo = await HelloSDK.Instance.GenerateAccessToken(channel, options);
accessToken = tokenInfo.AccessToken;
}
catch (HelloClientException e)
{
Debug.LogErrorFormat("failed to generate token, clientEx: {0}", e.Message);
throw;
}
catch (HelloServerException e)
{
Debug.LogErrorFormat("failed to generate token, serverEx: {0}", e.Message);
throw;
}
}

5.加入頻道和檢查設備權限

5.1 創建加入頻道的方法

當用戶加入房間后,會調用JoinChannel 方法來加入當前的語音頻道。

  • 在用戶加入房間的時候,將當前客戶端的 id 號賦值給變量 userId。然后調用 Init 方法來初始化 Hello SDK 和語音引擎對象。

  • 如果遠端用戶離線再上線的話,參數 uid 是會發生變化的,所以會再調用一次AuthTokenManager.ExternalLogin 方法進行驗證用戶的身份。

  • 然后再調用一下剛才封裝好的方法 GenerateAccessToken 來獲取用戶在指定頻道內的權限憑證。

  • 接著可以調用 JoinChannel 的方法加入語音頻道了。

  • 同時也會通過一個bool 值類型的標志變量 isInChannel 記錄下當前用戶已經在頻道內了。

  • 加入頻道時,也可以傳入一個 bool 值類型的變量 enableSpatialAudio ,來供用戶選擇是否要開啟空間音頻的效果。

private bool isInChannel;

public async void JoinChannel(uint uid, bool enableSpatialAudio)
{
userId = uid;
if (RtcEngine == null)
{
await Init();
}
if (!isInChannel && RtcEngine != null)
{
await AuthTokenManager.ExternalLogin(userId.ToString());
await GenerateAccessToken(channelName, new HelloOptions { role = Role.Publisher });
int ret = RtcEngine.JoinChannel(accessToken, channelName, "", userId);
isInChannel = true;
Debug.Log($"JoinChannel ## ret:{ret}, userId: {userId}");
}
if (enableSpatialAudio)
{
Instance.EnableSpatialAudio();
}
else
{
Instance.DisableSpatialAudio();
}
}

5.2 設置頻道的名字

加入某一個頻道的時候,需要傳入頻道的名字。而當我們在使用 Sync Relay 的服務創建房間時,就已經將創建的房間 ID 號設置為頻道的名字了。

找到RTCAudioProxy.cs腳本,可以看到封裝的設置頻道名字的方法 SetChannelName:

private string channelName;

public void SetChannelName(string name)
{
this.channelName = name;
 }

SetChannelName 方法在 LobbyUIMediator.cs 腳本的下列 2 個位置有被引用到:

調用位置1:當創建房間的時候,調用 SetChannelName 的方法來設置一下頻道名字。

private void OnRoomCreated(CreateRoomResponse response)
{
if (response.Code == (uint)RelayCode.OK)
{
// 需要在連接到Relay服務器之前,設置好房間信息
m_ConnectionManager.NetworkManager.GetComponent ().SetRoomData(response);
if (response.Visibility == LobbyRoomVisibility.Private && !string.IsNullOrEmpty(response.JoinCode))
{
m_ConnectionManager.NetworkManager.GetComponent ().SetJoinCode(response.JoinCode);
}
m_LocalUser.IsHost = true;
m_LocalRoom.RoomUuid = response.RoomUuid;
m_LocalRoom.RoomCode = response.RoomUuid;// TODO RoomCode not supported yet, use RoomUuid instead for now
m_LocalRoom.JoinCode = response.JoinCode;
m_LocalRoom.RoomName = response.Name;

Debug.Log($"Room created by user: {response.OwnerId} and room uuid is {response.RoomUuid}");
m_ConnectionManager.StartHost(m_LocalUser.DisplayName);
HelloService.RTCAudioProxy.Instance.SetChannelName(response.RoomUuid);
}
else
{
Debug.Log("Create Room Fail By Lobby Service, code: " + response.Code);
UnblockUIAfterLoadingIsComplete();
}
}

調用位置2:當用戶加入房間的時候,調用 SetChannelName 的方法來選擇要加入的頻道的名字。

private void OnRoomInfoGot(QueryRoomResponse response, string joinCode)
{
if (response.Code == (uint)RelayCode.OK)
{
// TODO
if (response.Visibility == LobbyRoomVisibility.Private)
{
response.JoinCode = joinCode;
m_ConnectionManager.NetworkManager.GetComponent ().SetJoinCode(joinCode);
}

// 需要在連接到Relay服務器之前,設置好房間信息
NetworkManager.Singleton.GetComponent ().SetRoomData(response);

m_LocalRoom.RoomUuid = response.RoomUuid;
m_LocalRoom.RoomCode = response.RoomUuid;// TODO RoomCode not supported yet, use RoomUuid instead
m_LocalRoom.JoinCode = response.JoinCode;
m_LocalRoom.RoomName = response.Name;

Debug.Log("Joining Room: " + response.RoomUuid);
m_ConnectionManager.StartClient(m_LocalUser.DisplayName);
HelloService.RTCAudioProxy.Instance.SetChannelName(response.RoomUuid);
}
else
{
Debug.Log("Query Room Fail By Lobby Service, code: " + response.Code);
UnblockUIAfterLoadingIsComplete();
}
}

5.3 調用加入頻道的方法

加入頻道的方法 JoinChannel,腳本中有 2 個位置會引用該方法。

調用位置1:

在 OnAssignedPlayerNumber 方法中會調用 JoinChannel 的方法,同時傳入的 bool 值為 false。原理是:在創建隊伍和加入隊伍以后,選擇角色的時候,我們默認這里是關閉空間音頻效果的,這樣用戶可以自由通話交流對方都能聽到聲音。

void OnAssignedPlayerNumber(int playerNum)
{
m_ClassInfoBox.OnSetPlayerNumber(playerNum);
HelloService.RTCAudioProxy.Instance.JoinChannel((uint)playerNum + 1, false);
}

調用位置2:

會在 ClientPlayerAvatar.cs 腳本的 OnNetworkSpawn 方法中被引用到,而 OnNetworkSpawn 方法通常用于對動態生成的網絡對象的初始化操作。

這里代碼的含義是:如果是自己客戶端通過網絡生成的角色對象,會調用加入頻道的方法 JoinChannel。同時會根據 ClientPrefs.cs 腳本中封裝的 GetSpaceAudio() 方法,獲取到設置的 UI 界面存儲的是否打開空間音頻的值,作為參數傳入。

public override void OnNetworkSpawn()
{
name = "PlayerAvatar" + OwnerClientId;

if (IsClient && IsOwner)
{
LocalClientSpawned?.Invoke(this);
}

if (m_PlayerAvatars)
{
m_PlayerAvatars.Add(this);
}
if (IsOwner)
{
HelloService.RTCAudioProxy.Instance.JoinChannel((uint)OwnerClientId + 1, ClientPrefs.GetSpaceAudio() > 0);
}
}

這里的參數設置完以后,大家已經可以點擊 Play 開始體驗項目了,我們先在 Unity 編輯器中運行項目:


然后會在 Console 控制臺看到相關的日志輸出:

  • 輸出 “Create Room Successfully”,說明房間創建成功了;

  • 輸出 “Room Created by user”這里,能看到當前的房間的 room uuid ,和 UOS 網頁端的 Sync Relay 頁面顯示的當前房間 ID 號是一致的;

  • 而且看到當前房間的狀態是 “已就緒”,對應輸出的日志信息顯示也是 “Ready” 的狀態;

  • 輸出 “successfully init agora rtc engine”,說明語音引擎已經成功實現了初始化的操作;

  • 輸出 “JoinChannel”,說明用戶已經成功加入語音頻道了;

  • 輸出 “DisableSpatialAudio”,說明在這個界面的時候,用戶進行語音通話是關閉空間音頻效果的;

  • 輸出 “Join Channel Success”,說明本地用戶調用 JoinChannel 方法成功加入頻道后,自動觸發了回調方法 OnJoinChannelSuccess。


public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
{
Debug.Log("Join Channel Success " + connection.channelId + " " + connection.localUid);
OnJoinChannelSuccessEvent?.Invoke(connection.localUid);
}

5.4 設備檢測

在我們開始進行實時音頻互動前,首先需要檢查是否已經獲取到設備的權限。

處理權限請求

先來給大家介紹下如何獲取設備的攝像頭、麥克風等權限!不同平臺下的處理方式是不一樣的。

溫馨提醒:在 Unity 2018.3 或以上版本中,Unity 不會主動向用戶獲取麥克風和相機權限,因此你需要請求獲取權限。

  • Windows 系統會在應用程序首次嘗試使用攝像頭或麥克風等功能時,自動向用戶彈出權限請求提示,因此你無需額外處理權限請求。

  • 如果你開發的目標平臺是iOS 或 macOS,聲網 RTC Unity SDK 在 Editor 文件夾下提供一個構建后處理腳本 BL_BuildPostProcess.cs。在你將項目從 Unity Editor 中構建并導出為 iOS 項目后,該腳本會自動向 Info.plist 文件添加攝像頭和麥克風權限,無需你手動處理。

  • 如果你開發的目標平臺是Android,Unity 提供了權限請求的 API CheckPermissions。你可以調用該方法來獲取相關權限。

獲取安卓權限

接下來參考下列步驟來獲取安卓權限:

  • 導入 Unity 的 Android 命名空間

#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
using UnityEngine.Android;
#endif

  • 創建需要獲取的權限列表,包括攝像頭和麥克風。

#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
private string[] permissionList = new string[]
{
//Permission.Camera,
Permission.Microphone
};
#endif

  • 檢查是否已獲取權限。

private void CheckPermissions()
{
#if (UNITY_2018_3_OR_NEWER && UNITY_ANDROID)
foreach (string permission in permissionList)
{
if (!Permission.HasUserAuthorizedPermission(permission))
{
Permission.RequestUserPermission(permission);
}
}
#endif
}

調用獲取權限的方法

調用 CheckPermissions 方法檢查是否已獲取實現語音互動所需的權限。

private void Update()
{
CheckPermissions();
}

6.開啟麥克風和聽筒,開始音頻互動測試

此時,我們已經完全可以運行項目,來進行語音通話測試了。為了便于體驗多人實時語音互動的效果,你可以將你的項目提前 Build 一個想要的平臺的可執行文件。

6.1 構建項目可執行文件(Windows 平臺)

我們先來 Build 一個 Windows 平臺的可執行文件測試下。點擊 File → BuildSettings,這里的 Development Build 默認是先不打勾的,Resolution 這里先選擇窗口模式(Windowed),預先設置個 1024 * 768 的窗口大小,大家可以自己修改設置。然后點擊 “Build”,文件放在 BossRoomHelloDemo_exe 文件夾下面。

Build 完成后,運行項目,然后點擊 “創建隊伍”。同時打開可執行文件 BossRoomHello.exe,點擊 “加入隊伍”,此時界面會提示 “加入失敗,請打開或關閉 Debug 模式后重試”。


這是因為編輯器默認是屬于開發者模式的,我們構建的 exe 文件并沒有選擇開發者模式。如果你想一個在 Unity 編輯器中運行,另一個在 exe 文件中運行的話,那就需要勾選 Development Build 選項,然后重新 Build 即可。


Build 完成后可以再次運行測試下效果,已經可以成功加入隊伍了。


6.2 當有遠端用戶加入

此時在 Console 控制臺可以看到打印輸出的信息 “OnUserJoined”,顯示遠端用戶已經加入了。


當遠端的用戶上線時,會自動觸發腳本中 override 重寫的回調方法 OnUserJoined。

public override void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
{
Debug.Log("OnUserJoined " + connection.channelId + " " + remoteUid);
OnUserJoinedEvent?.Invoke(remoteUid);
base.OnUserJoined(connection, remoteUid, elapsed);
}

由于我們還需要在此時做一些其它事件的處理,所以又自定義了一個 OnUserJoined 方法,該方法已經在最初進行初始化語音引擎的時候,注冊給了 handle 對象 。

接著我們來分析一下代碼:在 OnUserJoined 方法中,會將遠端用戶加入到 remoteUidList 集合中,同時還將遠端用戶的空間位置信息都存入了字典 remotePositionInfoDict 中,也會通過調用 UpdateRemotePosition 方法來更新遠端用戶的空間位置信息。

同時也會調用 SetSpatialAudioAttenuationByUid 方法來調整當前用戶的空間音頻的衰減系數,也可以調用 SetSpatialAudioBlurByUid 方法來調整人聲模糊度的效果的。這兩個方法后面會展開來詳細講解的。

private async Task CreateRTCEngine()
{
var handler = new UserEventHandler();
handler.OnUserJoinedEvent += OnUserJoined;
//此處省略其它代碼行......
}

private void OnUserJoined(uint uid)
{
remoteUidList.Add(uid);
RemoteVoicePositionInfo positionInfo = new RemoteVoicePositionInfo() { position = new float[3], forward = new float[3] };
remotePositionInfoDict.Add(uid, positionInfo);
SetSpatialAudioBlurByUid(uid, ClientPrefs.GetVoiceBlur() > 0);
SetSpatialAudioAttenuationByUid(uid, ClientPrefs.GetAttenuation());
if (SpatialAudioEngine != null)
{
SpatialAudioEngine.UpdateRemotePosition(uid, positionInfo);
}
}

6.3 構建 APK 文件

如果你想在 Android 手機上運行的話,可以選擇將你的平臺選擇 “Android”,然后點擊 “Switch Platform”,等待平臺的切換。構建 APK 的時候,你可以根據你的運行環境決定是否需要勾選 Development Build 參數即可。這里我先勾選,點擊 "Build",設置 APK 的名字為 BossRoomHello.apk。

大家可以自行選擇 Game 窗口的分辨率大小,然后點擊運行項目,創建房間并加入后,進入游戲場景。我們發現在 Android 平臺時,游戲場景中多了可操作的虛擬搖桿,這是為了適配移動端平臺而專門添加的。你可以實驗下,通過搖桿來控制角色的前后左右移動。


6.4 啟用麥克風/聽筒

在加入隊伍、選擇角色、全體準備就緒后,會加載進入 BossRoom 的游戲場景。

6.4.1 查看麥克風/聽筒的UI控件

在場景中找到游戲對象 SettingsPanelCanvas,先來看看這里的 UI。

下拉菜單子對象 MicrophoneSettings 是用來控制開啟或者關閉麥克風的。

麥克風的 UI:MicrophoneSettings有兩個選項,閉麥或者關麥,默認的 Value 是 1,是開麥的狀態。

下拉菜單子對象 ReceiverSettings 是用來控制開啟或者關閉聽筒的。

聽筒的 UI:ReceiverSettings 有兩個選項,打開聽筒或者關閉聽筒,默認的 Value 是 1,是打開聽筒的狀態。

再來看看 SettingsPanelCanvas 對象上掛載的 UISettingsVoice.cs 腳本中的事件的封裝。

  • 腳本的 Start 方法中,已經提前注冊了當選項的值發生變化時要監聽的事件。

  • 當打開或者關閉麥克風時,會執行回調方法 OnMicrophoneChanged。在 OnMicrophoneChanged 方法中,首先會調用 ClientPrefs.cs 腳本中封裝的 SetMicrophoneState 的方法來保存當前的麥克風的狀態值。

  • 然后再調用 RTCAudioProxy.cs 腳本中的 EnableMicrophone 方法來控制是否開啟麥克風。

  • 開啟聽筒的腳本控制的原理也是這樣的,回調事件都封裝在方法 OnReceiverChanged 中。

private void OnMicrophoneChanged(int index)
{
ClientPrefs.SetMicrophoneState(index);

// todo: handle microphone off/on
HelloService.RTCAudioProxy.Instance.EnableMicrophone(index == 1);
}

private void OnReceiverChanged(int index)
{
ClientPrefs.SetReceiverState(index);

// todo: handle receiver off/on
HelloService.RTCAudioProxy.Instance.EnableRemoteAudio(index == 1);
}

6.4.2 查看ClientPrefs類中數據存儲和讀取

在前面的代碼中,可以看到選擇開啟或者關閉麥克風、聽筒時,選擇后的數值會在 ClientPrefs 類封裝的方法中進行存儲。

打開 ClientPrefs.cs 腳本后,可以看到腳本中定義了一些要保存的數據的鍵值 key,然后在自定義的方法中使用 Unity 默認的 PlayerPrefs 玩家偏好類,來存儲和讀取面板修改的麥克風、聽筒、空間音頻等相關的參數值。

public static class ClientPrefs
{
private const string k_MicrophoneKey = "MicrophoneState";
private const string k_ReceiverKey = "ReceiverState";

private const int k_DefaultMicrophoneState = 0;
    private const int k_DefaultReceiverState = 1;


public static int GetMicrophoneState()
{
return PlayerPrefs.GetInt(k_MicrophoneKey, k_DefaultMicrophoneState);
}

public static void SetMicrophoneState(int value)
{
PlayerPrefs.SetInt(k_MicrophoneKey, value);
}

public static int GetReceiverState()
{
return PlayerPrefs.GetInt(k_ReceiverKey, k_DefaultReceiverState);
}

public static void SetReceiverState(int value)
{
PlayerPrefs.SetInt(k_ReceiverKey, value);
    }
}

6.4.3 是否開啟麥克風

我們來查看下是否開啟麥克風的方法 EnableMicrophone:

如果打開了空間音頻,或者沒有打開空間音頻但是在語音通話頻道內時,我們會調用 MuteLocalAudioStream 方法來選擇取消或恢復發布本地音頻流。如果本地打開了麥克風,我們就發布本地音頻流;如果關閉了麥克風,就取消發布本地音頻流。

public void EnableMicrophone(bool enable)
{
if (isOpenSpatialAudio)
{
int r = SpatialAudioEngine.MuteLocalAudioStream(!enable);
Debug.Log($"SpatialAudioEngine EnableMicrophone:{enable} ## ret:{r}");
}
else if (RtcEngine != null)
{
int ret = RtcEngine.MuteLocalAudioStream(!enable);
Debug.Log($"EnableMicrophone:{enable} ## ret:{ret}");
}
}

6.4.4 是否開啟聽筒

再來查看下是否開啟聽筒的方法 EnableRemoteAudio,原理也是類似的。

在打開了空間音頻,或者沒有打開空間音頻但是在語音通話頻道內時,會調用 MuteAllRemoteAudioStreams 方法來選擇取消或恢復訂閱所有遠端用戶的音頻流。如果本地打開了麥克風,我們就訂閱遠端用戶的音頻流;如果關閉了麥克風,則取消訂閱遠端用戶的音頻流。

public void EnableRemoteAudio(bool enable)
{
if (isOpenSpatialAudio)
{
int r = SpatialAudioEngine.MuteAllRemoteAudioStreams(!enable);
Debug.Log($"SpatialAudioEngine EnableMicrophone:{enable} ## ret:{r}");
}
else if (RtcEngine != null)
{
int ret = RtcEngine.MuteAllRemoteAudioStreams(!enable);
Debug.Log($"EnableRemoteAudio:{enable} ## ret:{ret}");
}
}

然后運行游戲,打開/關閉麥克風、打開/關閉聽筒,測試兩個角色之間進行對話時,是否還能聽到對方的聲音。

7.測試開啟和關閉空間音頻的效果

接下來我們再來看看空間音頻的開啟和關閉的交互效果實現。

先找到場景中的 SettingsPanelCanvas 對象,查看子對象 SettingsButton 的 UI 按鈕,已經注冊了事件,會執行綁定的 UISettingsCanvas.cs 腳本中的 OnClickSettingsButton 方法。在腳本的該方法中,實現了點擊該按鈕會打開設置面板:

7.1 查看空間音頻的UI控件

在游戲的【基礎設置】界面,是控制游戲的背景音樂和各種音效的音量的,這里不再展開講解,大家可以自行查看腳本的代碼控制。

我們切換到【高級設置】界面,可以看到這個界面上的所有的 UI 控件都在場景中的 SettingsPanel/AdvancedSettings下面。

  • SpaceAudio 是開啟空間音頻效果的 UI;

  • RangeAudio 是設置語音接收范圍的 UI;

  • Attenuation 是調節衰減系數的 UI;

  • VoiceBlur 是開啟人聲模糊的 UI;

7.2 查看空間音頻的使用指南

  • 點擊【高級設置】界面的【指南】按鈕:


可以查看下空間音頻 & 范圍音頻的使用原理的介紹:


7.3 開啟空間音頻

當前項目中默認是開啟空間音頻的。只有啟用了空間音頻后,我們才能來調整空間音頻的語音接收范圍、衰減系數、人聲模糊的效果。

接下來看看腳本中啟用空間音頻的方法 EnableSpatialAudio:

public void EnableSpatialAudio()
{
if (RtcEngine != null)
{
RtcEngine.MuteLocalAudioStream(true);
RtcEngine.MuteAllRemoteAudioStreams(true);
if (SpatialAudioEngine == null)
{
SpatialAudioEngine = RtcEngine.GetLocalSpatialAudioEngine();
var ret = SpatialAudioEngine.Initialize();
Debug.Log("_spatialAudioEngine: Initialize " + ret);

//設置音頻屬性和場景 ####
RtcEngine.SetAudioProfile(AUDIO_PROFILE_TYPE.AUDIO_PROFILE_DEFAULT);
RtcEngine.SetAudioScenario(AUDIO_SCENARIO_TYPE.AUDIO_SCENARIO_GAME_STREAMING);
}
else
{
int ret = RtcEngine.EnableSpatialAudio(true);
Debug.Log($"EnableSpatialAudio ## ret:{ret}");
}
isOpenSpatialAudio = true;
SpatialAudioEngine.SetMaxAudioRecvCount(10);
SetSpatialAudioRecvRange(ClientPrefs.GetRangeAudioDistance());
SetSpatialAudioBlur(ClientPrefs.GetVoiceBlur() > 0);
SetSpatialAudioAttenuation(ClientPrefs.GetAttenuation());

SpatialAudioEngine.MuteLocalAudioStream(ClientPrefs.GetMicrophoneState() != 1);
SpatialAudioEngine.MuteAllRemoteAudioStreams(ClientPrefs.GetReceiverState() != 1);
}
}

如果語音引擎對象 RtcEngine 不為空時:先取消發布本地音頻流,并取消訂閱所有遠端用戶的音頻流。

情況1:如果空間音頻對象為空,獲取到本地的空間音頻對象并進行初始化,然后設置音頻的編碼屬性和場景。

SetAudioProfile 指的是設置音頻編碼屬性,包含采樣率、碼率、編碼模式和聲道數。

SetAudioScenario 指的是設置音頻場景。不同的音頻場景下,設備的音量類型是不同的,在這里選擇的是高音質場景 AUDIO_SCENARIO_GAME_STREAMING 的類型。

情況2:如果空間音頻對象不為空,直接啟用空間音頻。

接著使用 bool 記錄下當前的狀態:空間音頻已打開。同時要設置音頻接收范圍內最多可接收的音頻流數,設置空間音頻的各種參數(包括語音接收距離、人聲模糊、音頻衰減系數)。還會根據本地設置的是否開啟麥克風和聽筒,來決定是否發布本地采集的音頻流,是否取消訂閱所有遠端用戶的音頻流。

再來看看調用開啟空間音頻腳本的地方:

調用位置1:

當通過 UI 的下拉菜單選項選擇開啟或者關閉空間音頻時,會觸發 UI 注冊的回調方法 OnSpaceAudioChanged。方法中根據 value 的值來調用剛才封裝的方法 EnableSpatialAudio 來啟用空間音頻,同時關閉空間音頻時會執行方法 DisableSpatialAudio 。

private void OnSpaceAudioChanged(int value)
{
ClientPrefs.SetSpaceAudio(value);

// todo: handle space audio changed;
if (value == 0)
{
rangeAudioUpdated?.Invoke(0);
}
else
{
rangeAudioUpdated?.Invoke(ClientPrefs.GetRangeAudioDistance());
}
Debug.Log("Space Audio Changed: " + (value > 0 ? "Open" : "Close"));
if (value > 0)
{
HelloService.RTCAudioProxy.Instance.EnableSpatialAudio();
}
else
{
HelloService.RTCAudioProxy.Instance.DisableSpatialAudio();
}
}

調用位置2:

在 JoinChannel 的異步方法中,用戶加入頻道時,會根據存儲的是否開啟空間音頻的值,來決定是否啟用空間音頻的效果。

public async void JoinChannel(uint uid, bool enableSpatialAudio)
{
userId = uid;
if (RtcEngine == null)
{
await Init();
}
if (!isInChannel && RtcEngine != null)
{
await AuthTokenManager.ExternalLogin(userId.ToString());
await GenerateAccessToken(channelName, new HelloOptions { role = Role.Publisher });
int ret = RtcEngine.JoinChannel(accessToken, channelName, "", userId);
isInChannel = true;
Debug.Log($"JoinChannel ## ret:{ret}, userId: {userId}");
}
if (enableSpatialAudio)
{
Instance.EnableSpatialAudio();
}
else
{
Instance.DisableSpatialAudio();
}
}

7.4 關閉空間音頻

如果在設置面板上,將空間音頻選擇【關】的選項:

會在 Console 控制臺看到回調方法的打印輸出信息,顯示空間音頻已關閉。

在 Game 窗口中發現人物腳底的表示范圍的綠色圓圈就看不到了,同時我們也發現無論兩個角色之間距離有多遠,在任何地方都可以聽到對方的聲音,不再受空間距離的限制。


當空間音頻禁用時,ClientPrefs.GetSpaceAudio() 方法的返回值為0,也就是代碼的邏輯會執行 DisableSpatialAudio 禁用空間音頻的方法。

接著查看下DisableSpatialAudio 方法

  • 會更新下 bool 變量值的狀態為 false:表示當前空間音頻效果未開啟;

  • 如果空間音頻對象不為空的話,清除所有的遠端的位置信息,并取消發布本地采集的音頻流,同時取消訂閱所有遠端用戶的音頻流。

  • 然后調用 EnableSpatialAudio 方法,來語音通話時禁用空間音頻的效果;

  • 也是同樣的會根據本地設置的是否開啟麥克風和聽筒,來決定語音引擎對象是否發布本地采集的音頻流,以及是否取消訂閱所有遠端用戶的音頻流。


public void DisableSpatialAudio()
{
isOpenSpatialAudio = false;
if (RtcEngine != null)
{
if (SpatialAudioEngine != null)
{
SpatialAudioEngine.ClearRemotePositions();
SpatialAudioEngine.MuteLocalAudioStream(true);
SpatialAudioEngine.MuteAllRemoteAudioStreams(true);
}
int ret = RtcEngine.EnableSpatialAudio(false);
Debug.Log($"DisableSpatialAudio ## ret:{ret}");

RtcEngine.MuteLocalAudioStream(ClientPrefs.GetMicrophoneState() != 1);
RtcEngine.MuteAllRemoteAudioStreams(ClientPrefs.GetReceiverState() != 1);
}
}

然后我們可以再次運行游戲,測試下空間音頻由開著到關閉后的聲音效果。

8. 調整空間音頻參數

8.1 調整語音接收范圍

打開下拉菜單,可以看到語音接收范圍分為近、中、遠,默認參數選擇的是近的范圍:


當我們修改這里的下拉菜單的選項值時,通過查看 Console 控制臺的日志信息 “Range Audio Changed”,可以看到會自動回調 UI 注冊好的方法 OnRangeAudioChanged。

  • 在該方法中,會存儲下當前選擇的參數值是近或者中或者遠。

  • 也會自動回調方法 GetRangeAudioDistance 來處理當前語音接收的范圍。在腳本中提前設定的分別是角色的周圍 5米、10米、15米,放在了數組 s_RangeAudioDistances 中,大家可以自行修改。


private void OnRangeAudioChanged(int value)
{
if (value < 0 || value > 2)
{
Debug.Log("Invalid value of range audio: " + value);
return;
}

ClientPrefs.SetRangeAudio(value);

// todo: handle space audio changed;
rangeAudioUpdated?.Invoke(ClientPrefs.GetRangeAudioDistance());
Debug.Log("Range Audio Changed: " + value);
HelloService.RTCAudioProxy.Instance.SetSpatialAudioRecvRange(ClientPrefs.GetRangeAudioDistance());
}

同時也會調用腳本中封裝的方法 SetSpatialAudioRecvRange,將當前語音接收范圍參數的 range 值來應用到實時語音通話效果中。

public void SetSpatialAudioRecvRange(float range)
{
if (SpatialAudioEngine != null)
{
int ret = SpatialAudioEngine.SetAudioRecvRange(range);
Debug.Log($"SetSpatialAudioRecvRange ## ret:{ret}");
}
}

使用不同的語音接收范圍

接著我們關閉設置的 UI 面板,回到 Game 場景中,看到角色 Player 的腳底會有一個綠圈。

我們來查看下 Player 腳底的表示語音接收范圍的綠圈,可以看到它是使用 Unity 的 LineRenderer 組件繪制出來的,繪制圓圈的代碼在角色身上的 PlayerVoiceRange.cs 腳本中,大家可以自行查看繪制的實現思路。

將空間音頻的語音接收范圍,由【近】改為【中】:

將范圍音頻的接收范圍,由【近】改為【中】以后,Game窗口中的語音范圍的綠圈的變化:


將范圍音頻的接收范圍,由【中】改為【遠】以后,Game窗口中的語音范圍的綠圈的變化:


8.2 調整空間音頻的衰減系數

設置音頻衰減系數

衰減系數:用來控制聲音隨著距離的衰減的快慢程度,取值范圍為0~1,數值越大的話,衰減越快。這里初始設置的衰減系數為 0.1。


根據日志信息可以看到,當衰減系數的 Slider 的 UI 發生變化時,會執行回調方法 OnAttenuationSliderChanged。

  • 在 OnAttenuationSliderChanged 方法中,通過調用 ClientPrefs.cs 腳本的 SetAttenuation 方法,會保存記錄下當前的衰減系數值。

  • 然后再調用 RTCAudioProxy.cs 腳本中的 SetSpatialAudioAttenuation 方法來調整空間音頻的衰減效果。


private void OnAttenuationSliderChanged(float newValue)
{
var value = Mathf.Round(newValue * 10.0f) / 10.0f;
m_AttenuationSlider.value = value;
ClientPrefs.SetAttenuation(value);

// todo: handle audio attenuation changed;
Debug.Log("Attenuation Changed: " + value);

調整:所有遠端用戶的音頻衰減系數

我們來查看下 RTCAudioProxy.cs 方法,在該方法中,會遍歷集合 remoteUidList 中的所有的遠端用戶,通過調用 SetRemoteAudioAttenuation 方法,來設置所有遠端用戶的聲音衰減效果。

public void SetSpatialAudioAttenuation(double value)
{
if (RtcEngine != null && SpatialAudioEngine != null)
{
foreach (var uid in remoteUidList)
{
int ret = SpatialAudioEngine.SetRemoteAudioAttenuation(uid, value, false);
Debug.Log($"SetSpatialAudioAttenuation ## uid:{uid} ## ret:{ret}");
}
}
}

調整:指定遠端用戶的音頻衰減系數

如果想要調整某一個遠端用戶的音頻衰減系數的話,可以查看下 OnUserJoined 的方法。在該方法中,當某一個遠端用戶上線加入頻道時,同時會調用方法 SetSpatialAudioAttenuationByUid,這樣就可以單獨來設置某一個用戶的空間音頻的衰減系數了。

private void SetSpatialAudioAttenuationByUid(uint uid, double value)
{
if (SpatialAudioEngine != null)
{
SpatialAudioEngine.SetRemoteAudioAttenuation(uid, value, false);
}
}

此時,運行游戲可以測試下,設置不同的音頻衰減系數后,聽到的對話音頻的效果。

8.3 啟用人聲模糊

是否開啟人聲模糊

人聲模糊:是指讓說話人的聲音進行人工模糊處理,讓聲音變得不清楚,無法理解內容,但是又沒有完全關閉這個聲音。這里設置的默認是關閉人聲模糊效果的。


同理當人聲模糊的下拉菜單的 UI 發生變化時,會自動回調方法 OnVoiceBlurChanged。


在 OnVoiceBlurChanged 方法中,通過調用 ClientPrefs.cs 腳本的 SetVoiceBlur 方法,會保存記錄下當前選擇的結果。然后再調用 RTCAudioProxy.cs 腳本中的 SetSpatialAudioBlur 方法來調整人聲模糊的效果。

private void OnVoiceBlurChanged(int value)
{
ClientPrefs.SetVoiceBlur(value);

// todo: handle voice blur changed;
Debug.Log("Voice Blur Changed: " + (value > 0 ? "Open" : "Close"));
HelloService.RTCAudioProxy.Instance.SetSpatialAudioBlur(value > 0);
}

調整:所有遠端用戶的人聲模糊的效果

我們來查看下 RTCAudioProxy.cs 腳本的 SetSpatialAudioBlur 方法,在該方法中,會遍歷集合 remoteUidList 中的所有的遠端用戶,通過調用 SetRemoteUserSpatialAudioParams 方法,來設置所有遠端用戶是否開啟人聲模糊的效果。

其中空間音頻的 enable_blur 參數表示是否開啟聲音模糊的處理,如果為 true,表示開啟模糊處理。

public void SetSpatialAudioBlur(bool enable)
{
if (RtcEngine != null)
{
SpatialAudioParams audioParams = new SpatialAudioParams();
audioParams.enable_blur.SetValue(enable);
foreach (var uid in remoteUidList)
{
int ret = RtcEngine.SetRemoteUserSpatialAudioParams(uid, audioParams);
Debug.Log($"SetSpatialAudioBlur ## uid:{uid} ## ret:{ret}");
}
}
}

調整:指定遠端用戶的人聲模糊的效果

如果想要調整某一個遠端用戶的人聲模糊效果的話,可以查看下 OnUserJoined 的方法。在該方法中,當某一個遠端用戶上線加入頻道時,同時會調用方法 SetSpatialAudioBlurByUid,這樣就可以單獨來設置某一個用戶的人聲模糊效果了。

private void SetSpatialAudioBlurByUid(uint uid, bool enable)
{
if (SpatialAudioEngine != null)
{
SpatialAudioParams audioParams = new SpatialAudioParams();
audioParams.enable_blur.SetValue(enable);
RtcEngine.SetRemoteUserSpatialAudioParams(uid, audioParams);
}
}

此時,運行游戲可以測試下,設置開啟人聲模糊的效果后,是否還能聽清楚對方說話的聲音。

8.4 調整通話音量

SDK 支持對采集和播放的音頻音量進行調整,以滿足用戶實際應用場景。例如,進行雙人通話時,需要靜音遠端用戶,可以通過調整播放音量的方法將音量設置為 0。

我們在項目中已經實現了基本的實時音頻功能,現在來試試調整通話音量的功能。找到腳本中的 CreateRTCEngine 方法:

  • AdjustPlaybackSignalVolume:用來調節本地播放的所有遠端用戶信號音量。

  • AdjustRecordingSignalVolume:用來調節麥克風采集的信號音量。

  • 它們的取值范圍都是 [0,400],0 表示靜音,100 表示是默認的原始音量,400 表示是原始音量的 4 倍,自帶溢出保護。

RtcEngine.AdjustPlaybackSignalVolume(300);
RtcEngine.AdjustRecordingSignalVolume(200);

9. 用戶離開頻道時的事件

9.1 當遠端用戶離線時

當某一個遠端用戶自己主動離線時,在 Game 界面會有彈窗提示對方已離開隊伍的。當然,用戶后面還可以再次重新上線加入這個頻道的。如果在語音通信過程中,如果因網絡問題導致連接斷開,Hello SDK 會自動開啟斷線重連機制的。

此時在 Console 控制臺可以看到用戶下線時的日志信息 “OnUserOffline”,這時會自動回調方法 OnUserOffline。


public override void OnUserOffline(RtcConnection connection, uint remoteUid, USER_OFFLINE_REASON_TYPE reason)
{
Debug.Log("OnUserOffline " + connection.channelId + " " + remoteUid);
OnUserOfflineEvent?.Invoke(remoteUid);
base.OnUserOffline(connection, remoteUid, reason);
}

我們還自定義了一個 OnUserOffline 方法,該方法已經在最初進行初始化語音引擎的時候注冊給了 handle 對象。

在 OnUserOffline 方法中,會將遠端用戶從 remoteUidList 集合和 remotePositionInfoDict 字典中移除;離開頻道后,為避免計算資源的浪費,還需要調用 RemoveRemotePosition 方法來刪除指定遠端用戶的空間位置信息。否則,該用戶的空間位置信息會一直被保存。

private void OnUserOffline(uint uid)
{
remoteUidList.Remove(uid);
remotePositionInfoDict.Remove(uid);
if (SpatialAudioEngine != null)
{
SpatialAudioEngine.RemoveRemotePosition(uid);
}
}

9.2 離開頻道

當本地客戶端用戶離開語音頻道時,查看下此時的 Console 控制臺的輸出日志信息。

看到此時輸出信息有 “LeaveChannel ## ret”, 因為此時調用了自定義封裝的LeaveChannel方法。

當調用 RtcEngine.LeaveChannel 方法后,SDK 會終止音頻互動、離開當前頻道,并同時會釋放會話相關的所有資源的。同時這里也要處理離開頻道后的行為,比如禁用空間音頻、清空遠端用戶的集合列表、清空遠端用戶的位置信息集合、重置是否還在頻道內的標志變量等。

public void LeaveChannel()
{
if (isInChannel && RtcEngine != null)
{
int ret = RtcEngine.LeaveChannel();
Debug.Log($"LeaveChannel ## ret:{ret}");
DisableSpatialAudio();
remoteUidList.Clear();
remotePositionInfoDict.Clear();
isInChannel = false;
}
}

我們還可以看到 Console 控制臺打印輸出的還有另外一個日志信息 “Leave Channel Success”,因為此時自動回調了方法 OnLeaveChannel。

public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
{
Debug.Log("Leave Channel Success" + connection.channelId + " " + connection.localUid);
}

9.3 銷毀RTC引擎

考慮到優化的處理,可以在用戶需要時才進行實時音頻通信,不需要時則將資源釋放出來用于其它操作。所以我們通過調用 Dispose 的方法來釋放 SDK 使用的所有資源。

private void DestroyRTCEngine()
{
if (RtcEngine != null)
{
RtcEngine.DisableAudio();
RtcEngine.Dispose();
RtcEngine = null;
}
}
private void DestroySpatialAudioEngine()
{
if (SpatialAudioEngine != null)
{
SpatialAudioEngine.Dispose();
SpatialAudioEngine = null;
}
}
private void OnDestroy()
{
DestroySpatialAudioEngine();
DestroyRTCEngine();
}

寄語

UOS Hello 作為 Unity 官方與聲網合作推出的游戲內置實時語音服務,廣泛支持多類型游戲及多平臺互通,未來還將集成 WWISE ——這一業界領先、功能強大的游戲音頻中間件,它不僅提供高度的自定義性和強大的實時處理能力,還以其豐富的音頻效果和高效的資源管理,為游戲語音功能提供更全面、高效的解決方案,進一步提升游戲的音頻品質和玩家的沉浸感。

學習途徑

UOS 配套的相關學習教程視頻也已同步上傳至 Unity 中文課堂和 B 站,搜索“使用Hello輕松暢享游戲內多人實時語音互動”即可找到,歡迎大家前往學習,UOS 更多學習教程持續更新中,敬請期待!

Unity Online Services (UOS) 是一個專為游戲開發者設計的一站式游戲云服務平臺,提供覆蓋游戲全生命周期的開發、運營和推廣支持。

了解更多 UOS 相關信息:

官網:https://uos.unity.cn

技術交流 QQ 群:823878269

公眾號:UOS 游戲云服務

Unity 官方微信

第一時間了解Unity引擎動向,學習進階開發技能

每一個“點贊”、“在看”,都是我們前進的動力

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關推薦
熱點推薦
G4前不會回歸!勇士官宣庫里至少傷停一周

G4前不會回歸!勇士官宣庫里至少傷停一周

雷速體育
2025-05-08 06:12:11
1-2!又是四大皆空的賽季 11.3億豪門無緣歐冠決賽 隊史首冠夢碎

1-2!又是四大皆空的賽季 11.3億豪門無緣歐冠決賽 隊史首冠夢碎

狍子歪解體壇
2025-05-08 05:06:24
巴鐵剛擊落印戰機,中國就對印出手,中巴“雙打”讓莫迪文武雙輸

巴鐵剛擊落印戰機,中國就對印出手,中巴“雙打”讓莫迪文武雙輸

說天說地說實事
2025-05-08 01:59:07
土耳其專家: 法國陣風戰斗機輸給中國殲10CE,是因為沒有原代碼!

土耳其專家: 法國陣風戰斗機輸給中國殲10CE,是因為沒有原代碼!

現代春秋
2025-05-08 04:58:43
劉霞,任上被查

劉霞,任上被查

新京報政事兒
2025-05-07 16:52:11
撒謊了?巴鐵公布殲10打敗陣風還原圖,現場有一款不該出現的軍機

撒謊了?巴鐵公布殲10打敗陣風還原圖,現場有一款不該出現的軍機

青輝
2025-05-07 21:45:06
五一的“虛假繁榮”,各地人山人海消費卻降低了,百姓咋不花錢了

五一的“虛假繁榮”,各地人山人海消費卻降低了,百姓咋不花錢了

阿鳧愛吐槽
2025-05-07 11:06:31
厲害了!6天新公司拿下水庫經營權,1500萬認繳資本撬動2.6億項目

厲害了!6天新公司拿下水庫經營權,1500萬認繳資本撬動2.6億項目

火山詩話
2025-05-07 13:41:25
信息量巨大,這次放水完全不一樣

信息量巨大,這次放水完全不一樣

大貓財經Pro
2025-05-07 14:48:42
美財長回應美中會談:只是緩和局勢,如何談判由特朗普定

美財長回應美中會談:只是緩和局勢,如何談判由特朗普定

北美商業電訊
2025-05-07 17:15:27
恩里克:我們是農民聯賽,但淘汰了4支英超球隊

恩里克:我們是農民聯賽,但淘汰了4支英超球隊

雷速體育
2025-05-08 07:48:41
饒毅傷害了一位杰出女性,而且傷害了三次

饒毅傷害了一位杰出女性,而且傷害了三次

清暉有墨
2025-05-07 14:33:43
又是20分逆轉!尼克斯2-0凱爾特人,唐斯21+17塔圖姆致命失誤

又是20分逆轉!尼克斯2-0凱爾特人,唐斯21+17塔圖姆致命失誤

湖人崛起
2025-05-08 09:31:20
央視《刑警的日子》被觀眾要求下架,理由出奇一致:毀了警察形象

央視《刑警的日子》被觀眾要求下架,理由出奇一致:毀了警察形象

她時尚丫
2025-05-07 18:13:16
不舍!廣州一知名酒樓宣布即將停業!街坊:有太多的回憶……

不舍!廣州一知名酒樓宣布即將停業!街坊:有太多的回憶……

城事特搜
2025-05-07 20:03:36
笑死了!記者暗訪貴陽各臺球城的女陪練,直接問有沒有特殊服務…

笑死了!記者暗訪貴陽各臺球城的女陪練,直接問有沒有特殊服務…

火山詩話
2025-05-08 06:56:22
中央決定:公職人員出差伙食費、交通費、住宿費、會議費標準!

中央決定:公職人員出差伙食費、交通費、住宿費、會議費標準!

小江網評
2025-05-07 22:59:08
金主花400萬包養Coser卻遭出軌,帶前男友回家做,發現時正穿褲子

金主花400萬包養Coser卻遭出軌,帶前男友回家做,發現時正穿褲子

社會醬
2025-05-07 17:40:21
隨著巴黎3-1阿森納,挺進歐冠決賽,產生4大不可思議+3個不爭事實

隨著巴黎3-1阿森納,挺進歐冠決賽,產生4大不可思議+3個不爭事實

侃球熊弟
2025-05-08 05:15:40
浙江30歲獨自爬山男子已找到,遺體在水域發現,事發地曾多人被困

浙江30歲獨自爬山男子已找到,遺體在水域發現,事發地曾多人被困

墜入二次元的海洋
2025-05-08 00:29:36
2025-05-08 10:04:49
Unity incentive-icons
Unity
Unity中國官方帳戶
2300文章數 6718關注度
往期回顧 全部

游戲要聞

GTA6新預告是PS5純實機!不敢想上PC后畫質有多好!

頭條要聞

國泰航空空姐誤給3歲男童白葡萄酒 家屬:她一直未道歉

頭條要聞

國泰航空空姐誤給3歲男童白葡萄酒 家屬:她一直未道歉

體育要聞

未來是你們這些年輕人的,但現在還不行!

娛樂要聞

出道15年零緋聞,被劉濤贊揚演技的他

財經要聞

特朗普修改AI芯片出口管制?美商務部回應

科技要聞

蘋果宣布重大計劃 谷歌市值蒸發1500億美元

汽車要聞

《臺州宣言》再進一步 吉利汽車將全資控股極氪

態度原創

教育
本地
健康
游戲
公開課

教育要聞

走上學校管理崗,一定要學會布置工作

本地新聞

為什么太行山上長滿了韓國人?

唇皰疹和口腔潰瘍是"同伙"嗎?

經典RPG游戲《空之軌跡 the 1st》將于9月19日發售

公開課

李玫瑾:為什么性格比能力更重要?

無障礙瀏覽 進入關懷版 主站蜘蛛池模板: 沂水县| 乌鲁木齐市| 同仁县| 株洲县| 绥德县| 汝阳县| 榕江县| 香港| 额尔古纳市| 五家渠市| 牙克石市| 广昌县| 东源县| 江北区| 普兰县| 昌吉市| 东乡| 新河县| 大同县| 乌拉特中旗| 海安县| 任丘市| 陵川县| 浙江省| 宝坻区| 革吉县| 溆浦县| 夏邑县| 无棣县| 当涂县| 三原县| 宁乡县| 绥棱县| 车险| 囊谦县| 水城县| 临沂市| 门头沟区| 松桃| 仁寿县| 南安市|