using Unity.XR.CoreUtils; using Unity.XR.CoreUtils.Bindings.Variables; using UnityEngine; using UnityEngine.Android; namespace XRMultiplayer { /// /// Represents the offline player avatar. /// public class OfflinePlayerAvatar : MonoBehaviour { public static BindableVariable voiceAmp = new BindableVariable(); /// /// Gets or sets a value indicating whether the player is muted. /// public static bool muted { get => s_Muted; set { if (Permission.HasUserAuthorizedPermission(Permission.Microphone)) s_Muted = value; } } /// /// A value indicating whether the player is muted. /// static bool s_Muted; /// /// The head transform. /// [SerializeField] Transform m_HeadTransform; /// /// The head renderer. /// [SerializeField] SkinnedMeshRenderer m_HeadRend; /// /// The voice amplitude curve. /// [SerializeField] AnimationCurve m_VoiceCurve; /// /// The head origin. /// Transform m_HeadOrigin; /// /// The mouth blend smoothing. /// [SerializeField] float m_MouthBlendSmoothing = 5.0f; /// /// The microphone loudness. /// float m_MicLoudness; /// /// The microphone device name. /// string m_Device; /// /// The sample window. /// int m_SampleWindow = 128; /// /// The clip record. /// AudioClip m_ClipRecord; /// /// The voice destination volume. /// float m_VoiceDestinationVolume; bool m_MicInitialized = false; /// void Start() { XROrigin rig = FindFirstObjectByType(); m_HeadOrigin = rig.Camera.transform; } void OnEnable() { XRINetworkGameManager.LocalPlayerColor.Subscribe(UpdatePlayerColor); VoiceChatManager.s_HasMicrophonePermission.Subscribe(MicrophonePermissionGranted); XRINetworkGameManager.Connected.Subscribe(connected => { gameObject.SetActive(!connected); }); } void OnDisable() { XRINetworkGameManager.LocalPlayerColor.Unsubscribe(UpdatePlayerColor); VoiceChatManager.s_HasMicrophonePermission.Subscribe(MicrophonePermissionGranted); StopMicrophone(); XRINetworkGameManager.Connected.Unsubscribe(connected => { gameObject.SetActive(!connected); }); } /// private void LateUpdate() { m_HeadTransform.SetPositionAndRotation(m_HeadOrigin.position, m_HeadOrigin.rotation); } /// void Update() { if (!s_Muted) { m_MicLoudness = LevelMax(); m_VoiceDestinationVolume = Mathf.Clamp01(Mathf.Lerp(m_VoiceDestinationVolume, m_MicLoudness, Time.deltaTime * m_MouthBlendSmoothing)); float appliedCurve = m_VoiceCurve.Evaluate(m_VoiceDestinationVolume); voiceAmp.Value = appliedCurve; m_HeadRend.SetBlendShapeWeight(0, 100 - appliedCurve * 100); } else { voiceAmp.Value = 0.0f; } } void MicrophonePermissionGranted(bool granted) { if (granted) { InitMic(); } } void UpdatePlayerColor(Color color) { m_HeadRend.materials[2].color = color; } /// /// Initializes the microphone, called from . /// void InitMic() { m_MicInitialized = true; m_Device ??= Microphone.devices[0]; m_ClipRecord = Microphone.Start(m_Device, true, 999, 44100); } /// /// Stops the microphone. /// void StopMicrophone() { m_MicInitialized = false; if (Permission.HasUserAuthorizedPermission(Permission.Microphone)) { Microphone.End(m_Device); } else { s_Muted = true; } } /// /// Gets the maximum level of the microphone input. /// /// The maximum level of the microphone input. float LevelMax() { if (!m_MicInitialized) return 0; float levelMax = 0; float[] waveData = new float[m_SampleWindow]; int micPosition = Microphone.GetPosition(null) - (m_SampleWindow + 1); // null means the first microphone if (micPosition < 0) return 0; m_ClipRecord.GetData(waveData, micPosition); // Getting a peak on the last 128 samples for (int i = 0; i < m_SampleWindow; i++) { float wavePeak = waveData[i] * waveData[i]; if (levelMax < wavePeak) { levelMax = wavePeak; } } return levelMax; } } }