Multiplayer Networking Leap Motion with Mirror
I am trying to get the leap motion to work with Mirror Networking so two players in VR can see one another hands and finger movements. To complicate things I am also using a Vituix Omni so simply following and updating the multi player solution found at https://forums.leapmotion.com/t/tutorial-rigged-hands-across-a-network-using-unity-networking-unet/6629 is not working. In-spite of all that I don't think this is an issue with the Omni, VR or Leap I think I am missing something in the network code.
I did use the network skeleton builder script from the above tutorial and borrowed many ideas to make my own network player however I also have a local player controller and while this works perfectly for the client the host has an issue where the hands are not disappearing from the clients view when the leap motion losses tracking. This is only happening to the host players hands in the client players view. When the clients hands lose tracking they are no longer visible to anyone as intended.
using UnityEngine;
using Mirror;
public class LeapNetPlayerCtrl : NetworkBehaviour
{
public float stopThreshold = 0.0001F;
private Vector3 oldLeftEulerAngles;
private Vector3 oldRightEulerAngles;
EnableEventRelay leftHandEnabler;
EnableEventRelay rightHandEnabler;
//source gameobjects head, left and right hand and...
private GameObject theLocalPlayer;
private GameObject headlessPlayer;
GameObject NetworkedPlayer;
//...joints of local player
#region local joints
[Tooltip("These local private feilds are only visible for visual troubleshooting" +
" They should load when the game server connects." +
"Only the root of each finger is shown but each joint is loaded.")]
[Header("Local Player / Auto Loads on Start")]
[SerializeField] private GameObject localHead = default; //iterating all the child bones rather then placing them below may be better if possible?
[SerializeField] private GameObject localLeftHand = default;
private GameObject localLeftWrist = default;
private GameObject localLeftPalm = default;
[SerializeField] private GameObject localLeftIndexMeta = default;
private GameObject localLeftIndexA = default;
private GameObject localLeftIndexB = default;
private GameObject localLeftIndexC = default;
private GameObject localLeftIndexEnd = default;
//And so on for each finger and joint on each hand
#endregion
//Player parts viewable to others and hidden from the local player
#region networked joints
[Header("Networked Player Head")]
//Player parts viewable to others and hidden from the local player
[SerializeField] private GameObject netHeadObj = default;
[Header("Network Left Hand")]
[SerializeField] private GameObject netLeftHand = default;
[SerializeField] private GameObject netLeftWrist = default;
[SerializeField] private GameObject netLeftPalm = default;
[SerializeField] private GameObject netLeftIndexMeta = default;
[SerializeField] private GameObject netLeftIndexA = default;
[SerializeField] private GameObject netLeftIndexB = default;
[SerializeField] private GameObject netLeftIndexC = default;
[SerializeField] private GameObject netLeftIndexEnd = default;
// repeated for each hand and all joints
#endregion
//private void Awake()
//{
// DontDestroyOnLoad(this.gameObject); //Handled by the NetworkManager
//}
void Start()
{
//Debug.Log("Start of the vr player");
NetworkedPlayer = this.gameObject;
if (isLocalPlayer)
{
//disabled controller meshes at Local VR player side so it cannot be viewed by local player
netHeadObj.GetComponent<MeshRenderer>().enabled = false;
netLeftHand.GetComponentInChildren<SkinnedMeshRenderer>().enabled = false;
netRightHand.GetComponentInChildren<SkinnedMeshRenderer>().enabled = false;
//}
//if (isClient)
//{
oldLeftEulerAngles = netLeftPalm.transform.rotation.eulerAngles;
oldRightEulerAngles = netRightPalm.transform.rotation.eulerAngles;
}
else
{
leftHandEnabler = netLeftHand.gameObject.AddComponent<EnableEventRelay>();
leftHandEnabler.Enabled = new UnityEngine.Events.UnityEvent();
leftHandEnabler.Disabled = new UnityEngine.Events.UnityEvent();
leftHandEnabler.Enabled.AddListener(CmdLeftHandEnable);
leftHandEnabler.Disabled.AddListener(CmdLeftHandDisable);
rightHandEnabler = netRightHand.gameObject.AddComponent<EnableEventRelay>();
rightHandEnabler.Enabled = new UnityEngine.Events.UnityEvent();
rightHandEnabler.Disabled = new UnityEngine.Events.UnityEvent();
rightHandEnabler.Enabled.AddListener(CmdRightHandEnable);
rightHandEnabler.Disabled.AddListener(CmdRightHandDisable);
}
}
void Update()
{
updateLeapHeadAndHands();
if (!isLocalPlayer)
{
return;
}
//sync pos on network
OnStartLocalPlayer();
}
#region Hand Enabling/Disabling
[Command]
void CmdLeftHandEnable()
{
if (isClient) netLeftHand.gameObject.SetActive(true);
else RpcLeftHandEnable();
}
[ClientRpc]
void RpcLeftHandEnable()
{
netLeftHand.gameObject.SetActive(true);
}
[Command]
void CmdLeftHandDisable()
{
if (isClient && isLocalPlayer) netLeftHand.gameObject.SetActive(false);
else RpcLeftHandDisable();
}
[ClientRpc]
void RpcLeftHandDisable()
{
netLeftHand.gameObject.SetActive(false);
Debug.Log("Left Hand Disable");
}
// right hand
[Command]
void CmdRightHandEnable()
{
if (isClient && isLocalPlayer) netRightHand.gameObject.SetActive(true);
else RpcRightHandEnable();
}
[ClientRpc]
void RpcRightHandEnable()
{
netRightHand.gameObject.SetActive(true);
Debug.Log("Rght Hand Enable");
}
[Command]
void CmdRightHandDisable()
{
if (isClient && isLocalPlayer) netRightHand.gameObject.SetActive(false);
else RpcRightHandDisable();
}
[ClientRpc]
void RpcRightHandDisable()
{
netRightHand.gameObject.SetActive(false);
Debug.Log("Right hand Disable");
}
#endregion
//instantiate networkPlayer prefab and connect to Local Player Rig
public override void OnStartLocalPlayer()
{
// this is ONLY called on local player
//Debug.Log(gameObject.name + "Entered local start player, locating rig objects");
//isLinkedToVR = true;
// find the gaming rig in the scene and link to it
if (theLocalPlayer == null)
{
theLocalPlayer = GameObject.Find("[CameraRig]");// find the rig in the scene
}
// Link localHMD, localHands to the Rig so that they are
// automatically filled when the rig moves
localHead = theLocalPlayer.transform.Find("Camera (eye)").gameObject;
#region local left and joints
localLeftHand = theLocalPlayer.transform.GetChild(2).GetChild(0).gameObject; //Left hand
localLeftWrist = theLocalPlayer.transform.GetChild(2).GetChild(0).GetChild(0).gameObject; //wrist
localLeftPalm = theLocalPlayer.transform.GetChild(2).GetChild(0).GetChild(0).GetChild(0).gameObject; //palm
localLeftIndexMeta = theLocalPlayer.transform.GetChild(2).GetChild(0).GetChild(0).GetChild(0).GetChild(0).gameObject; //Index
localLeftIndexA = theLocalPlayer.transform.GetChild(2).GetChild(0).GetChild(0).GetChild(0).GetChild(0).GetChild(0).gameObject;
// And so on for each joint on both hands of the leap motion LoPoly Rigged hands
#endregion
}
void updateLeapHeadAndHands()
{
if (!isLocalPlayer)
{
// do nothing, networktransform, FlexNetworkTransform, or smooth sync does all the work here.
}
else
{
// we are the local player.
// Copy the values from the Rig's parts so they can be used for positioning the online presence
#region head
// prevent headless version of app from crashing
// depends on SteamVR version if HMD is null or simply won't move
if (localHead == null)
{
headlessPlayer = GameObject.Find("NoSteamVRFallbackObjects");
// when running as headless, provide default non-moving objects instead
localHead = headlessPlayer.transform.Find("HeadCollider").gameObject;
localLeftHand = headlessPlayer.transform.Find("FallbackHand").gameObject;
localRightHand = headlessPlayer.transform.Find("FallbackHand").gameObject;
Debug.Log("HEADLESS detected");
}
NetworkedPlayer.transform.position = localHead.transform.position;
NetworkedPlayer.transform.rotation = localHead.transform.rotation;
#endregion
#region left Hand
if (localLeftHand)
{
// With the left Hand connected
// Is there a better way to do this with a dictionary or something? I'm not sure how you could loop through and match these?
netLeftHand.transform.position = localLeftHand.transform.position;
netLeftHand.transform.rotation = localLeftHand.transform.rotation;
netLeftWrist.transform.position = localLeftWrist.transform.position;
netLeftWrist.transform.rotation = localLeftWrist.transform.rotation;
netLeftPalm.transform.position = localLeftPalm.transform.position;
netLeftPalm.transform.rotation = localLeftPalm.transform.rotation;
//This gets local information but is not syncing across the network with the Cmd and Rpc I get the client hands working as intended but the host hands flip out and break.
if (Mathf.Abs(oldLeftEulerAngles.x - netLeftPalm.transform.rotation.eulerAngles.x) < stopThreshold)
{
//NO ROTATION
CmdLeftHandDisable();
}
else
{
oldLeftEulerAngles = netLeftPalm.transform.rotation.eulerAngles;
//ROTATION
CmdLeftHandEnable();
}
netLeftIndexMeta.transform.position = localLeftIndexMeta.transform.position;
netLeftIndexMeta.transform.rotation = localLeftIndexMeta.transform.rotation;
netLeftIndexA.transform.position = localLeftIndexA.transform.position;
netLeftIndexA.transform.rotation = localLeftIndexA.transform.rotation;
// more of the above lines repeat to link each local hand to the networked hand.
}
#endregion
#region right hand
if (localRightHand)
{
// only if right hand is connected
netRightHand.transform.position = localRightHand.transform.position;
netRightHand.transform.rotation = localRightHand.transform.rotation;
netRightWrist.transform.position = localRightWrist.transform.position;
netRightWrist.transform.rotation = localRightWrist.transform.rotation;
netRightPalm.transform.position = localRightPalm.transform.position;
netRightPalm.transform.rotation = localRightPalm.transform.rotation;
if (Mathf.Abs(oldRightEulerAngles.x - netRightPalm.transform.rotation.eulerAngles.x) < stopThreshold)
{
//NO ROTATION
CmdRightHandDisable();
}
else
{
oldRightEulerAngles = netRightPalm.transform.rotation.eulerAngles;
//ROTATION
CmdRightHandEnable();
}
netRightIndexMeta.transform.position = localRightIndexMeta.transform.position;
netRightIndexMeta.transform.rotation = localRightIndexMeta.transform.rotation;
netRightIndexA.transform.position = localRightIndexA.transform.position;
netRightIndexA.transform.rotation = localRightIndexA.transform.rotation;
//And so on to link all the fingers on the right hand.
}
#endregion
}
}
}
This all seems to work from the client point of view that is when the client moves their hands out of the leaps field of view the hands disappear for both the client and the host. However when the hosts hands loss tracking they only disappear from the host view and the client is left viewing a pair of floating hands that the hosts head can "walk" away from. So I'm clearly doing something wrong with the Mirror Synchronization but I don't what?
Answer by Giantbean · Dec 20, 2021 at 06:43 PM
I changed the Commands to :
[Command]
void CmdLeftHandDisable()
{
if (isClient && hasAuthority)
{
netLeftHand.gameObject.SetActive(false);
else RpcLeftHandDisable();
}
else RpcLeftHandDisable();
}
Its working now!