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?

I changed the Commands to :

[Command]
     void CmdLeftHandDisable()
     {
         if (isClient && hasAuthority) 
         {
              netLeftHand.gameObject.SetActive(false);
              else RpcLeftHandDisable();
         }
         else RpcLeftHandDisable();
     }

Its working now!