Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Transfer In-Scene Network Object Ownership

Discussion in 'Netcode for GameObjects' started by Flix2299, Dec 6, 2023.

  1. Flix2299

    Flix2299

    Joined:
    Jun 8, 2022
    Posts:
    3
    Hello Unity Community,

    I am currently getting into the topic of "multiplayer" and am trying to have a network object that already exists in the scene controlled alternately by several clients. I am using ParallelSync for testing. With one editor I join as host, with the other I join as client. My network object has two scripts, a simple one for movement (PlayerNetwork.cs), the other for transferring ownership (testAccessibility.cs).

    PlayerNetwork.cs:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using Unity.Netcode;
    6. using UnityEngine;
    7.  
    8. public class PlayerNetwork : NetworkBehaviour
    9. {
    10.     private void Update()
    11.     {
    12.         if (!IsOwner)
    13.         {
    14.             return;
    15.         }
    16.         Vector3 moveDir = new Vector3(0, 0, 0);
    17.  
    18.         if (Input.GetKey(KeyCode.W)) moveDir.z = +1f;
    19.         if (Input.GetKey(KeyCode.S)) moveDir.z = -1f;
    20.         if (Input.GetKey(KeyCode.A)) moveDir.x = -1f;
    21.         if (Input.GetKey(KeyCode.D)) moveDir.x = +1f;
    22.      
    23.         MovementServerRPC(moveDir);
    24.      
    25.      
    26.     }
    27.  
    28.     [ServerRpc(RequireOwnership = false)]
    29.     private void MovementServerRPC(Vector3 moveDir)
    30.     {
    31.         float moveSpeed = 3f;
    32.         transform.position += moveDir * moveSpeed * Time.deltaTime;
    33.     }
    34. }
    testAccessibility.cs:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6. using Unity.Netcode;
    7. using UnityEngine;
    8. using UnityEngine.Rendering.VirtualTexturing;
    9.  
    10. public class testAccessibility : NetworkBehaviour
    11. {
    12.     private PlayerNetwork ownedObject;
    13.  
    14.     public void OnEnable()
    15.     {
    16.         ownedObject = FindObjectOfType<PlayerNetwork>();
    17.     }
    18.  
    19.     private void Update()
    20.     {
    21.         if (!IsOwner)
    22.         {
    23.             //Debug.Log("I have no Ownership!");
    24.             return;
    25.         }
    26.  
    27.         if (Input.GetKeyDown(KeyCode.T))
    28.         {
    29.             SwitchOwnerServerRPC();
    30.         }
    31.     }
    32.  
    33.     [ServerRpc(RequireOwnership = false)]
    34.     private void SwitchOwnerServerRPC()
    35.     {
    36.         Debug.Log("Actual Object OwnerId: " + ownedObject.OwnerClientId.ToString());
    37.         List<ulong> clientsIds = NetworkManager.ConnectedClientsIds.ToList();
    38.        
    39.         if (ownedObject.OwnerClientId == 0 && clientsIds.Contains(1))
    40.         {
    41.             ownedObject.NetworkObject.ChangeOwnership(1);
    42.         }
    43.         else if (ownedObject.OwnerClientId == 1 && clientsIds.Contains(0))
    44.         {
    45.             ownedObject.NetworkObject.ChangeOwnership(0);
    46.         }
    47.            
    48.         Debug.Log("New Object OwnerID: " + ownedObject.OwnerClientId.ToString());
    49.         Debug.Log(ownedObject.IsOwner);
    50.     }
    51.    
    52. }
    53.  
    The following problems occur during testing:

    1. only the host can control the network object, the client cannot move anything. (As far as I know, all in-scene network objects are server-owned by default, but that's exactly why I'm trying to use the RPC calls, right?).

    2. if I want to perform the ownership transfer at the host using the T key, no error is thrown and the OwnerID of the object has been changed to that of the other client and the control at the host is gone. However, the client with the new ownership is also unable to control the network object. However, if you then press the T key on this other client, the ownership switches back to the host and the object can be moved by the host again. The client remains connected and the switch just described can be repeated as often as required.


    upload_2023-12-6_22-42-42.png

    3. Also in the output I find it strange that "Debug.Log(ownedObject.IsOwner);" is False when switching from host to client (first change from 0 to 1), but True from client to host (second change from 1 to 0).

    How can I manage to switch back and forth between the clients and always have the current owner take control?


    Please excuse me if there are many simple errors in the code, I am still a complete beginner. I am grateful for every new insight. Thank you for your help.
     
  2. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    This is the correct behavior since the SwitchOwnerServerRPC is executing only on the host-server side (i.e. it should return false when the client owns it...because the server no longer owns it...and then true when the client turns it back over to the server)

    A few questions:
    • I am assuming both of the scripts provided are attached to the same in-scene placed NetworkObject?
    • Do you have a NetworkTransform attached to the in-scene placed NetworkObject?
    • Do you have a Rigidbody attached to to the in-scene placed NetworkObject?
      • If so, do you have a NetworkRigidbody also attached?
     
  3. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    You might just replace both of your scripts (and if you have a NeworkTransform remove that too) with this:
    Code (CSharp):
    1. using Unity.Netcode;
    2. using Unity.Netcode.Components;
    3. using UnityEngine;
    4.  
    5. #if UNITY_EDITOR
    6. using UnityEditor;
    7. // This bypases the default custom editor for NetworkTransform
    8. // and lets you modify your custom NetworkTransform's properties
    9. // within the inspector view
    10. [CustomEditor(typeof(TransformMotionController), true)]
    11. public class TransformMotionControllerEditor : Editor
    12. {
    13. }
    14. #endif
    15. public class TransformMotionController : NetworkTransform
    16. {
    17.  
    18.     public enum AuthorityModes
    19.     {
    20.         Server,
    21.         Owner
    22.     }
    23.     public AuthorityModes AuthorityMode;
    24.  
    25.     [Range(0.01f, 10.0f)]
    26.     public float MovementSpeed = 3.0f;
    27.  
    28.     protected Vector2 m_MoveDir;
    29.  
    30.     protected override bool OnIsServerAuthoritative()
    31.     {
    32.         return AuthorityMode == AuthorityModes.Server;
    33.     }
    34.  
    35.     protected virtual void OnOwnerUpdate()
    36.     {
    37.         m_MoveDir = Vector2.zero;
    38.  
    39.         if (Input.GetKey(KeyCode.W)) m_MoveDir.y = +1;
    40.         if (Input.GetKey(KeyCode.S)) m_MoveDir.y = -1;
    41.         if (Input.GetKey(KeyCode.A)) m_MoveDir.x = -1f;
    42.         if (Input.GetKey(KeyCode.D)) m_MoveDir.x = +1f;
    43.  
    44.         // If server authoritative and we are not the server, then send the movement
    45.         // direction to the server. Otherwise, if owner authoritative then it will
    46.         // just be applied and the updates to the transform will be propagated.
    47.         if (OnIsServerAuthoritative() && !IsServer && m_MoveDir.magnitude > 0)
    48.         {
    49.             UpdateMoveDirServerRpc(m_MoveDir);
    50.         }
    51.  
    52.         if (Input.GetKeyDown(KeyCode.T))
    53.         {
    54.             if (IsServer)
    55.             {
    56.                 var minClientCount = IsHost ? 2 : 1;
    57.                 if (NetworkManager.ConnectedClientsIds.Count >= minClientCount)
    58.                 {
    59.                     // Just selecting the first remote client, you can adjust as needed
    60.                     OnSwitchOwner(NetworkManager.ConnectedClientsIds[minClientCount - 1]);
    61.                 }
    62.             }
    63.             else
    64.             {
    65.                 SwitchOwnerServerRPC();
    66.             }
    67.         }
    68.     }
    69.  
    70.     protected virtual void OnUpdateMotion()
    71.     {
    72.         if (m_MoveDir.magnitude > 0)
    73.         {
    74.             var motion = Vector3.zero;
    75.             motion.x = m_MoveDir.x;
    76.             motion.z = m_MoveDir.y;
    77.             transform.position += motion * MovementSpeed * Time.deltaTime;
    78.             m_MoveDir = Vector2.zero;
    79.         }
    80.     }
    81.  
    82.     protected override void Update()
    83.     {
    84.         // If the instance is not spawned, exit early
    85.         if (!IsSpawned)
    86.         {
    87.             return;
    88.         }
    89.  
    90.         // Owner will control motion and switching of ownership
    91.         if (IsOwner)
    92.         {
    93.             OnOwnerUpdate();
    94.         }
    95.  
    96.         // Authority (determined by OnIsServerAuthoritative) will apply the motion
    97.         if (CanCommitToTransform)
    98.         {
    99.             OnUpdateMotion();
    100.         }
    101.  
    102.         // You must invoke the base Update for Interpolation to work properly
    103.         base.Update();
    104.     }
    105.  
    106.     /// <summary>
    107.     /// Sending only the axis that need to be sent to reduce bandwidth consumption.
    108.     /// Use the ServerRpcParams to get the client identifier of the sender (this is automatically set for you)
    109.     /// </summary>
    110.     [ServerRpc(RequireOwnership = false)]
    111.     protected void UpdateMoveDirServerRpc(Vector2 moveDir, ServerRpcParams serverRpcParams = default)
    112.     {
    113.         if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
    114.         {
    115.             return;
    116.         }
    117.         m_MoveDir = moveDir;
    118.     }
    119.  
    120.  
    121.     [ServerRpc(RequireOwnership = false)]
    122.     protected void SwitchOwnerServerRPC()
    123.     {
    124.         OnSwitchOwner(NetworkManager.ServerClientId);
    125.     }
    126.  
    127.     protected virtual void OnSwitchOwner(ulong clientId)
    128.     {
    129.         // If already set or not running in the server context, then do nothing
    130.         if (OwnerClientId == clientId || !IsServer)
    131.         {
    132.             return;
    133.         }
    134.         Debug.Log($"Original Owner: {OwnerClientId}");
    135.         NetworkObject.ChangeOwnership(clientId);
    136.         Debug.Log($"New Owner: {OwnerClientId}");
    137.     }
    138. }
    It should provide you with a motion controller that will work both as server authoritative or owner authoritative.
     
  4. Flix2299

    Flix2299

    Joined:
    Jun 8, 2022
    Posts:
    3
    Hi,

    thank you very much for the quick reply.

    To answer your questions first:
    Yes, both scripts are attached to the in-scene placed NetworkObject.
    This network object also has a NetworkTransform. But here for testing I did not use a Rigidbody.

    I launched Unity today and wanted to test the current logic again, and now everything works as intended. I haven't changed anything since yesterday and spent 3 hours yesterday trying to find my logic error. So I have no idea why it works today.

    Thanks also for the additional script, which I tried to use as described, but when the client tried to connect, he disconnected immediately. Maybe I just didn't use it correctly.

    I also have a few questions about your code and Unity:

    1. I understand roughly what NetworkTransform and NetworkBehavior do, but since you now also have the motion control in NetworkTransform in your code, I am confused about what I do in which class. So what is better to do in NetworkTransform and what in NetworkBehavior? What are the differences?

    2. In your code I don't understand exactly what the empty class with the inheritance from Editor is useful for. Why is it there?

    3. Whenever the client wants to do something in a server authoritative environment, then this can simply be sent to the server as a request via ServerRPC, correct? Or are there restrictions?

    Happy holidays
     
  5. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    You might have run into a bug with the default network prefab list. If you are using the default network prefab list I would recommend creating your own:
    Right click in a folder you wish to create your project specific network prefab list, select Create-->Netcode-->Network Prefab List. Next, populate that list with all network prefabs you are using in your project.

    I actually tested the script and it should "just work".


    Really it is an order of operations thing and I have (personally) found it more useful to have access to the NetworkTransform's overrides and properties (i.e. CanCommitToTransform is a quick way to know you have authority).
    NetworkTransform derives from NetworkBehaviour... so the primary difference is that it is just a child of NetworkBehaviour that has code to synchronize the GameObject transform that the NetworkTransform component is attached to.
    Really, it is up to your own preference as to what feels more comfortable to you... but it also depends upon "how many things" you will have spawned... for players it isn't that big of deal having another component updating every frame... but if you have a bunch of AI running around it is better to handle all of your "motion related logic" in a single update then to have 2x the updates (i.e. one for NetworkTransform and one for your custom NetworkBehaviour applying motion). It just makes more sense (in my personal opinion) to do "motion related things" in the same update/component. For a couple dozen spawned objects it isn't that big of a deal, but when you hit several hundred, like say 350, spawned objects moving around it is more performant to have only 350 updates for motion than to have 700.

    That basically disables the transform editor control. If that isn't there then you will never see any of the serialized fields you add to your derived NetworkTransform class. (something we plan on fixing but it isn't the highest in the queue of things to resolve...getting close to reaching those level of bugs...but not quite yet there ;))

    You are correct. When using a server authoritative model, you would typically send some form of input or motion vector to the server and then the server moves the object for you.
     
    Last edited: Dec 9, 2023