Search Unity

Resolved Netcode for GameObjects code for assigning colors to players

Discussion in 'Scripting' started by ericspataru, Dec 5, 2022.

  1. ericspataru

    ericspataru

    Joined:
    Jan 31, 2017
    Posts:
    54
    Hey everyone! I am having some trouble assigning player colors in Unity with a really basic setup, specifically for late joining clients.

    In my example, the scene only contains a NetworkManager empty game object with the following attached:
    - NetworkManager script with a Player prefab as a gameobject
    - UnityTransport, protocol type Unity Transport
    The only prefab is a Player empty game object, with the following attached:
    - NetworkObject
    - Client Network Transform (Script) - coming from Mutliplayer Tools package I believe
    - PlayerNetwork (personal script)

    The setup is one of the players is the host, and one is the client.
    In my script, players can press a button to turn yellow.

    Desired behaviour
    1. If both users are connected, they can walk, see each other and see each other changing colours
    2. If only host is connected and host changes color, and client joins after the action was done, client should see host with the changed color

    Actual behaviour
    1 works
    2 does not. Late-Connected client does not see the new color of the host, but the host can see it

    Code (CSharp):
    1. public class PlayerNetwork : NetworkBehaviour
    2. {
    3.  
    4.     [SerializeField] private Renderer teamColorRenderer;
    5.     private NetworkVariable<PlayerColor> playerColor = new NetworkVariable<PlayerColor>();
    6.  
    7.     private void OnEnable()
    8.     {
    9.         playerColor.OnValueChanged += OnColorChanged;
    10.     }
    11.  
    12.     private void OnDisable()
    13.     {
    14.         playerColor.OnValueChanged -= OnColorChanged;
    15.     }
    16.  
    17.     private void OnColorChanged(PlayerColor oldColor, PlayerColor newColor)
    18.     {
    19.         if(!IsClient) {
    20.             return;
    21.         }
    22.  
    23.         string colorDecided = "Yellow";
    24.         Material color = Resources.Load(colorDecided, typeof(Material)) as Material;
    25.         teamColorRenderer.material = color;
    26.     }
    27.  
    28.     void Update() {
    29.         if (!IsOwner) {
    30.             return;
    31.         }
    32.  
    33.         if(Input.GetKeyDown(KeyCode.T)) {
    34.             ChangeColorServerRpc(PlayerColor.YELLOW);
    35.         }
    36.  
    37.         Vector3 moveDir = new Vector3(0, 0, 0);
    38.         if(Input.GetKey(KeyCode.W)) { moveDir.z = +1f; }
    39.         if(Input.GetKey(KeyCode.S)) { moveDir.z = -1f; }
    40.         if(Input.GetKey(KeyCode.A)) { moveDir.x = -1f; }
    41.         if(Input.GetKey(KeyCode.D)) { moveDir.x = +1f; }
    42.         transform.position += moveDir * 3f * Time.deltaTime;
    43.     }
    44.  
    45.     [ServerRpc]
    46.     public void ChangeColorServerRpc(PlayerColor chosenColor)
    47.     {
    48.         playerColor.Value = chosenColor;
    49.     }
    50. }
    51.  
    According to the docs, NetworkVariables are synced for late joining clients too, and from my understanding the fact that I use ServerRpc for setting it should not be a problem. Even though the ServerRpc won't re-execute for the late joining client, the NetVar will be synced already. So my only guess is the NetVar is synced appropriately, but the capsule renderer isn't.

    And so my question is, how do I also sync the capsule renderer?

    Thanks
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,752
    Rather than guess, you should instrument and debug. It will save you a great deal of time.

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    IF you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    When in doubt, print it out!(tm)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
  3. ericspataru

    ericspataru

    Joined:
    Jan 31, 2017
    Posts:
    54
    Hey @Kurt-Dekker , thanks for getting in touch.
    I would print everything there is to print, but I don't know how and what.
    In this case, it's not that a Debug.log(printColor) wouldn't work. It just doesn't help me. I already know it is being correctly assigned.

    The core problem for me is that I don't know how to access the NetworkVariable of other connected clients.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,752
    When debugging you gotta be able to set it up with two computers locally and debug each one.

    For instance, the questions I would have is if your onvaluechanged callbacks happen ... that's the START of the entire process.

    Also, as a warning, I have zero specific domain experience in this networking package, so I cannot speak to if you are doing any of it correctly, so I am assuming that you ALMOST are, but there's some subtle detail missing.

    Intuitively, I wonder also about Line 5 above, how you
    new
    up a network variable.

    Almost 100% of everything in Unity CANNOT be created with new. Yes, a few things can, but not things that interoperate, such as MonoBehaviours and / or ScriptableObjects.

    Are you sure you're not supposed to call something like a CreateInstance<T>() function to make one of those?

    ALSO: if that is the case you cannot just replace the above new with a CreateIsntance: it has to NOT be a field initializer, since field initializers for UnityEngine.Object-derived classes are actually not called on the main thread.

    I would look at some sample code, just to make sure you are following known patterns.
     
  5. MaskedMouse

    MaskedMouse

    Joined:
    Jul 8, 2014
    Posts:
    1,092
    That line is fine. Nothing wrong with that. It isn't a UnityEngine.Object type.

    Each connected client has their own instance. The color change has already happened for the host (or even other players) before this client connected. So the
    OnColorChanged()
    is not called in that case. Set a break point in the OnColorChanged method. Is it called? probably not.
    So you aren't applying the color anywhere that already comes from the
    NetworkVariable
    .
    For all connected clients it has to apply that color.

    So upon spawning, set the color that is in the network variable.
    You can use
    override void OnNetworkSpawn()
    for this.

    When a client connects it has to spawn in all other players. So all their
    OnNetworkSpawn
    gets called setting the current color value.

    https://docs-multiplayer.unity3d.com/netcode/current/basics/networkvariable

    Also there's a dedicated subforum for Netcode for GameObjects.
    https://forum.unity.com/forums/netcode-for-gameobjects.661/
     
    ericspataru and Kurt-Dekker like this.
  6. ericspataru

    ericspataru

    Joined:
    Jan 31, 2017
    Posts:
    54
    Thank you, that indeed fixed my problem. And I understood the explanation.
    I suspected something like that, but I only duplicated the renderer assignment code into `OnEnabled`, thinking that'd do it for the clients in the case of late-connecting client. But I was wrong, apparently there's a huge difference between when and how OnEnabled gets called and OnNetworkSpawn.