Initial commit

This commit is contained in:
Thorbjoern
2025-05-26 00:46:28 +02:00
commit e5bca03433
3896 changed files with 1434297 additions and 0 deletions
@@ -0,0 +1,85 @@
using UnityEngine;
namespace XRMultiplayer
{
/// <summary>
/// This class is used to represent the Avatar IK system in both the <see cref="XRINetworkPlayer"/> and the Offline Player"/>.
/// </summary>
public class XRAvatarIK : MonoBehaviour
{
/// <summary>
/// Transform for the Network Player Head.
/// </summary>
[SerializeField, Tooltip("Transform for the Network Player Head.")] Transform m_HeadTransform;
/// <summary>
/// Torso Parent Transform.
/// </summary>
[SerializeField, Tooltip("Torso Parent Transform.")] Transform m_TorsoParentTransform;
/// <summary>
/// Root of the Head Visuals.
/// </summary>
[SerializeField, Tooltip("Root of the Head Visuals.")] Transform m_HeadVisualsRoot;
/// <summary>
/// Neck Transform.
/// </summary>
[SerializeField, Tooltip("Neck Transform.")] Transform m_Neck;
/// <summary>
/// Offset to be applied to the head height.
/// </summary>
[SerializeField, Tooltip("Offset to be applied to the head height.")] float m_HeadHeightOffset = .3f;
/// <summary>
/// Theshold to where body rotation appoximation is applied.
/// </summary>
[Range(0, 360.0f)]
[SerializeField, Tooltip("Theshold to where body rotation appoximation is applied.")] float m_RotateThreshold = 25.0f;
/// <summary>
/// Speed at which the body rotates.
/// </summary>
[SerializeField, Tooltip("Speed at which the body rotates.")] float m_RotateSpeed = 3.0f;
/// <summary>
/// Transform associated with this script.
/// </summary>
Transform m_Transform;
/// <summary>
/// Rotation destination for the Y euler value.
/// </summary>
float m_DestinationY;
/// <inheritdoc/>
private void Start()
{
m_Transform = GetComponent<Transform>();
m_DestinationY = m_HeadTransform.transform.eulerAngles.y;
}
/// <inheritdoc/>
private void Update()
{
// Update Head.
m_HeadVisualsRoot.position = m_HeadTransform.position;
m_HeadVisualsRoot.position -= m_HeadTransform.up * m_HeadHeightOffset;
m_Neck.rotation = m_HeadTransform.rotation;
// Update Body.
m_Transform.position = m_HeadTransform.position;
m_TorsoParentTransform.rotation = Quaternion.Slerp(m_TorsoParentTransform.rotation, Quaternion.Euler(new Vector3(0, m_DestinationY, 0)), Time.deltaTime * m_RotateSpeed);
// Rotate Body if past threshold.
if (Mathf.Abs(m_TorsoParentTransform.eulerAngles.y - m_HeadTransform.eulerAngles.y) >= m_RotateThreshold)
{
m_DestinationY = m_HeadTransform.transform.eulerAngles.y;
}
// Update scale.
m_HeadVisualsRoot.localScale = m_HeadTransform.localScale;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa392b809b089674e82ce792ca5f7dbb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,127 @@
using System;
using UnityEngine;
namespace XRMultiplayer
{
[RequireComponent(typeof(XRINetworkPlayer))]
public class XRAvatarVisuals : MonoBehaviour
{
/// <summary>
/// Head Renderers to change rendering mode for local players.
/// </summary>
[Header("Renderer References"), SerializeField, Tooltip("Head Renderers to change rendering mode for local players.")]
protected Renderer[] m_HeadRends;
/// <summary>
/// Head Renderer to control the blend shape for mouth movement. Also updates shirt color based on <see cref="playerColor"/>.
/// </summary>
[SerializeField, Tooltip("Head Renderer to drive mouth movement blendshape and player shirt color.")]
protected SkinnedMeshRenderer m_headRend;
/// <summary>
/// GameObject to enable to show what player is the Room Host.
/// </summary>
[Header("Host Visuals"), SerializeField, Tooltip("GameObject that gets enabled for the Host only.")]
protected GameObject m_HostVisuals;
/// <summary>
/// GameObject to enable to show what player is the Room Host.
/// </summary>
[SerializeField, Tooltip("Show Host Visuals.")]
protected bool m_ShowHostVisuals = true;
/// <summary>
/// Materials to swap for the local player.
/// </summary>
[Header("Local Player Material Swap"), SerializeField]
protected LocalPlayerMaterialSwap m_LocalPlayerMaterialSwap;
/// <summary>
/// Reference to the attached XRINetworkPlayerAvatar component.
/// </summary>
protected XRINetworkPlayer m_NetworkPlayerAvatar;
public virtual void Awake()
{
if (!TryGetComponent(out m_NetworkPlayerAvatar))
{
Utils.LogError("XRAvatarVisuals requires a XRINetworkPlayerAvatar component to be attached to the same GameObject. Disabling this component now.");
enabled = false;
return;
}
m_NetworkPlayerAvatar.onSpawnedLocal += PlayerSpawnedLocal;
m_NetworkPlayerAvatar.onSpawnedAll += PlayerSpawnedAll;
m_NetworkPlayerAvatar.onColorUpdated += SetPlayerColor;
}
public virtual void OnDestroy()
{
m_NetworkPlayerAvatar.onSpawnedLocal -= PlayerSpawnedLocal;
m_NetworkPlayerAvatar.onSpawnedAll -= PlayerSpawnedAll;
m_NetworkPlayerAvatar.onColorUpdated -= SetPlayerColor;
}
public virtual void Update()
{
UpdateMouth();
}
public virtual void UpdateMouth()
{
if (m_headRend != null)
m_headRend.SetBlendShapeWeight(0, 100 - (m_NetworkPlayerAvatar.playerVoiceAmp * 100));
}
public virtual void PlayerSpawnedLocal()
{
m_LocalPlayerMaterialSwap.SwapMaterials();
int layer = LayerMask.NameToLayer("Mirror");
foreach (var r in m_HeadRends)
{
r.gameObject.layer = layer;
r.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
}
}
public virtual void PlayerSpawnedAll()
{
m_HostVisuals.SetActive(m_ShowHostVisuals && m_NetworkPlayerAvatar.IsOwnedByServer);
}
public virtual void SetPlayerColor(Color newColor)
{
m_headRend.materials[2].SetColor("_BaseColor", newColor);
}
}
}
[Serializable]
/// <summary>
/// Helper class for swapping the local player to standard materials from the dithering materials.
/// </summary>
public class LocalPlayerMaterialSwap
{
public Renderer headRend;
public Renderer hmdRend;
public Renderer hostRend;
public Renderer[] hands;
public Material[] headMaterials;
public Material[] hmdMaterials;
public Material hostMaterial;
public Material handMaterial;
public void SwapMaterials()
{
for (int i = 0; i < hands.Length; i++)
{
hands[i].material = handMaterial;
}
hmdRend.materials = hmdMaterials;
headRend.materials = headMaterials;
hostRend.material = hostMaterial;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 054a6b4527d98b446aa238d86cd24270
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,592 @@
using System.Collections.Generic;
using Unity.Netcode;
using Unity.XR.CoreUtils;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.XR.Hands;
using UnityEngine.XR.Interaction.Toolkit.Inputs;
namespace XRMultiplayer
{
/// <summary>
/// This class will synchronize the hand poses over the network.
/// It will also allow for the user to control the fidelity of the hand poses and how much data is being sent over the network.
/// This class is going to have major changes in the near future based on the Hand Pose work being done.
/// </summary>
public class XRHandPoseReplicator : NetworkBehaviour
{
/// <summary>
/// Controls the level of fidelity and how much data is being sent over the network.
/// 0 is highest level of fidelity and the most bandwidth, 2 is the lowest and the least bandwidth.
/// </summary>
[Header("Hands and Fingers"), Tooltip("0 is highest, 2 is lowest")]
[Range(0, 2), SerializeField] int m_FidelityLevel;
[SerializeField, Tooltip("Determines minimum value threshold for updating finger rotations")] float m_MinUpdateDelta = .1f;
[SerializeField] JointBasedHand[] m_HandCurler;
[SerializeField] float m_FingerLerpSpeed = 20.0f;
[SerializeField] bool m_UpdateHandsLocally;
[Header("Controller Inputs")]
[SerializeField] InputActionProperty[] m_GripInputProperties;
[SerializeField] InputActionProperty[] m_TriggerInputProperties;
[SerializeField] InputActionProperty[] m_ThumbTouchProperties;
NetworkList<Vector3> m_FingerRotationsLeft;
NetworkList<Vector3> m_FingerRotationsRight;
NetworkList<float> m_FingerCurlLeft;
NetworkList<float> m_FingerCurlRight;
NetworkVariable<bool> m_IsInitialized = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
Pose[] m_handTrackedStartPose = new Pose[2];
[Header("Offsets")]
[SerializeField] Vector3[] m_HandControllerOffsets;
[SerializeField] Vector3[] m_HandControllerEulerOffsets;
HandFidelityOption[] m_LocalHandFidelityOptions;
public XRInputModalityManager.InputMode trackingType { get => m_TrackingType.Value; }
readonly NetworkVariable<XRInputModalityManager.InputMode> m_TrackingType = new(XRInputModalityManager.InputMode.None, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
XRInputModalityManager m_XRModalityManager;
XROrigin m_XROrigin;
Transform m_LeftHandTransformReference;
Transform m_RightHandTransformReference;
Transform m_LeftControllerTransformReference;
Transform m_RightControllerTransformReference;
/// <summary>
/// Internal references to the Local Player Transforms.
/// </summary>
protected Transform m_LeftHandOrigin, m_RightHandOrigin;
private void Awake()
{
m_FingerRotationsLeft = new NetworkList<Vector3>(new List<Vector3>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
m_FingerRotationsRight = new NetworkList<Vector3>(new List<Vector3>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
m_FingerCurlLeft = new NetworkList<float>(new List<float>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
m_FingerCurlRight = new NetworkList<float>(new List<float>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
for (int i = 0; i < m_HandCurler.Length; i++)
{
m_handTrackedStartPose[i].position = m_HandCurler[i].transform.localPosition;
m_handTrackedStartPose[i].rotation = m_HandCurler[i].transform.localRotation;
}
}
private void OnEnable()
{
m_TrackingType.OnValueChanged += UpdateTrackingType;
}
private void OnDisable()
{
m_TrackingType.OnValueChanged -= UpdateTrackingType;
}
public override void OnDestroy()
{
base.OnDestroy();
if (IsOwner)
{
m_XRModalityManager.trackedHandModeStarted.RemoveListener(SwapToHands);
m_XRModalityManager.motionControllerModeStarted.RemoveListener(SwapToControllers);
}
}
private void Update()
{
if (!m_IsInitialized.Value || !NetworkManager.IsConnectedClient || NetworkManager.ShutdownInProgress) return;
if (trackingType == XRInputModalityManager.InputMode.TrackedHand)
{
switch (m_FidelityLevel)
{
case 0:
SyncAllFingerData();
break;
case 1:
SyncFingerCurl();
break;
case 2:
SyncFingerCurlLimited();
break;
}
}
else
{
SyncControllerTracking();
}
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if (IsOwner)
{
m_XROrigin = FindFirstObjectByType<XROrigin>();
if (m_XROrigin.TryGetComponent(out m_XRModalityManager))
{
SetupLocalHands();
}
SetupLocalFingerReferences();
}
else
{
ChangeControllerType(m_TrackingType.Value);
}
}
void UpdateTrackingType(XRInputModalityManager.InputMode old, XRInputModalityManager.InputMode current)
{
ChangeControllerType(current);
}
void SetupLocalHands()
{
m_LeftControllerTransformReference = m_XRModalityManager.leftController.transform;
m_RightControllerTransformReference = m_XRModalityManager.rightController.transform;
if (m_XRModalityManager.leftHand == null) //Rig doesn't have hands setup
{
m_LeftHandTransformReference = m_XRModalityManager.leftController.transform;
m_RightHandTransformReference = m_XRModalityManager.rightController.transform;
SetTrackingType(XRInputModalityManager.InputMode.MotionController);
}
else //Setup Hands and modality change listeners
{
m_LeftHandTransformReference = m_XRModalityManager.leftHand.GetComponentInChildren<XRHandSkeletonDriver>().rootTransform;
m_RightHandTransformReference = m_XRModalityManager.rightHand.GetComponentInChildren<XRHandSkeletonDriver>().rootTransform;
SetTrackingType(XRInputModalityManager.currentInputMode.Value);
m_XRModalityManager.trackedHandModeStarted.AddListener(SwapToHands);
m_XRModalityManager.motionControllerModeStarted.AddListener(SwapToControllers);
}
}
void SetupLocalFingerReferences()
{
XRInputModalityManager modalityMangager = FindFirstObjectByType<XRInputModalityManager>();
// Early out if hands are not setup
if (modalityMangager.leftHand == null)
{
// Set Default values for finger rotations
for (int i = 0; i < 3; i++)
{
m_FingerCurlLeft.Add(0.0f);
m_FingerCurlRight.Add(0.0f);
}
m_IsInitialized.Value = true;
ChangeControllerType(XRInputModalityManager.InputMode.MotionController);
return;
}
XRHandSkeletonDriver localLeftHandSkeleton = modalityMangager.leftHand.GetComponentInChildren<XRHandSkeletonDriver>();
XRHandSkeletonDriver localRightHandSkeleton = modalityMangager.rightHand.GetComponentInChildren<XRHandSkeletonDriver>();
m_LocalHandFidelityOptions = new HandFidelityOption[2];
for (int i = 0; i < m_LocalHandFidelityOptions.Length; i++)
{
m_LocalHandFidelityOptions[i].fingerJoints = new FingerJoints[5];
// Loop through each finger and setup name and joints
for (int j = 0; j < m_LocalHandFidelityOptions[i].fingerJoints.Length; j++)
{
m_LocalHandFidelityOptions[i].fingerJoints[j].fingerName = m_HandCurler[i].handFidelityOptions[0].fingerJoints[j].fingerName;
m_LocalHandFidelityOptions[i].fingerJoints[j].jointTransformReferences = new List<JointToTransformReference>();
// Loop through each joint in the finger and setup the joint references
for (int k = 0; k < m_HandCurler[i].handFidelityOptions[0].fingerJoints[j].jointTransformReferences.Count; k++)
{
XRHandSkeletonDriver currentHandSkeletonDriver = i % 2 == 0 ? localLeftHandSkeleton : localRightHandSkeleton;
// Loop through each local hand and look up the joint reference
foreach (var localJoint in currentHandSkeletonDriver.jointTransformReferences)
{
if (m_HandCurler[i].handFidelityOptions[0].fingerJoints[j].jointTransformReferences[k].xrHandJointID == localJoint.xrHandJointID)
{
m_LocalHandFidelityOptions[i].fingerJoints[j].jointTransformReferences.Add(localJoint);
break;
}
}
}
}
}
foreach (var fingerSync in m_LocalHandFidelityOptions[0].fingerJoints)
{
foreach (var joint in fingerSync.jointTransformReferences)
{
m_FingerRotationsLeft.Add(joint.jointTransform.eulerAngles);
}
}
foreach (var fingerSync in m_LocalHandFidelityOptions[1].fingerJoints)
{
foreach (var joint in fingerSync.jointTransformReferences)
{
m_FingerRotationsRight.Add(joint.jointTransform.eulerAngles);
}
}
// Set Default values for finger curl
foreach (var fingerSync in m_LocalHandFidelityOptions[0].fingerJoints)
{
m_FingerCurlLeft.Add(0.0f);
}
foreach (var fingerSync in m_LocalHandFidelityOptions[1].fingerJoints)
{
m_FingerCurlRight.Add(0.0f);
}
m_IsInitialized.Value = true;
SetFidelity(m_FidelityLevel);
}
public void ChangeControllerType(XRInputModalityManager.InputMode inputMode)
{
if (inputMode == XRInputModalityManager.InputMode.MotionController)
{
SetFidelity(2);
SetHandsToControllerOffset();
}
else
{
SetFidelity(m_FidelityLevel);
ResetHandsToStart();
}
}
void SetFidelity(int fidelity)
{
fidelity = Mathf.Clamp(fidelity, 0, 2);
m_HandCurler[0].fidelityLevel = fidelity;
m_HandCurler[1].fidelityLevel = fidelity;
m_HandCurler[0].useCurl = fidelity > 0;
m_HandCurler[1].useCurl = fidelity > 0;
}
void SyncAllFingerData()
{
if (IsOwner)
{
SetNetworkFingerRotations();
if (m_UpdateHandsLocally)
{
GetNetworkFingerRotations();
}
}
else
{
GetNetworkFingerRotations();
}
}
void SetNetworkFingerRotations()
{
int currentIdx = 0;
for (int i = 0; i < m_LocalHandFidelityOptions[0].fingerJoints.Length; i++)
{
for (int j = 0; j < m_LocalHandFidelityOptions[0].fingerJoints[i].jointTransformReferences.Count; j++)
{
m_FingerRotationsLeft[currentIdx++] =
m_LocalHandFidelityOptions[0].fingerJoints[i].jointTransformReferences[j].jointTransform.eulerAngles;
}
}
currentIdx = 0;
for (int i = 0; i < m_LocalHandFidelityOptions[1].fingerJoints.Length; i++)
{
for (int j = 0; j < m_LocalHandFidelityOptions[1].fingerJoints[i].jointTransformReferences.Count; j++)
{
m_FingerRotationsRight[currentIdx++] =
m_LocalHandFidelityOptions[1].fingerJoints[i].jointTransformReferences[j].jointTransform.eulerAngles;
}
}
}
void GetNetworkFingerRotations()
{
int currentIdx = 0;
int hand = 0;
for (int i = 0; i < m_HandCurler[hand].handFidelityOptions[0].fingerJoints.Length; i++)
{
for (int j = 0; j < m_HandCurler[hand].handFidelityOptions[0].fingerJoints[i].jointTransformReferences.Count; j++)
{
m_HandCurler[hand].handFidelityOptions[0].fingerJoints[i].jointTransformReferences[j].jointTransform.rotation =
Quaternion.Slerp(m_HandCurler[hand].handFidelityOptions[0].fingerJoints[i].jointTransformReferences[j].jointTransform.rotation,
Quaternion.Euler(m_FingerRotationsLeft[currentIdx++]),
Time.deltaTime * m_FingerLerpSpeed);
}
}
currentIdx = 0;
hand = 1;
for (int i = 0; i < m_HandCurler[hand].handFidelityOptions[0].fingerJoints.Length; i++)
{
for (int j = 0; j < m_HandCurler[hand].handFidelityOptions[0].fingerJoints[i].jointTransformReferences.Count; j++)
{
m_HandCurler[hand].handFidelityOptions[0].fingerJoints[i].jointTransformReferences[j].jointTransform.rotation =
Quaternion.Slerp(m_HandCurler[hand].handFidelityOptions[0].fingerJoints[i].jointTransformReferences[j].jointTransform.rotation,
Quaternion.Euler(m_FingerRotationsRight[currentIdx++]),
Time.deltaTime * m_FingerLerpSpeed);
}
}
}
void SyncFingerCurl()
{
if (IsOwner)
{
SetNetworkCurl();
if (m_UpdateHandsLocally)
{
GetNetworkCurl();
}
}
else
{
GetNetworkCurl();
}
}
void SetNetworkCurl()
{
for (int i = 0; i < m_LocalHandFidelityOptions[0].fingerJoints.Length; i++)
{
SetLocalNetworkFingerCurl(0, i, GetAverageX(0, i));
}
for (int i = 0; i < m_LocalHandFidelityOptions[1].fingerJoints.Length; i++)
{
SetLocalNetworkFingerCurl(1, i, GetAverageX(1, i));
}
}
void GetNetworkCurl()
{
for (int i = 0; i < m_HandCurler[0].handFidelityOptions[0].fingerJoints.Length; i++)
{
m_HandCurler[0].SetCurl(i, m_FingerCurlLeft[i]);
}
for (int i = 0; i < m_HandCurler[1].handFidelityOptions[0].fingerJoints.Length; i++)
{
m_HandCurler[1].SetCurl(i, m_FingerCurlRight[i]);
}
}
float GetAverageX(int hand, int finger)
{
float x = 0;
int digitCount = 4;
if (finger == 0) //Thumbs have 1 less joint
{
digitCount--;
}
for (int i = 1; i < digitCount; i++)
{
float currentX = m_LocalHandFidelityOptions[hand].fingerJoints[finger].jointTransformReferences[i].jointTransform.localEulerAngles.x;
if (currentX < 0 || currentX > 180)
{
currentX = 0;
}
x += currentX;
}
float avg = Mathf.Clamp(x / (digitCount - 1), 0, 100);
return avg / 100;
}
void SyncFingerCurlLimited()
{
if (IsOwner)
{
SetNetworkCurlLimited();
if (m_UpdateHandsLocally)
{
GetNetworkCurlLimited();
}
}
else
{
GetNetworkCurlLimited();
}
}
void SetNetworkCurlLimited()
{
for (int i = 0; i < 2; i++)
{
SetLocalNetworkFingerCurl(0, i, GetAverageX(0, i));
}
for (int i = 0; i < 2; i++)
{
SetLocalNetworkFingerCurl(1, i, GetAverageX(1, i));
}
SetLocalNetworkFingerCurl(0, 2, GetAverageXCombined(0));
SetLocalNetworkFingerCurl(1, 2, GetAverageXCombined(1));
}
void GetNetworkCurlLimited()
{
for (int i = 0; i < 3; i++)
{
m_HandCurler[0].SetCurl(i, m_FingerCurlLeft[i]);
}
for (int i = 0; i < 3; i++)
{
m_HandCurler[1].SetCurl(i, m_FingerCurlRight[i]);
}
}
float GetAverageXCombined(int hand)
{
float x = 0;
int digitCount = 4;
int startFinger = 2;
int endFinger = 5;
int count = 0;
for (int i = startFinger; i < endFinger; i++)
{
for (int j = 1; j < digitCount; j++)
{
float currentX = m_LocalHandFidelityOptions[hand].fingerJoints[i].jointTransformReferences[j].jointTransform.localEulerAngles.x;
if (currentX < 0 || currentX > 180)
{
currentX = 0;
}
x += currentX;
count++;
}
}
float avg = Mathf.Clamp(x / count, 0, 100);
return avg / 100;
}
void SyncControllerTracking()
{
//TODO: Sync Controller Input and map to hand poses
if (IsOwner)
{
SetNetworkControllerFingerSync();
if (m_UpdateHandsLocally)
{
GetNetworkedControllerFingerSync();
}
}
else
{
GetNetworkedControllerFingerSync();
}
}
void SetNetworkControllerFingerSync()
{
SetLocalNetworkFingerCurl(0, 0, m_ThumbTouchProperties[0].action?.ReadValue<float>() ?? 0.0f);
SetLocalNetworkFingerCurl(0, 1, m_TriggerInputProperties[0].action?.ReadValue<float>() ?? 0.0f);
SetLocalNetworkFingerCurl(0, 2, m_GripInputProperties[0].action?.ReadValue<float>() ?? 0.0f);
SetLocalNetworkFingerCurl(1, 0, m_ThumbTouchProperties[1].action?.ReadValue<float>() ?? 0.0f);
SetLocalNetworkFingerCurl(1, 1, m_TriggerInputProperties[1].action?.ReadValue<float>() ?? 0.0f);
SetLocalNetworkFingerCurl(1, 2, m_GripInputProperties[1].action?.ReadValue<float>() ?? 0.0f);
}
void SetLocalNetworkFingerCurl(int hand, int finger, float value)
{
if (hand == 0)
{
if (Mathf.Abs(m_FingerCurlLeft[finger] - value) > m_MinUpdateDelta)
m_FingerCurlLeft[finger] = value;
}
else
{
if (Mathf.Abs(m_FingerCurlRight[finger] - value) > m_MinUpdateDelta)
m_FingerCurlRight[finger] = value;
}
}
void GetNetworkedControllerFingerSync()
{
GetNetworkCurlLimited();
}
void SwapToHands()
{
SetTrackingType(XRInputModalityManager.InputMode.TrackedHand);
}
void SwapToControllers()
{
SetTrackingType(XRInputModalityManager.InputMode.MotionController);
}
public void SetTrackingType(XRInputModalityManager.InputMode trackingType)
{
m_TrackingType.Value = trackingType;
if (trackingType == XRInputModalityManager.InputMode.MotionController)
{
m_LeftHandOrigin = m_LeftControllerTransformReference;
m_RightHandOrigin = m_RightControllerTransformReference;
}
else
{
m_LeftHandOrigin = m_LeftHandTransformReference;
m_RightHandOrigin = m_RightHandTransformReference;
}
XRINetworkPlayer.LocalPlayer.SetHandOrigins(m_LeftHandOrigin, m_RightHandOrigin);
}
void ResetHandsToStart()
{
for (int i = 0; i < m_HandCurler.Length; i++)
{
m_HandCurler[i].transform.localPosition = m_handTrackedStartPose[i].position;
m_HandCurler[i].transform.localRotation = m_handTrackedStartPose[i].rotation;
}
}
void SetHandsToControllerOffset()
{
for (int i = 0; i < m_HandCurler.Length; i++)
{
m_HandCurler[i].transform.localPosition = m_HandControllerOffsets[i];
m_HandCurler[i].transform.localRotation = Quaternion.Euler(m_HandControllerEulerOffsets[i]);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 79af78f95e61cdd458d2f24b1a8356fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,449 @@
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
{
/// <summary>
/// XRINetworkPlayer class used for simple interactions.
/// </summary>
public class XRINetworkPlayer : NetworkBehaviour
{
/// <summary>
/// Speed at which voice amplitude changes.
/// </summary>
const float k_VoiceAmplitudeSpeed = 15.0f;
/// <summary>
/// Singleton Reference for the Local Player.
/// </summary>
public static XRINetworkPlayer LocalPlayer;
[Header("Avatar Transform References"), Tooltip("Assign to local avatar transform.")]
/// <summary>
/// Non-Local player transforms.
/// </summary>
public Transform head;
/// <summary>
/// Non-Local player transforms.
/// </summary>
public Transform leftHand;
/// <summary>
/// Non-Local player transforms.
/// </summary>
public Transform rightHand;
/// <summary>
/// Action called when the player name is updated.
/// </summary>
public Action<string> onNameUpdated;
/// <summary>
/// Action called when the player color is updated.
/// </summary>
public Action<Color> onColorUpdated;
/// <summary>
/// Action called when the Local Player is finished spawning in.
/// </summary>
public Action onSpawnedLocal;
/// <summary>
/// Action called when the Local Player is finished spawning in.
/// </summary>
public Action onSpawnedAll;
/// <summary>
/// Action called when the player color is updated.
/// </summary>
public Action<XRINetworkPlayer> onDisconnected;
/// <summary>
/// Bindable Variable used for other clients to mute this user locally.
/// </summary>
public BindableVariable<bool> squelched = new BindableVariable<bool>(false);
/// <summary>
/// Current Voice Amplitude driven from Vivox.
/// </summary>
public float playerVoiceAmp { get => m_VoiceAmplitudeCurrent; }
float m_VoiceAmplitudeCurrent;
/// <summary>
/// Player Voice Id string that reads from the internal NetworkVariable for the Player Voice Id.
/// </summary>
public string playerVoiceId { get => m_PlayerVoiceId.Value.ToString(); }
readonly NetworkVariable<FixedString128Bytes> m_PlayerVoiceId = new("", NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
/// <summary>
/// Player Name string that reads from the internal NetworkVariable for the Player Name.
/// </summary>
public string playerName { get => m_PlayerName.Value.ToString(); }
readonly NetworkVariable<FixedString128Bytes> m_PlayerName = new("", NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
/// <summary>
/// Player Color that reads from the internal NetworkVariable for the Player Color.
/// </summary>
public Color playerColor { get => m_PlayerColor.Value; }
readonly NetworkVariable<Color> m_PlayerColor = new(Color.white, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
[HideInInspector] public readonly NetworkVariable<bool> selfMuted = new(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
/// <summary>
/// Player Name Tag.
/// </summary>
[Header("Player Name Tag"), SerializeField, Tooltip("Player Name Tag.")] protected bool m_UpdateObjectName = true;
// /// <summary>
// /// Head Renderers to change rendering mode for local players.
// /// </summary>
// [SerializeField, Tooltip("Head Renderers to change rendering mode for local players.")] protected Renderer[] m_HeadRends;
/// <summary>
/// Hand Objects to be disabled for the local player.
/// </summary>
[Header("Networked Hands"), SerializeField, Tooltip("Hand Objects to be disabled for the local player.")] protected GameObject[] m_handsObjects;
/// <summary>
/// Player Name Tag.
/// </summary>
[Header("Player Name Tag"), SerializeField, Tooltip("Player Name Tag.")] protected PlayerNameTag m_PlayerNameTag;
/// <summary>
/// Internal references to the Local Player Transforms.
/// </summary>
protected Transform m_LeftHandOrigin, m_RightHandOrigin, m_HeadOrigin;
/// <summary>
/// Reference to the local player XR Origin
/// </summary>
protected XROrigin m_XROrigin;
/// <summary>
/// If the player has been connected to the the game.
/// </summary>
protected bool m_InitialConnected = false;
/// <summary>
/// Reference to the VoiceChatManager.
/// </summary>
protected VoiceChatManager m_VoiceChat;
/// <summary>
/// Reference to the VivoxParticipant.
/// </summary>
protected VivoxParticipant m_VivoxParticipant;
/// <summary>
/// Time to update the voice position.
/// </summary>
protected float m_VoicePositionUpdateTime = .1f, m_VoiceUpdatePosotionDelta = .05f;
/// <summary>
/// Destination for the voice amplitude.
/// </summary>
protected float m_VoiceAmplitudeDestination;
/// <summary>
/// Timer to check the voice position.
/// </summary>
protected float m_VoicePositionCheckTimer;
/// <summary>
/// Previous position of the head.
/// </summary>
protected Vector3 m_PrevHeadPos;
protected void Awake()
{
m_VoiceChat = FindFirstObjectByType<VoiceChatManager>();
m_VoicePositionCheckTimer = m_VoicePositionUpdateTime;
}
///<inheritdoc/>
protected virtual void OnEnable()
{
m_PlayerName.OnValueChanged += UpdatePlayerName;
m_PlayerColor.OnValueChanged += UpdatePlayerColor;
}
///<inheritdoc/>
protected virtual void OnDisable()
{
m_PlayerName.OnValueChanged -= UpdatePlayerName;
m_PlayerColor.OnValueChanged -= UpdatePlayerColor;
}
///<inheritdoc/>
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);
}
///<inheritdoc/>
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);
}
///<inheritdoc/>
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;
}
///<inheritdoc/>
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<XROrigin>();
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($"<b>{m_PlayerName.Value}</b> left");
onDisconnected?.Invoke(this);
}
/// <summary>
/// Called from <see cref="XRHandPoseReplicator"/> when swapping between hand tracking and controllers.
/// </summary>
/// <param name="left">Transform for Left Hand.</param>
/// <param name="right">Transform for Right Hand.</param>
public void SetHandOrigins(Transform left, Transform right)
{
m_LeftHandOrigin = left;
m_RightHandOrigin = right;
}
/// <summary>
/// Hides and disables Renderers and GameObjects on the Local Player.
/// Also sets the initial values for <see cref="m_PlayerColor"/> and <see cref="m_PlayerName"/>.
/// Finally we subscribe to any updates for Color and Name.
/// </summary>
/// <remarks>Only called on the Local Player.</remarks>
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();
}
/// <summary>
/// Called from the local player only
/// </summary>
/// <param name="muted"></param>
void SelfMutedChanged(bool muted)
{
selfMuted.Value = muted;
}
/// <summary>
/// Callback for the bindable variable <see cref="XRINetworkGameManager.LocalPlayerColor"/>.
/// </summary>
/// <param name="color">New Color for player.</param>
/// <remarks>Only called on Local Player.</remarks>
protected virtual void UpdateLocalPlayerColor(Color color)
{
m_PlayerColor.Value = XRINetworkGameManager.LocalPlayerColor.Value;
}
/// <summary>
/// Callback for the bindable variable <see cref="XRINetworkGameManager.LocalPlayerName"/>.
/// </summary>
/// <param name="name">New Name for player.</param>
/// <remarks>Only called on Local Player.</remarks>
protected virtual void UpdateLocalPlayerName(string name)
{
m_PlayerName.Value = new FixedString128Bytes(XRINetworkGameManager.LocalPlayerName.Value);
}
/// <summary>
/// Called when the player object is finished being setup.
/// </summary>
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<WorldCanvas>();
if (worldCanvas != null)
{
// If we are using a World Canvas, reparent name tag and destroy local canvas.
Canvas localCanvas = m_PlayerNameTag.GetComponentInParent<Canvas>();
worldCanvas.SetupPlayerNameTag(this, m_PlayerNameTag);
Destroy(localCanvas.gameObject);
}
else
{
// If we are not using a World Canvas, setup the name tag for local use.
m_PlayerNameTag.SetupNameTag(this);
}
onSpawnedAll?.Invoke();
}
/// <summary>
/// Callback anytime the local player sets <see cref="m_PlayerName"/>.
/// </summary><remarks>Invokes the callback <see cref="onNameUpdated"/>.</remarks>
void UpdatePlayerName(FixedString128Bytes oldName, FixedString128Bytes currentName)
{
onNameUpdated?.Invoke(currentName.ToString());
if (!m_InitialConnected & !string.IsNullOrEmpty(currentName.ToString()))
{
m_InitialConnected = true;
if (!IsLocalPlayer)
PlayerHudNotification.Instance.ShowText($"<b>{playerName}</b> joined");
}
if (m_UpdateObjectName)
gameObject.name = currentName.ToString();
}
/// <summary>
/// Callback when the local player sets <see cref="m_PlayerColor"/>.
/// </summary><remarks>Invokes the callback <see cref="onColorUpdated"/>.</remarks>
void UpdatePlayerColor(Color oldColor, Color newColor)
{
onColorUpdated?.Invoke(newColor);
}
void UpdatePlayerVoiceEnergy(float current)
{
m_VoiceAmplitudeDestination = Mathf.Clamp01(current);
}
/// <summary>
/// Called when new players connect to the game and set their initial <see cref="m_PlayerVoiceId"/>
/// and when <see cref="VoiceChatManager.OnParticipantAdded(VivoxParticipant)"/> is called for existing players.
/// </summary>
public void SetupVoicePlayer()
{
m_VivoxParticipant = m_VoiceChat.GetVivoxParticipantById(playerVoiceId);
if (m_VivoxParticipant != null)
{
m_VivoxParticipant.ParticipantAudioEnergyChanged += ParticipantAudioEnergyChanged;
}
else
{
Utils.Log($"No Participant with id: {playerVoiceId}", 1);
}
if (!VoiceChatManager.m_PlayersDictionary.ContainsKey(playerVoiceId))
{
VoiceChatManager.AddNewVivoxPlayer(playerVoiceId, this);
}
}
private void ParticipantAudioEnergyChanged()
{
UpdatePlayerVoiceEnergy((float)m_VivoxParticipant.AudioEnergy);
}
public void SetVoiceId(string voiceId)
{
if (!IsOwner) return;
m_PlayerVoiceId.Value = new FixedString128Bytes(voiceId);
SetupVoicePlayer();
if (XRINetworkGameManager.Instance.positionalVoiceChat)
{
m_VoiceChat.Set3DAudio(m_HeadOrigin);
}
}
/// <summary>
/// Called from clients to mute this player locally for that client.
/// </summary>
public void ToggleSquelch()
{
if (m_VivoxParticipant != null)
{
squelched.Value = !squelched.Value;
if (squelched.Value)
m_VivoxParticipant.MutePlayerLocally();
else
m_VivoxParticipant.UnmutePlayerLocally();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c156e0102a8d0514b91acc63faadec62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: