diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Prefabs/Characters/Avatar.prefab b/Basic/DistributedAuthoritySocialHub/Assets/Prefabs/Characters/Avatar.prefab index fbab99b0b..3d35a65c0 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Prefabs/Characters/Avatar.prefab +++ b/Basic/DistributedAuthoritySocialHub/Assets/Prefabs/Characters/Avatar.prefab @@ -17,6 +17,7 @@ GameObject: - component: {fileID: 539700041658036575} - component: {fileID: 4655812346339390596} - component: {fileID: 7543523986693376117} + - component: {fileID: 4616764017061829939} m_Layer: 0 m_Name: Avatar m_TagString: Player @@ -53,7 +54,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 3001504883 + GlobalObjectIdHash: 2473698027 InScenePlacedSourceGlobalObjectIdHash: 0 DeferredDespawnTick: 0 Ownership: 0 @@ -203,7 +204,6 @@ MonoBehaviour: m_AvatarNetworkAnimator: {fileID: 7543523986693376117} m_AnimationEventRelayer: {fileID: 3050276823525430539} m_MainCollider: {fileID: 36137897785899347} - m_AvatarInputs: {fileID: 0} m_PickupLocFixedJoint: {fileID: 3522541554063944713} m_PickupLocChild: {fileID: 1191881578848548316} m_LeftHandContact: {fileID: 8336303498504233401} @@ -297,6 +297,19 @@ MonoBehaviour: TransitionIndex: 1 m_Animator: {fileID: 7006295767383679619} m_PhysicsPlayerController: {fileID: 4655812346339390596} +--- !u!114 &4616764017061829939 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5478015027486214707} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: bee830f09ba1465fa953159de83e882e, type: 3} + m_Name: + m_EditorClassIdentifier: + ShowTopMostFoldoutHeaderGroup: 1 --- !u!1 &9009864878170659705 GameObject: m_ObjectHideFlags: 0 diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/GameplayEventHandler.cs b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/GameplayEventHandler.cs index 56ca5cf6d..e7a2989b7 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/GameplayEventHandler.cs +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/GameplayEventHandler.cs @@ -1,7 +1,7 @@ using Unity.Netcode; -using UnityEngine; using System; using System.Threading.Tasks; +using Unity.Services.Vivox; using UnityEngine.SceneManagement; namespace Unity.Multiplayer.Samples.SocialHub.GameManagement @@ -17,8 +17,9 @@ static class GameplayEventHandler internal static event Action OnExitedSession; internal static event Action OnTextMessageReceived; internal static event Action OnSendTextMessage; - internal static event Action OnBlockPlayerControls; - internal static event Action OnChatIsReady; + internal static event Action OnChatIsReady; + internal static event Action OnParticipantJoinedVoiceChat; + internal static event Action OnParticipantLeftVoiceChat; internal static void NetworkObjectDespawned(NetworkObject networkObject) { @@ -75,14 +76,19 @@ internal static void SendTextMessage(string message) OnSendTextMessage?.Invoke(message); } - public static void BlockPlayerControls(bool disable) + public static void SetTextChatReady(bool enabled, string channelName) { - OnBlockPlayerControls?.Invoke(disable); + OnChatIsReady?.Invoke(enabled, channelName); } - public static void SetTextChatReady(bool enabled) + public static void ParticipantJoinedVoiceChat(VivoxParticipant vivoxParticipant) { - OnChatIsReady?.Invoke(enabled); + OnParticipantJoinedVoiceChat?.Invoke(vivoxParticipant); + } + + public static void ParticipantLeftVoiceChat(VivoxParticipant vivoxParticipant) + { + OnParticipantLeftVoiceChat?.Invoke(vivoxParticipant); } } } diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/Unity.Multiplayer.Samples.SocialHub.GameManagement.asmdef b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/Unity.Multiplayer.Samples.SocialHub.GameManagement.asmdef index 5c21da73f..abdc18ca3 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/Unity.Multiplayer.Samples.SocialHub.GameManagement.asmdef +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/GameManagement/Unity.Multiplayer.Samples.SocialHub.GameManagement.asmdef @@ -3,7 +3,9 @@ "rootNamespace": "Unity.Multiplayer.Samples.SocialHub", "references": [ "GUID:1491147abca9d7d4bb7105af628b223e", - "GUID:c15e7f658578345fcb824b0a64d4dbe8" + "GUID:c15e7f658578345fcb824b0a64d4dbe8", + "GUID:6087a74f6015aae4daed9a2577a7596c" + ], "includePlatforms": [], "excludePlatforms": [], @@ -14,4 +16,4 @@ "defineConstraints": [], "versionDefines": [], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Player/AvatarTransform.cs b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Player/AvatarTransform.cs index b4b9b2be9..83cbeb09e 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Player/AvatarTransform.cs +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Player/AvatarTransform.cs @@ -1,7 +1,5 @@ using System; using Unity.Collections; -using Unity.Multiplayer.Samples.SocialHub.GameManagement; -using Unity.Multiplayer.Samples.SocialHub.Gameplay; using UnityEngine; using Unity.Multiplayer.Samples.SocialHub.Input; using Unity.Multiplayer.Samples.SocialHub.Physics; @@ -27,6 +25,8 @@ class AvatarTransform : PhysicsObjectMotion, INetworkUpdateSystem PlayersTopUIController m_TopUIController; NetworkVariable m_PlayerName = new NetworkVariable(string.Empty, readPerm: NetworkVariableReadPermission.Everyone, writePerm: NetworkVariableWritePermission.Owner); + NetworkVariable m_PlayerId = new NetworkVariable(string.Empty, readPerm: NetworkVariableReadPermission.Everyone, writePerm: NetworkVariableWritePermission.Owner); + public override void OnNetworkSpawn() { @@ -34,6 +34,7 @@ public override void OnNetworkSpawn() m_TopUIController = FindFirstObjectByType(); m_PlayerName.OnValueChanged += OnPlayerNameChanged; + m_PlayerId.OnValueChanged += OnPlayerIdChanged; OnPlayerNameChanged(string.Empty, m_PlayerName.Value); if (!HasAuthority) @@ -42,8 +43,8 @@ public override void OnNetworkSpawn() return; } - // a randomly-generated suffix consisting of a hash and four digits (e.g. #1234) is automatically appended to the requested name - m_PlayerName.Value = new FixedString32Bytes(AuthenticationService.Instance.PlayerName.Split('#')[0]); + m_PlayerId.Value = new FixedString32Bytes(AuthenticationService.Instance.PlayerId); + m_PlayerName.Value = new FixedString32Bytes(UIUtils.ExtractPlayerNameFromAuthUserName(AuthenticationService.Instance.PlayerName)); m_PlayerInput.enabled = true; GameInput.Actions.Player.Jump.performed += OnJumped; m_AvatarInteractions.enabled = true; @@ -66,8 +67,6 @@ public override void OnNetworkSpawn() Debug.LogError("CameraControl not found on the Main Camera or Main Camera is missing."); } - GameplayEventHandler.OnBlockPlayerControls += OnBlockPlayerControls; - base.OnNetworkSpawn(); } @@ -85,7 +84,6 @@ public override void OnNetworkDespawn() cameraControl.SetTransform(null); } - GameplayEventHandler.OnBlockPlayerControls -= OnBlockPlayerControls; m_TopUIController?.RemovePlayer(gameObject); } @@ -116,12 +114,12 @@ void OnTransformUpdate() void OnPlayerNameChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue) { - m_TopUIController.AddPlayer(gameObject, newValue.Value); + m_TopUIController.AddOrUpdatePlayer(gameObject, newValue.Value,m_PlayerId.Value.Value); } - void OnBlockPlayerControls(bool blockInput) + void OnPlayerIdChanged(FixedString32Bytes previousValue, FixedString32Bytes newValue) { - m_PlayerInput.enabled = !blockInput; + m_TopUIController.AddOrUpdatePlayer(gameObject, m_PlayerName.Value.Value,newValue.Value); } public void NetworkUpdate(NetworkUpdateStage updateStage) diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/ServicesHelper.cs b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/ServicesHelper.cs index e52a3a474..178ed905b 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/ServicesHelper.cs +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/ServicesHelper.cs @@ -129,9 +129,9 @@ async void LeaveSession() } } - void SignInFailed(RequestFailedException obj) + void SignInFailed(RequestFailedException e) { - Debug.LogWarning($"Sign in via Authentication failed: obj.ErrorCode {obj.ErrorCode}"); + Debug.LogWarning($"Sign in via Authentication failed: obj.ErrorCode {e.ErrorCode}"); } void RemovedFromSession() diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Unity.Multiplayer.Samples.SocialHub.Services.asmdef b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Unity.Multiplayer.Samples.SocialHub.Services.asmdef index 2adb0fab0..79ec2676c 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Unity.Multiplayer.Samples.SocialHub.Services.asmdef +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Unity.Multiplayer.Samples.SocialHub.Services.asmdef @@ -8,7 +8,8 @@ "GUID:6087a74f6015aae4daed9a2577a7596c", "GUID:1491147abca9d7d4bb7105af628b223e", "GUID:707bb6c7c4ef140eeac239b4f8251af8", - "GUID:75469ad4d38634e559750d17036d5f7c" + "GUID:75469ad4d38634e559750d17036d5f7c", + "GUID:86923d8926a1f4aca9fcdd3f0097bfd7" ], "includePlatforms": [], "excludePlatforms": [], @@ -19,4 +20,4 @@ "defineConstraints": [], "versionDefines": [], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Vivox3DPositioning.cs b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Vivox3DPositioning.cs new file mode 100644 index 000000000..4c9c478ae --- /dev/null +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Vivox3DPositioning.cs @@ -0,0 +1,56 @@ +using UnityEngine; +using Unity.Netcode; +using Unity.Multiplayer.Samples.SocialHub.GameManagement; + +namespace Unity.Multiplayer.Samples.SocialHub.Services +{ + class Vivox3DPositioning : NetworkBehaviour + { + bool m_Initialized; + float m_NextPosUpdate; + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + if (!HasAuthority) + { + enabled = false; + return; + } + + GameplayEventHandler.OnChatIsReady += OnChatIsReady; + GameplayEventHandler.OnExitedSession += OnExitSession; + } + + void OnChatIsReady(bool chatIsReady, string channelName) + { + m_Initialized = chatIsReady; + } + + void OnExitSession() + { + m_Initialized = false; + } + + void Update() + { + if (!m_Initialized) + { + return; + } + + if (Time.time > m_NextPosUpdate) + { + VivoxManager.Instance.SetPlayer3DPosition(gameObject); + m_NextPosUpdate = Time.time + 0.3f; + } + } + + public override void OnNetworkDespawn() + { + base.OnNetworkDespawn(); + GameplayEventHandler.OnChatIsReady -= OnChatIsReady; + GameplayEventHandler.OnExitedSession -= OnExitSession; + } + } +} diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Vivox3DPositioning.cs.meta b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Vivox3DPositioning.cs.meta new file mode 100644 index 000000000..6bd649d14 --- /dev/null +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/Vivox3DPositioning.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bee830f09ba1465fa953159de83e882e +timeCreated: 1732099329 \ No newline at end of file diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/VivoxManager.cs b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/VivoxManager.cs index 3b26ccda3..b0da11109 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/VivoxManager.cs +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/Services/VivoxManager.cs @@ -1,6 +1,9 @@ -using System; +#if UNITY_STANDALONE_OSX || UNITY_IOS +using System.Collections; +#endif using System.Threading.Tasks; using Unity.Multiplayer.Samples.SocialHub.GameManagement; +using Unity.Multiplayer.Samples.SocialHub.UI; using Unity.Services.Authentication; using UnityEngine; using Unity.Services.Vivox; @@ -9,8 +12,14 @@ namespace Unity.Multiplayer.Samples.SocialHub.Services { class VivoxManager : MonoBehaviour { - internal static VivoxManager Instance { get; set; } - string ChannelName { get; set; } + string m_TextChannelName; + string m_VoiceChannelName; + +#if UNITY_STANDALONE_OSX || UNITY_IOS + bool m_MicPermissionChecked; +#endif + + internal static VivoxManager Instance { get; private set; } void Awake() { @@ -25,18 +34,32 @@ void Awake() } } +#if UNITY_STANDALONE_OSX || UNITY_IOS + IEnumerator RequestMicrophonePermissionsIOsMacOS() + { + yield return Application.RequestUserAuthorization(UserAuthorization.Microphone); + m_MicPermissionChecked = true; + } +#endif + internal async Task Initialize() { await VivoxService.Instance.InitializeAsync(); - GameplayEventHandler.OnConnectToSessionCompleted += LoginVivox; - VivoxService.Instance.LoggedIn += OnLoggedInVivox; - VivoxService.Instance.ChannelJoined += OnChannelJoined; - GameplayEventHandler.OnExitedSession += LogoutVivox; + BindGlobalEvents(true); } async void LoginVivox(Task t, string sessionName) { - ChannelName = sessionName; +#if UNITY_STANDALONE_OSX || UNITY_IOS + // Vivox is not allowed to access the microphone on iOS and macOS without user permission. + StartCoroutine(RequestMicrophonePermissionsIOsMacOS()); + while (m_MicPermissionChecked == false) + { + await Task.Yield(); + } +#endif + m_TextChannelName = sessionName + "_text"; + m_VoiceChannelName = sessionName + "_voice"; await VivoxService.Instance.InitializeAsync(); var loginOptions = new LoginOptions() { @@ -48,49 +71,100 @@ async void LoginVivox(Task t, string sessionName) async void OnLoggedInVivox() { - await JoinChannel(ChannelName); + await JoinChannels(); + } + + async Task JoinChannels() + { + var positionalChannelProperties = new Channel3DProperties(10, 1, 1f, AudioFadeModel.InverseByDistance); + BindChannelEvents(true); + await VivoxService.Instance.JoinPositionalChannelAsync(m_VoiceChannelName, ChatCapability.AudioOnly, positionalChannelProperties); + await VivoxService.Instance.JoinGroupChannelAsync(m_TextChannelName, ChatCapability.TextOnly); } - async Task JoinChannel(string channelName) + void OnParticipantLeftChannel(VivoxParticipant vivoxParticipant) { var channelOptions = new ChannelOptions(); + // UI only needs to react to VoiceChannel participants. + if (vivoxParticipant.ChannelName != m_VoiceChannelName) + return; - await VivoxService.Instance.JoinGroupChannelAsync(channelName, ChatCapability.TextOnly, channelOptions); - GameplayEventHandler.OnSendTextMessage -= SendVivoxMessage; - GameplayEventHandler.OnSendTextMessage += SendVivoxMessage; - VivoxService.Instance.ChannelMessageReceived -= OnMessageReceived; - VivoxService.Instance.ChannelMessageReceived += OnMessageReceived; + GameplayEventHandler.ParticipantLeftVoiceChat(vivoxParticipant); + } + + void OnParticipantAddedToChannel(VivoxParticipant vivoxParticipant) + { + // UI only needs to react to VoiceChannel participants. + if (vivoxParticipant.ChannelName != m_VoiceChannelName) + return; + + GameplayEventHandler.ParticipantJoinedVoiceChat(vivoxParticipant); } void OnChannelJoined(string channelName) { - GameplayEventHandler.SetTextChatReady(true); + if (channelName == m_TextChannelName) + GameplayEventHandler.SetTextChatReady(true, m_TextChannelName); } async void LogoutVivox() { - GameplayEventHandler.SetTextChatReady(false); + GameplayEventHandler.SetTextChatReady(false, m_TextChannelName); await VivoxService.Instance.LogoutAsync(); } async void SendVivoxMessage(string message) { - await VivoxService.Instance.SendChannelTextMessageAsync(VivoxManager.Instance.ChannelName, message); + await VivoxService.Instance.SendChannelTextMessageAsync(m_TextChannelName, message); } void OnMessageReceived(VivoxMessage vivoxMessage) { - var senderName = vivoxMessage.SenderDisplayName.Split("#")[0]; + var senderName = UIUtils.ExtractPlayerNameFromAuthUserName(vivoxMessage.SenderDisplayName); GameplayEventHandler.ProcessTextMessageReceived(senderName, vivoxMessage.MessageText, vivoxMessage.FromSelf); } - void OnDestroy() + internal void SetPlayer3DPosition(GameObject avatar) + { + VivoxService.Instance.Set3DPosition(avatar, m_VoiceChannelName, false); + } + + void BindGlobalEvents(bool doBind) { - GameplayEventHandler.OnSendTextMessage -= SendVivoxMessage; - VivoxService.Instance.ChannelMessageReceived -= OnMessageReceived; - GameplayEventHandler.OnExitedSession -= LogoutVivox; GameplayEventHandler.OnConnectToSessionCompleted -= LoginVivox; VivoxService.Instance.LoggedIn -= OnLoggedInVivox; + VivoxService.Instance.ChannelJoined -= OnChannelJoined; + GameplayEventHandler.OnExitedSession -= LogoutVivox; + + if (doBind) + { + GameplayEventHandler.OnConnectToSessionCompleted += LoginVivox; + VivoxService.Instance.LoggedIn += OnLoggedInVivox; + VivoxService.Instance.ChannelJoined += OnChannelJoined; + GameplayEventHandler.OnExitedSession += LogoutVivox; + } + } + + void BindChannelEvents(bool doBind) + { + VivoxService.Instance.ParticipantAddedToChannel -= OnParticipantAddedToChannel; + VivoxService.Instance.ParticipantRemovedFromChannel -= OnParticipantLeftChannel; + GameplayEventHandler.OnSendTextMessage -= SendVivoxMessage; + VivoxService.Instance.ChannelMessageReceived -= OnMessageReceived; + + if (doBind) + { + VivoxService.Instance.ParticipantAddedToChannel += OnParticipantAddedToChannel; + VivoxService.Instance.ParticipantRemovedFromChannel += OnParticipantLeftChannel; + GameplayEventHandler.OnSendTextMessage += SendVivoxMessage; + VivoxService.Instance.ChannelMessageReceived += OnMessageReceived; + } + } + + void OnDestroy() + { + BindGlobalEvents(false); + BindChannelEvents(false); } } } diff --git a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/UI/IngameUI/IngameMenu.cs b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/UI/IngameUI/IngameMenu.cs index ca5b376e3..718e3c02e 100644 --- a/Basic/DistributedAuthoritySocialHub/Assets/Scripts/UI/IngameUI/IngameMenu.cs +++ b/Basic/DistributedAuthoritySocialHub/Assets/Scripts/UI/IngameUI/IngameMenu.cs @@ -1,5 +1,6 @@ -using System; using Unity.Multiplayer.Samples.SocialHub.GameManagement; + +using Unity.Services.Vivox; using Unity.Multiplayer.Samples.SocialHub.Input; using UnityEngine; using UnityEngine.InputSystem; @@ -19,39 +20,105 @@ class IngameMenu : MonoBehaviour VisualTreeAsset m_IngameMenuAsset; VisualElement m_Root; - VisualElement m_Menu; - VisualElement m_SceenOverlay; + Button m_BurgerButton; + Button m_ExitButton; + Button m_GotoMainButton; + Button m_CloseMenuButton; + + Toggle m_MuteToggle; + + Slider m_InputVolumeSlider; + Slider m_OutputVolumeSlider; + + DropdownField m_InputDevicesDropdown; + DropdownField m_OutputDevicesDropdown; + void OnEnable() { m_Root = m_UIDocument.rootVisualElement.Q("ingame-menu-container"); m_Root.Add(m_IngameMenuAsset.CloneTree().GetFirstChild()); - GameInput.Actions.Player.TogglePauseMenu.performed += OnTogglePauseMenu; - m_Menu = m_Root.Q("menu"); m_SceenOverlay = m_Root.Q("sceen-overlay"); + + m_BurgerButton = m_Root.Q