Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Network sync issues

Discussion in 'Multiplayer' started by VCC_Geek, Jan 4, 2018.

  1. VCC_Geek

    VCC_Geek

    Joined:
    Apr 30, 2013
    Posts:
    29
    Warning - I have 200+ lines of code here, so if you're not a programmer, don't torture yourself.

    Edit: kienbb was nice enough to help me with this, so here's the bottom line. You can't use anything but primitives as a SyncVar. If you want to use an object, then grab NetworkIdentity.netId.Value, which is a uint, and sync that. Then, on the client side, use ClientScene.FindLocalObject() to find the client-side copy of the object.

    Edit2: That might be NetworkServer.FindLocalObject(). I'm still experimenting and learning. Please note that the below code doesn't work, and probably won't work. I need to rewrite it.

    Okay, guys, I'm not one to ask for help easily, so this might be a doozy. Or I'm just an idiot. That definitely might be the case. At any rate, I'm trying to implement grabbing an object and throwing it using the Oculus Touch. That's not the hard part - I can do that in my sleep. The hard part is that I need to sync it over the network. Right now, my opinion of Unity Networking isn't very high, but then again, that might be a function of my frustration level. I've been working on this for over a week, 8-10 hours a day, with no luck. I've rewritten it six times. I've Googled my fingers off. I'm not seeing much in the way of documentation on most commands, but if I'm wrong, please point me to the wealth of information that I'm missing. At this point, I don't even care if it's a stupid mistake on my part. I'll just be happy to have it working. I know this is long, but I'm not sure where the problem is, and I don't want to leave out something that might be important. I'm on Unity 2017.1.0p4 with Windows 10.

    Here are the parts I think I understand. Please correct as necessary.

    [Server] functions run only on the server, and throw a warning and refuse to run if the client tries to call them.

    [Client] functions only run on the client, and throw a warning and refuse to run if the server tries to run them.

    [Command] functions have to start with Cmd. They are called by the client, but are run on the server. Presumably, that means that the code in a [Command] function operates on the server-side copies of the variables. This is the method for allowing a client to set a variable on the server, which then syncs it to all of the clients.

    [ClientRpc] functions have to start with Rpc. They are called by the server, but are run on the client.

    [SyncVar] variables are automatically synchronised across the network by the server. I assume a [SyncVar] should be set on the server only, and then allow the server to sync it to the clients. It can be given a hook, which is a callback that is called when the variable is changed. Adding a hook disables the automatic syncing, so the syncing needs to be done in the hook function. It's unclear whether the hook runs on the server or client.

    SyncLists are lists of variables. They only sync monolithically, so if you change a member, then it won't be resynced until the whole list is.

    Okay, assuming all of that is at least marginally right, every bit of logic I can muster says that my code should work. I've spread comments liberally through the code. (I stopped trying to come up with new names for the class and started just numbering them, so this one's called NetworkGrabSync3.cs.) The grabbers get spawned both on the server and on the client, so spawning isn't an issue. The problem is that the client never gets a reference to the spawned grabbers. One thing that sporadically comes up in the log is

    Code (csharp):
    1.  
    2. Did not find target for sync message for 10
    3. UnityEngine.Networking.NetworkIdentity:UNetStaticUpdate()
    4.  
    . . . but it doesn't always show up - even if I haven't recompiled. I would think that this suggests a race condition somewhere, but I'm not sure how to track it down, or even if it matters, since it doesn't work whether the message appears or not.

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Networking;
    6. // I added this just in case it was a network latency issue - which doesn't really make sense,
    7. // because if that were the case, then it would go away after a few frames.  it doesn't.
    8. [NetworkSettings(sendInterval = 0)]
    9. public class NetworkGrabSync3 : NetworkBehaviour {
    10.     public float throwVelocity;
    11.     [Range(0f, 1f)]
    12.     public float grabThreshold;
    13.     [Range(0f, 1f)]
    14.     public float releaseThreshold;
    15.     // the grab prefab has been added to the spawnable prefabs on the NetworkManager object with
    16.     // the localPlayerAuthority checkbox checked.
    17.     public Transform grabPrefab;
    18.     public Transform leftHandAnchor;
    19.     public Transform rightHandAnchor;
    20.     public SpellScript spell;
    21.     private bool leftDown;
    22.     private bool rightDown;
    23.     public Grab leftHandGrab;
    24.     public Grab rightHandGrab;
    25.     // I had hooks on these, but they didn't help, so I removed them. I have changed this to several
    26.     // different types, including NetworkIdentity, GameObject, and Transform.  I tried one that
    27.     // isn't supposed to work, and it threw an error, and none of these do, so I assume they
    28.     // are allowed.
    29.     [SyncVar]
    30.     private NetworkIdentity leftHand;
    31.     [SyncVar]
    32.     private NetworkIdentity rightHand;
    33.     // here, the server instantiates the grabber objects, and then spawns them with client authority
    34.     // it also calls RpcSetHands(), which is SUPPOSED to assign them to the local client's reference
    35.     // to the grabber objects.
    36.     private void Start() {
    37.         NetworkIdentity nid = GetComponent<NetworkIdentity>();
    38.         Transform lh, rh;
    39.         if (nid.isServer) {
    40.             lh = Instantiate(grabPrefab);
    41.             rh = Instantiate(grabPrefab);
    42.             // this renaming does not happen on the client, although the objects themselves are actually
    43.             // spawned.  I'm not sure why.  the renaming DOES work on the server.  I would think that the
    44.             // client-spawned objects would have the same name as the GameObject it was spawned from,
    45.             // although I guess it may be pulled from the prefab instead when the object is spawned.
    46.             // removing the renaming does not help.
    47.             lh.gameObject.name = "leftHandGrabber";
    48.             rh.gameObject.name = "rightHandGrabber";
    49.             NetworkServer.SpawnWithClientAuthority(lh.gameObject, gameObject);
    50.             NetworkServer.SpawnWithClientAuthority(rh.gameObject, gameObject);
    51.             leftHand = lh.GetComponent<NetworkIdentity>();
    52.             rightHand = rh.GetComponent<NetworkIdentity>();
    53.             RpcSetHands(leftHand, rightHand);
    54.             // this does not help either, although I'm not sure I'm using it correctly.  there is no documentation
    55.             // on what the bitmask is supposed to be.  I'm sort of wildly guessing that 1 means dirty.  No idea, really.
    56.             SetDirtyBit(1);
    57.         }
    58.         // this appears in the log.  I would assume that that means leftHand has not (yet?) been synchronised
    59.         // to the client.
    60.         if (leftHand == null && isClient) Debug.Log("Start(): leftHand is null");
    61.     }
    62.     // this was the hook that I set for the SyncVar for leftHand.
    63.     public void setLeftHand(NetworkIdentity ni) {
    64.         // when the hook is enabled, this appears in the log.
    65.         if (isClient) Debug.Log("setLeftHand(): setting left hand");
    66.         leftHand = ni;
    67.         // neither one of these do.  again, I assume that this means that the variables are getting set.
    68.         if (leftHand == null) Debug.Log("setLeftHand(): NetworkIdentity is null");
    69.         if (leftHand.gameObject == null) Debug.Log("setLeftHand(): GameObject is null");
    70.     }
    71.     // ditto for rightHand.
    72.     public void setRightHand(NetworkIdentity ni) {
    73.         rightHand = ni;
    74.     }
    75.     // this appears to work correctly, as long as the grabber prefab exists.
    76.     private bool grabbingLeft {
    77.         get {
    78.             if (!leftDown) {
    79.                 if (OVRInput.Get(OVRInput.RawAxis1D.LHandTrigger) > grabThreshold) {
    80.                     leftDown = true;
    81.                 }
    82.             } else {
    83.                 if (OVRInput.Get(OVRInput.RawAxis1D.LHandTrigger) < releaseThreshold) {
    84.                     leftDown = false;
    85.                     if (leftHandGrab != null) leftHandGrab.applyVelocity();
    86.                     leftHandGrab = null;
    87.                 }
    88.             }
    89.             if (leftHandGrab == null) {
    90.                 //Debug.Log("No grab detected.");
    91.                 return false;
    92.             }
    93.             return leftDown;
    94.         }
    95.     }
    96.     // ditto.
    97.     private bool grabbingRight {
    98.         get {
    99.             if (!rightDown) {
    100.                 if (OVRInput.Get(OVRInput.RawAxis1D.RHandTrigger) > grabThreshold) {
    101.                     //Debug.Log("right hand activated");
    102.                     rightDown = true;
    103.                 }
    104.             } else {
    105.                 if (OVRInput.Get(OVRInput.RawAxis1D.RHandTrigger) < releaseThreshold) {
    106.                     //Debug.Log("right hand released");
    107.                     rightDown = false;
    108.                     if (rightHandGrab != null) rightHandGrab.applyVelocity();
    109.                     rightHandGrab = null;
    110.                 }
    111.             }
    112.             if (rightDown == false) return false;
    113.             if (rightHandGrab == null) {
    114.                 //Debug.Log("No grab detected.");
    115.                 return false;
    116.             }
    117.             return rightDown;
    118.         }
    119.     }
    120.     // this also seems to work correctly, as long as the grabber prefabs are set.
    121.     // split off client and server updates
    122.     public void FixedUpdate() {
    123.         if (GetComponent<NetworkIdentity>().isClient) {
    124.             doClientUpdate();
    125.         }
    126.         if (GetComponent<NetworkIdentity>().isServer) {
    127.             doServerUpdate();
    128.         }
    129.         // for both client and server
    130.         leftHand.transform.match(leftHandAnchor);
    131.         rightHand.transform.match(rightHandAnchor);
    132.     }
    133.     // this is where stuff breaks.
    134.     [Client]
    135.     private void doClientUpdate() {
    136.         // this appears in the log hundreds of times, which means that the null reference is not temporary.
    137.         // it NEVER gets synchronised.
    138.         if (rightHand == null) Debug.Log("doClientUpdate(): rightHand is null");
    139.         if (grabbingLeft) {
    140.             //leftHandGrab.sync();
    141.         } else {
    142.             CmdSetGrab("left", leftHand.gameObject.GetComponent<HandGrabber3>().collidingWith);
    143.         }
    144.         if (grabbingRight) {
    145.             //rightHandGrab.sync();
    146.         } else {
    147.             CmdSetGrab("right", rightHand.gameObject.GetComponent<HandGrabber3>().collidingWith);
    148.         }
    149.     }
    150.     // this supposedly sets the hand references on the client.
    151.     [ClientRpc]
    152.     private void RpcSetHands(NetworkIdentity left, NetworkIdentity right) {
    153.         // this appears in the log, which I assume means that it's getting executed.  since
    154.         // it's a ClientRpc, I would also assume that it's running on the client.
    155.         Debug.Log("RpcSetHands(): Setting hands");
    156.         leftHand = left;
    157.         rightHand = right;
    158.         // this never appears in the log, so I'm again assuming that this means that the client's
    159.         // local copy of rightHand is getting set.
    160.         if (rightHand == null) Debug.Log("RpcSetHands(): right hand is null");
    161.     }
    162.     // this is another one that works fine as long as the grabber prefabs exist.
    163.     [Command]
    164.     private void CmdSetGrab(string hand, NetworkIdentity target) {
    165.         Grab g = null;
    166.         Transform handTrans;
    167.         if (hand == "left") {
    168.             handTrans = leftHand.transform;
    169.         } else {
    170.             handTrans = rightHand.transform;
    171.         }
    172.         if (target != null) {
    173.             //Debug.Log("Registering collision with " + hand + " hand");
    174.             g = new Grab(target, target.transform, handTrans.transform, throwVelocity);
    175.         } else {
    176.             //Debug.Log("Removing collision with " + hand + " hand");
    177.         }
    178.         if (hand == "left") {
    179.             leftHandGrab = g;
    180.         } else {
    181.             rightHandGrab = g;
    182.         }
    183.     }
    184.     // ditto.
    185.     [ServerCallback]
    186.     private void doServerUpdate() {
    187.         //Debug.Log("server update");}
    188.         if (grabbingLeft) {
    189.             //Debug.Log("Syncing left hand");
    190.             leftHandGrab.sync();
    191.         }
    192.         if (grabbingRight) {
    193.             //Debug.Log("Syncing right hand");
    194.             rightHandGrab.sync();
    195.         }
    196.     }
    197. }
    198.  
    199.  
    200.  
    For reference, here's the HandGrabber script:

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEngine.Networking;
    6. public class HandGrabber3 : NetworkBehaviour {
    7.     [SyncVar]
    8.     public NetworkIdentity     collidingWith;
    9.     public Transform        anchor;
    10.     private void OnTriggerEnter(Collider other) {
    11.         if (isInvalid(other)) return;
    12.         // override any previous collisions
    13.         collidingWith = other.gameObject.GetComponent<NetworkIdentity>();
    14.     }
    15.     private void OnTriggerExit(Collider other) {
    16.         if (isInvalid(other)) return;
    17.         NetworkIdentity n = other.gameObject.GetComponent<NetworkIdentity>();
    18.         // only clear it if it's the same one we're currently colliding with
    19.         if (n == collidingWith) collidingWith = null;
    20.     }
    21.     private bool isInvalid(Collider other) {
    22.         // yes, we're returning true if the collider can't be grabbed.  makes syntax cleaner.
    23.         if (other.gameObject.GetComponent<NetworkIdentity>() == null) return true;
    24.         return false;
    25.     }
    26.     private void Update() {
    27.         //transform.match(anchor);
    28.     }
    29. }
    30.  
    31.  
    32.  
    Again, this is on a prefab, added to the NetworkManager in the spawnable prefabs, with the LocalPlayerAuthority checked. The prefab also has a BoxCollider, a Rigidbody, a NetworkIdentity (where the LocalPlayerAuthority has been checked), and a NetworkTransform.

    If anyone can help me figure out where I've gone wrong, I would be hugely grateful - even if it's a dumb mistake on my part.
     
    Last edited: Jan 8, 2018
  2. kienbb

    kienbb

    Joined:
    Sep 10, 2014
    Posts:
    13
    Code (CSharp):
    1. [SyncVar]
    2.     private NetworkIdentity leftHand;
    Huh, I think HLAPI can't sync ref type, you need change to primitive type
    ex:
    Code (CSharp):
    1. [SyncVar] private int _iHealth;
    Same above, you need change NetworkIdentity type to primitive type
    ex:
    Code (CSharp):
    1. [ClientRpc]
    2.     private void RpcSetHands(float speed, string name) {
    3.        // Do something
    4.     }
     
  3. VCC_Geek

    VCC_Geek

    Joined:
    Apr 30, 2013
    Posts:
    29
    I'll give that a shot. It seems like Unity would give an error if that was the case, though. For instance, during my adventures, more than once I ran across an error that said, "Failed to generate network code", or something to that effect. It was usually something easy to fix, as it was things like forgetting the "Cmd" prefix. It would compile and run, but it would also have that error. Another time I got that was when I intentionally gave it something I knew wasn't allowed to see what it would do.

    I do hope you're right, though. I'm going to give this a shot once I get back to my development space. I think I recall seeing an int member of the NetworkIdentity class that I could use as an identifier. The question is, can I then take that integer client-side and create the correct NetworkIdentity from it? I'll have to poke at it.

    Thanks for looking - I know that was a lot of code I barfed up, and probably not that easy to read.
     
  4. kienbb

    kienbb

    Joined:
    Sep 10, 2014
    Posts:
    13
    NetworkIdentity is component required by NetworkBehaviour component. NetworkIdentity have field AssetId for instantiate object on network. So, when you create new class extend from NetworkBehaviour and define int field then you do not care to NetworkIdentity component.
    Ex:
    You create a new GameObject and add a component(Class extend from NetworkBehaviour Component). Now, you can sync variables with SyncVar attribute or pass parameter in method.


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Networking;
    3.  
    4. namespace Network.Demo
    5. {
    6.     public class DemoNetworkBehaviour : NetworkBehaviour
    7.     {
    8.         [SyncVar] private int _iHealth = 0;
    9.         private float speed = 0;
    10.  
    11.         private void Update()
    12.         {
    13.             if (Input.GetKeyDown(KeyCode.Space))
    14.             {
    15.                 _iHealth++;
    16.             }
    17.             Debug.Log("Health: " + _iHealth);
    18.         }
    19.     }
    20. }
     
  5. VCC_Geek

    VCC_Geek

    Joined:
    Apr 30, 2013
    Posts:
    29
    That actually did do the trick, by the way (changing it to a uint, I mean). They really need better network documentation - some of the documentation pages are literally one-liners - although rumour has it that Unreal is even worse. Much appreciated for the tip, though. I'm still having some issues, but at this point, I think most of it is just baggage from trying to make the thing work. I'll probably need to do one more rewrite, and God willing, it will work. Again, thank you for the tip.
     
    Last edited: Jan 8, 2018
  6. VCC_Geek

    VCC_Geek

    Joined:
    Apr 30, 2013
    Posts:
    29
    Now that I think about it, you're exactly right - it doesn't even make sense to sync a ref type, because a ref type variable holds a memory address. There's no way in the world you can guarantee that the same object will be at the same memory location on two different machines. That's why we have a NetworkIdentity.

    I'm starting to get this more and more. The system is actually a really good one - it's just the documentation that needs some attention.
     
    Last edited: Jan 8, 2018