From 7056ef18ce43e0f28d2d3223642c562c3929c093 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Wed, 7 Jul 2021 12:21:37 +0200 Subject: [PATCH] Moved chat lobby interaction to generic EOSVoiceChat class --- Assets/Scripts/EOSVoiceChat.cs | 187 +++++++++++++++++++++++++ Assets/Scripts/EOSVoiceChat.cs.meta | 11 ++ Assets/Scripts/MagnificentVoiceChat.cs | 91 ++---------- 3 files changed, 210 insertions(+), 79 deletions(-) create mode 100644 Assets/Scripts/EOSVoiceChat.cs create mode 100644 Assets/Scripts/EOSVoiceChat.cs.meta diff --git a/Assets/Scripts/EOSVoiceChat.cs b/Assets/Scripts/EOSVoiceChat.cs new file mode 100644 index 0000000..2fc8341 --- /dev/null +++ b/Assets/Scripts/EOSVoiceChat.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Epic.OnlineServices; +using Epic.OnlineServices.Lobby; +using Epic.OnlineServices.RTC; +using Epic.OnlineServices.RTCAudio; +using UnityEngine; + +/// +/// Generic re-usable cross-platform class to provide common voice chat functionality based on the EOS Voice Chat service. +/// This class does not know anything about EOS platform initialization or authentication; it just takes the required +/// EOS interfaces and exposes a number of game-related voice functions. +/// +public class EOSVoiceChat +{ + private const uint DefaultMaxChatPlayers = 16; // "Lobbies that generate conference rooms must have <= 16 max players" + + private readonly LobbyInterface lobbyInterface; + private readonly RTCInterface rtcInterface; + private readonly RTCAudioInterface audioInterface; + private readonly Func productUserProvider; + + private string connectedLobbyId; + public bool IsConnected => lobbyInterface != null && rtcInterface != null && audioInterface != null && !string.IsNullOrEmpty(connectedLobbyId); + + /// + /// Provide the required interfaces for voice chat. Product User ID is provided through a callback, so that the + /// same instance of this class can remain in use even if the logged in user changes. + /// + public EOSVoiceChat( + LobbyInterface lobbyInterface, RTCInterface rtcInterface, RTCAudioInterface audioInterface, + Func productUserProvider) + { + this.lobbyInterface = lobbyInterface; + this.rtcInterface = rtcInterface; + this.audioInterface = audioInterface; + this.productUserProvider = productUserProvider; + } + + /// + /// Join an existing chat lobby or create a new one. + /// The completion callback is invoked on both success and failure, and can be used to set up the chat lobby's initial parameters. + /// + public void ConnectToChat(string chatLobbyName, Action onCompleted = null, uint maxChatPlayers = DefaultMaxChatPlayers) + { + DisconnectChat(); // Leave any currently connected chat lobby + + lobbyInterface.CreateLobbySearch(new CreateLobbySearchOptions { MaxResults = 1 }, out var searchHandle); + searchHandle.SetLobbyId(new LobbySearchSetLobbyIdOptions { LobbyId = chatLobbyName }); + + var localUserId = productUserProvider.Invoke(); + searchHandle.Find(new LobbySearchFindOptions { LocalUserId = localUserId }, null, findData => + { + switch (findData.ResultCode) + { + case Result.Success: + searchHandle.CopySearchResultByIndex(new LobbySearchCopySearchResultByIndexOptions { LobbyIndex = 0 }, out var lobbyDetails); + + Debug.Log("Found existing lobby, joining..."); + lobbyInterface.JoinLobby + ( + new JoinLobbyOptions + { + LocalUserId = localUserId, + LobbyDetailsHandle = lobbyDetails, + PresenceEnabled = false, + }, + null, + data => HandleLobbyJoined(data, onCompleted) + ); + break; + default: + Debug.Log($"Creating new chat lobby..."); + lobbyInterface.CreateLobby + ( + new CreateLobbyOptions + { + LocalUserId = localUserId, + AllowInvites = false, + PermissionLevel = LobbyPermissionLevel.Publicadvertised, + PresenceEnabled = false, + MaxLobbyMembers = maxChatPlayers, + DisableHostMigration = false, + LobbyId = chatLobbyName, + BucketId = Application.productName, // TODO: do we need anything more specific than this? + EnableRTCRoom = true, + }, + chatLobbyName, + data => HandleLobbyCreated(data, onCompleted) + ); + break; + } + }); + } + + private void HandleLobbyCreated(CreateLobbyCallbackInfo data, Action onCompleted) + { + switch (data.ResultCode) + { + case Result.Success: + connectedLobbyId = data.LobbyId; + Debug.Log($"Chat lobby creation successful, lobby ID = {connectedLobbyId}"); + onCompleted?.Invoke(true); + break; + case Result.LobbyLobbyAlreadyExists: + // This can happen if two clients try to create the same lobby at the same time, a classic race condition. + // Try to join the other client's newly created chat lobby instead. + connectedLobbyId = null; + Debug.Log("Chat lobby already exists, attempting to join it..."); + ConnectToChat((string)data.ClientData, onCompleted); + break; + default: + connectedLobbyId = null; + Debug.LogError($"Chat lobby creation failed, result code = {data.ResultCode}"); + onCompleted?.Invoke(false); + break; + } + } + + private void HandleLobbyJoined(JoinLobbyCallbackInfo data, Action onCompleted) + { + switch (data.ResultCode) + { + case Result.Success: + connectedLobbyId = data.LobbyId; + Debug.Log($"Chat lobby joined successfully, lobby ID = {connectedLobbyId}"); + onCompleted?.Invoke(true); + break; + default: + connectedLobbyId = null; + Debug.LogError($"Chat lobby join failed, result code = {data.ResultCode}"); + onCompleted?.Invoke(false); + break; + } + } + + public void DisconnectChat() + { + if (!IsConnected) + return; + + lobbyInterface.LeaveLobby + ( + new LeaveLobbyOptions + { + LocalUserId = productUserProvider.Invoke(), + LobbyId = connectedLobbyId, + }, + null, + data => { } + ); + + connectedLobbyId = null; + } + + /// + /// Mute or unmute the local player's voice chat. This can be used to implement push-to-talk. + /// + public void SetLocalMuted(bool muted) + { + if (!IsConnected) + return; + + + } + + /// + /// Mute or unmute a specific remove player. This can be used to filter out specific players in the chat lobby, + /// or for manually muting toxic players. + /// + public void SetRemoteMuted(string playerId, bool muted) + { + if (!IsConnected) + return; + } + + /// + /// Set all remote players to muted. This can be used during loading screens, or to initialize the chat lobby + /// when chatting with only a small subset of players. + /// + public void MuteAllRemote() + { + if (!IsConnected) + return; + } +} diff --git a/Assets/Scripts/EOSVoiceChat.cs.meta b/Assets/Scripts/EOSVoiceChat.cs.meta new file mode 100644 index 0000000..389805c --- /dev/null +++ b/Assets/Scripts/EOSVoiceChat.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f668ddd1628206e4b83baeb572784acd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MagnificentVoiceChat.cs b/Assets/Scripts/MagnificentVoiceChat.cs index 89b5a58..85c165c 100644 --- a/Assets/Scripts/MagnificentVoiceChat.cs +++ b/Assets/Scripts/MagnificentVoiceChat.cs @@ -33,7 +33,7 @@ public class MagnificentVoiceChat : MonoBehaviour private EpicAccountId localEpicAccountId; private ProductUserId localProductUserId; - private string lobbyId; + private EOSVoiceChat voiceChat; private string XAudio29DllPath => #if UNITY_EDITOR @@ -125,6 +125,8 @@ public class MagnificentVoiceChat : MonoBehaviour Credentials = GetEpicCredentials(), ScopeFlags = AuthScopeFlags.BasicProfile, }, null, HandleLoginResult); + + voiceChat = new EOSVoiceChat(lobbyInterface, rtcInterface, audioInterface, () => localProductUserId); } private Credentials GetEpicCredentials() // This is platform-specific @@ -213,80 +215,16 @@ public class MagnificentVoiceChat : MonoBehaviour private void CreateOrJoinVoiceLobby() { - lobbyInterface.CreateLobbySearch(new CreateLobbySearchOptions { MaxResults = 1 }, out var searchHandle); - searchHandle.SetLobbyId(new LobbySearchSetLobbyIdOptions { LobbyId = DebugLobbyId }); - searchHandle.Find(new LobbySearchFindOptions { LocalUserId = localProductUserId }, null, findData => + voiceChat.ConnectToChat(DebugLobbyId, success => { - switch (findData.ResultCode) - { - case Result.Success: - searchHandle.CopySearchResultByIndex(new LobbySearchCopySearchResultByIndexOptions { LobbyIndex = 0 }, out var lobbyDetails); - - Debug.Log("Found existing lobby, joining..."); - status.AppendLine("Found existing lobby, joining..."); - - lobbyInterface.JoinLobby(new JoinLobbyOptions - { - LocalUserId = localProductUserId, - LobbyDetailsHandle = lobbyDetails, - PresenceEnabled = false, - }, null, HandleLobbyJoined); - - break; - default: - Debug.Log("Creating new lobby..."); - status.AppendLine("Creating new lobby..."); - - // Lobby creation options should probably be platform-specific; generated lobby ID and max members depends on the chat scheme - lobbyInterface.CreateLobby(new CreateLobbyOptions - { - LocalUserId = localProductUserId, - AllowInvites = false, - PermissionLevel = LobbyPermissionLevel.Publicadvertised, - PresenceEnabled = false, - MaxLobbyMembers = 16, // "Lobbies that generate conference rooms must have <= 16 max players" - DisableHostMigration = false, - LobbyId = DebugLobbyId, - BucketId = DebugBucketId, // Something that combines front, game mode, region, etc (or maybe just the room ID) - EnableRTCRoom = true, - }, null, HandleLobbyCreated); - break; - } + Debug.Log($"Chat lobby connect result = {success}"); + if (success) + status.AppendLine("Chat lobby successfully connected!"); + else + status.AppendLine("Chat lobby connect failure..."); }); } - private void HandleLobbyCreated(CreateLobbyCallbackInfo data) - { - switch (data.ResultCode) - { - case Result.Success: - lobbyId = data.LobbyId; - Debug.Log("Lobby creation successful: " + lobbyId); - status.AppendLine("Lobby creation successful: " + lobbyId); - break; - default: - Debug.Log("Lobby creation failed, result code = " + data.ResultCode); - status.AppendLine("Lobby creation failed"); - break; - } - } - - private void HandleLobbyJoined(JoinLobbyCallbackInfo data) - { - switch (data.ResultCode) - { - case Result.Success: - lobbyId = data.LobbyId; - Debug.Log("Lobby joined successfully: " + lobbyId); - status.AppendLine("Lobby joined successfully: " + lobbyId); - break; - default: - Debug.Log("Lobby join failed, result code = " + data.ResultCode); - status.AppendLine("Lobby join failed"); - break; - } - } - void Update() { platformInterface.Tick(); @@ -294,15 +232,10 @@ public class MagnificentVoiceChat : MonoBehaviour private void OnDestroy() { - if (lobbyInterface != null && lobbyId != null) + if (voiceChat != null) { - lobbyInterface.LeaveLobby(new LeaveLobbyOptions - { - LocalUserId = localProductUserId, - LobbyId = lobbyId, - }, null, data => { }); - - lobbyId = null; + voiceChat.DisconnectChat(); + voiceChat = null; } if (platformInterface != null)