using UnityEngine;
using Unity.Netcode;
using Unity.XR.CoreUtils;
using Unity.Collections;
using System;
using Unity.Services.Vivox;
using Unity.XR.CoreUtils.Bindings.Variables;
namespace XRMultiplayer
{
///
/// XRINetworkPlayer class used for simple interactions.
///
public class XRINetworkPlayer : NetworkBehaviour
{
///
/// Speed at which voice amplitude changes.
///
const float k_VoiceAmplitudeSpeed = 15.0f;
///
/// Singleton Reference for the Local Player.
///
public static XRINetworkPlayer LocalPlayer;
[Header("Avatar Transform References"), Tooltip("Assign to local avatar transform.")]
///
/// Non-Local player transforms.
///
public Transform head;
///
/// Non-Local player transforms.
///
public Transform leftHand;
///
/// Non-Local player transforms.
///
public Transform rightHand;
///
/// Action called when the player name is updated.
///
public Action onNameUpdated;
///
/// Action called when the player color is updated.
///
public Action onColorUpdated;
///
/// Action called when the Local Player is finished spawning in.
///
public Action onSpawnedLocal;
///
/// Action called when the Local Player is finished spawning in.
///
public Action onSpawnedAll;
///
/// Action called when the player color is updated.
///
public Action onDisconnected;
///
/// Bindable Variable used for other clients to mute this user locally.
///
public BindableVariable squelched = new BindableVariable(false);
///
/// Current Voice Amplitude driven from Vivox.
///
public float playerVoiceAmp { get => m_VoiceAmplitudeCurrent; }
float m_VoiceAmplitudeCurrent;
///
/// Player Voice Id string that reads from the internal NetworkVariable for the Player Voice Id.
///
public string playerVoiceId { get => m_PlayerVoiceId.Value.ToString(); }
readonly NetworkVariable m_PlayerVoiceId = new("", NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
///
/// Player Name string that reads from the internal NetworkVariable for the Player Name.
///
public string playerName { get => m_PlayerName.Value.ToString(); }
readonly NetworkVariable m_PlayerName = new("", NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
///
/// Player Color that reads from the internal NetworkVariable for the Player Color.
///
public Color playerColor { get => m_PlayerColor.Value; }
readonly NetworkVariable m_PlayerColor = new(Color.white, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
[HideInInspector] public readonly NetworkVariable selfMuted = new(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
///
/// Player Name Tag.
///
[Header("Player Name Tag"), SerializeField, Tooltip("Player Name Tag.")] protected bool m_UpdateObjectName = true;
// ///
// /// Head Renderers to change rendering mode for local players.
// ///
// [SerializeField, Tooltip("Head Renderers to change rendering mode for local players.")] protected Renderer[] m_HeadRends;
///
/// Hand Objects to be disabled for the local player.
///
[Header("Networked Hands"), SerializeField, Tooltip("Hand Objects to be disabled for the local player.")] protected GameObject[] m_handsObjects;
///
/// Player Name Tag.
///
[Header("Player Name Tag"), SerializeField, Tooltip("Player Name Tag.")] protected PlayerNameTag m_PlayerNameTag;
///
/// Internal references to the Local Player Transforms.
///
protected Transform m_LeftHandOrigin, m_RightHandOrigin, m_HeadOrigin;
///
/// Reference to the local player XR Origin
///
protected XROrigin m_XROrigin;
///
/// If the player has been connected to the the game.
///
protected bool m_InitialConnected = false;
///
/// Reference to the VoiceChatManager.
///
protected VoiceChatManager m_VoiceChat;
///
/// Reference to the VivoxParticipant.
///
protected VivoxParticipant m_VivoxParticipant;
///
/// Time to update the voice position.
///
protected float m_VoicePositionUpdateTime = .1f, m_VoiceUpdatePosotionDelta = .05f;
///
/// Destination for the voice amplitude.
///
protected float m_VoiceAmplitudeDestination;
///
/// Timer to check the voice position.
///
protected float m_VoicePositionCheckTimer;
///
/// Previous position of the head.
///
protected Vector3 m_PrevHeadPos;
protected void Awake()
{
m_VoiceChat = FindFirstObjectByType();
m_VoicePositionCheckTimer = m_VoicePositionUpdateTime;
}
///
protected virtual void OnEnable()
{
m_PlayerName.OnValueChanged += UpdatePlayerName;
m_PlayerColor.OnValueChanged += UpdatePlayerColor;
}
///
protected virtual void OnDisable()
{
m_PlayerName.OnValueChanged -= UpdatePlayerName;
m_PlayerColor.OnValueChanged -= UpdatePlayerColor;
}
///
protected virtual void Update()
{
if (IsOwner && XRINetworkGameManager.Instance.positionalVoiceChat)
{
if (Time.time > m_VoicePositionCheckTimer)
{
m_VoicePositionCheckTimer += m_VoicePositionUpdateTime;
if (Vector3.Distance(m_PrevHeadPos, m_HeadOrigin.position) > m_VoiceUpdatePosotionDelta)
{
m_PrevHeadPos = m_HeadOrigin.position;
if (XRINetworkGameManager.Instance.positionalVoiceChat)
{
m_VoiceChat.Set3DAudio(m_HeadOrigin);
}
}
}
}
m_VoiceAmplitudeCurrent = Mathf.Lerp(m_VoiceAmplitudeCurrent, m_VoiceAmplitudeDestination, Time.deltaTime * k_VoiceAmplitudeSpeed);
}
///
protected virtual void LateUpdate()
{
if (!IsOwner) return;
// Set transforms to be replicated with ClientNetworkTransforms
leftHand.SetPositionAndRotation(m_LeftHandOrigin.position, m_LeftHandOrigin.rotation);
rightHand.SetPositionAndRotation(m_RightHandOrigin.position, m_RightHandOrigin.rotation);
head.SetPositionAndRotation(m_HeadOrigin.position, m_HeadOrigin.rotation);
}
///
public override void OnDestroy()
{
base.OnDestroy();
if (IsOwner)
{
// Local Name unsubscribe.
XRINetworkGameManager.LocalPlayerName.Unsubscribe(UpdateLocalPlayerName);
XRINetworkGameManager.LocalPlayerColor.Unsubscribe(UpdateLocalPlayerColor);
m_VoiceChat.selfMuted.Unsubscribe(SelfMutedChanged);
}
else if (NetworkManager.Singleton != null && NetworkManager.Singleton.IsConnectedClient)
{
// Inform Network Manager that player left current session.
XRINetworkGameManager.Instance.PlayerLeft(NetworkObject.OwnerClientId);
}
// Unsubscribe from color updating.
m_PlayerColor.OnValueChanged -= UpdatePlayerColor;
}
///
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if (IsOwner)
{
// Set Local Player.
LocalPlayer = this;
XRINetworkGameManager.Instance.LocalPlayerConnected(NetworkObject.OwnerClientId);
// Get Origin and set head.
m_XROrigin = FindFirstObjectByType();
if (m_XROrigin != null)
{
m_HeadOrigin = m_XROrigin.Camera.transform;
}
else
{
Utils.Log("No XR Rig Available", 1);
}
SetupLocalPlayer();
}
CompleteSetup();
}
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
PlayerHudNotification.Instance.ShowText($"{m_PlayerName.Value} left");
onDisconnected?.Invoke(this);
}
///
/// Called from when swapping between hand tracking and controllers.
///
/// Transform for Left Hand.
/// Transform for Right Hand.
public void SetHandOrigins(Transform left, Transform right)
{
m_LeftHandOrigin = left;
m_RightHandOrigin = right;
}
///
/// Hides and disables Renderers and GameObjects on the Local Player.
/// Also sets the initial values for and .
/// Finally we subscribe to any updates for Color and Name.
///
/// Only called on the Local Player.
protected virtual void SetupLocalPlayer()
{
foreach (var hand in m_handsObjects)
{
hand.SetActive(false);
}
m_PlayerColor.Value = XRINetworkGameManager.LocalPlayerColor.Value;
m_PlayerName.Value = new FixedString128Bytes(XRINetworkGameManager.LocalPlayerName.Value);
XRINetworkGameManager.LocalPlayerColor.Subscribe(UpdateLocalPlayerColor);
XRINetworkGameManager.LocalPlayerName.Subscribe(UpdateLocalPlayerName);
m_VoiceChat.selfMuted.Subscribe(SelfMutedChanged);
m_VoiceChat.ToggleSelfMute(true, true);
onSpawnedLocal?.Invoke();
}
///
/// Called from the local player only
///
///
void SelfMutedChanged(bool muted)
{
selfMuted.Value = muted;
}
///
/// Callback for the bindable variable .
///
/// New Color for player.
/// Only called on Local Player.
protected virtual void UpdateLocalPlayerColor(Color color)
{
m_PlayerColor.Value = XRINetworkGameManager.LocalPlayerColor.Value;
}
///
/// Callback for the bindable variable .
///
/// New Name for player.
/// Only called on Local Player.
protected virtual void UpdateLocalPlayerName(string name)
{
m_PlayerName.Value = new FixedString128Bytes(XRINetworkGameManager.LocalPlayerName.Value);
}
///
/// Called when the player object is finished being setup.
///
void CompleteSetup()
{
// Add player to XRINetworkManager.
XRINetworkGameManager.Instance.PlayerJoined(NetworkObject.OwnerClientId);
// Update Color and Name.
UpdatePlayerColor(Color.white, m_PlayerColor.Value);
UpdatePlayerName(new FixedString128Bytes(""), m_PlayerName.Value);
// Check if WorldCanvas exists
WorldCanvas worldCanvas = FindFirstObjectByType();
if (worldCanvas != null)
{
// If we are using a World Canvas, reparent name tag and destroy local canvas.
Canvas localCanvas = m_PlayerNameTag.GetComponentInParent