Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Client to execute a method on another client

Discussion in 'Netcode for GameObjects' started by GradientOGames, Jun 9, 2023.

  1. GradientOGames

    GradientOGames

    Joined:
    Feb 8, 2021
    Posts:
    28
    I am currently delving into netcode and I want to allow a client to execute a method on another client with a vector3 input. How would I be able to do this? I have heard of client rpcs, is that what I'm looking for?

    Or can I just do it directly, what I want to do is transfer velocity from one player to another when they collide and in a singleplayer game I can use OnCollisionEnter, but how do I do that with multiplayer? Trying to execute the method it wont let me, presumably because the client wasn't the owner of the other client's player I was executing code in.

    Help is much appreciated :)
     
  2. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    Hi @GradientOGames !

    Netcode for GameObjects is server authoritative, so your clients don't talk to each other directly. To achive what you want you need to send a ServerRPC, and then a targeted ClientRPC.

    The RPC documentation contains examples about this.

    Does this help?
     
  3. GradientOGames

    GradientOGames

    Joined:
    Feb 8, 2021
    Posts:
    28
    It really does help! Although only partially, the documentation doesn't describe where I would put the code.
    When I write the serverrpc code on the client script, will it just execute on the server for all players, e.g. If I call a server rpc can I just spawn a network prefab somewhere or is there other things I need to add? And when do the clientrpc do I just write the code on the same script or is there a specific place I write the server rpc?

    Would it be something like this?

    (written in psuedoish-code)
    Code (CSharp):
    1. public class PlayerMovement_FPS : NetworkBehaviour
    2. {
    3.     void OnCollisionEnter(Collision collision)
    4.     {
    5.         if (collision.gameObject.tag == "Player")
    6.         {
    7.             GetID = //how would I get playerid from collision?
    8.          
    9.             SetVelocityServerRpc(Velocity, GetID)
    10.         }
    11.     }
    12.  
    13.     [ServerRpc]
    14.     void SetVelocityServerRpc(Vector3 Velocity, PlayerID ID)
    15.     {
    16.         if (!IsServer) return;
    17.  
    18.         ClientRpcParams clientRpcParams = new ClientRpcParams
    19.         {
    20.             Send = new ClientRpcSendParams
    21.             {
    22.                 TargetClientIds = new ulong[] { ID }
    23.             }
    24.         };
    25.  
    26.         SetVelocityClientRpc(Velocity, clientRpcParams);
    27.     }
    28.  
    29.     [ClientRpc]
    30.     void SetVelocityClientRpc(Vector3 Velocity, ClientRpcParams clientRpcParams)
    31.     {
    32.         if (IsOwner) return;
    33.  
    34.         rb.velocity = Velocity;
    35.     }
    36. }
    If you couldn't bother reading my above paragraph here I my follow-up questions:
    -Would my code above work?
    -How would I get the playerID from a collision?

    I gleefully appreciate your help and await your reply :D
     
  4. TruckerJoe

    TruckerJoe

    Joined:
    Feb 25, 2019
    Posts:
    39
    I think, in your clientRpc this:
    Code (CSharp):
    1. if (IsOwner) return;
    should be:
    Code (CSharp):
    1. if (!IsOwner) return;
    or the hole line can be removed...
     
  5. GradientOGames

    GradientOGames

    Joined:
    Feb 8, 2021
    Posts:
    28
    I got sidetracked making a nametag system and I got it working flawlessly, now back to this. This is my code:
    Code (CSharp):
    1. void OnCollisionEnter(Collision collision)
    2.     {
    3.         if (!IsOwner) return;
    4.  
    5.         Debug.Log("Collided");
    6.  
    7.         Vector3 V = Fields.Rigidbody.velocity;
    8.  
    9.         if (collision.gameObject.CompareTag("Player"))
    10.         {
    11.             Debug.Log("Collided with player");
    12.  
    13.             ulong ID = collision.gameObject.GetComponent<NetworkObject>().NetworkObjectId;
    14.  
    15.             SetVelocityServerRpc(V, ID);
    16.         }
    17.     }
    18.  
    19.     [ServerRpc]
    20.     void SetVelocityServerRpc(Vector3 Velocity, ulong ID)
    21.     {
    22.         Debug.Log("send message to server");
    23.  
    24.         ClientRpcParams clientRpcParams = new ClientRpcParams
    25.         {
    26.             Send = new ClientRpcSendParams
    27.             {
    28.                 TargetClientIds = new ulong[] { ID }
    29.             }
    30.         };
    31.  
    32.         SetVelocityClientRpc(Velocity, clientRpcParams);
    33.     }
    34.  
    35.     [ClientRpc]
    36.     void SetVelocityClientRpc(Vector3 Velocity, ClientRpcParams clientRpcParams)
    37.     {
    38.         Debug.Log("send message to client with velocity: " + Velocity);
    39.  
    40.         Fields.Rigidbody.velocity = Velocity;
    41.     }
    Doing that made no difference, at the moment the serverrpc is being sent succesfully but executing the clientrpc throws this warning: Trying to send ClientRpcMessage to client 2 which is not in a connected state.

    I think it is something to do with the serverrpc not creating correct clientrpcparameters. instead of using clientrpcparams, would it be feasible to just use network object ids instead? (if that is the issue of course)

    Thanks! :)
     
  6. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    656
    Try replacing NetworkObjectId with OwnerClientId.
     
  7. GradientOGames

    GradientOGames

    Joined:
    Feb 8, 2021
    Posts:
    28
    Thanks for the reply! I figured that one out myself a few hours ago, it now works successfully on both client (supposed to be one of them but ez fix) and successfully puts the velocity value in the console! But... now it throws a NullReferenceException (NRE) and I have never encountered anything like it in the past. It appears to be a multiplayer thing but it is having trouble setting the velocity from what I can tell of the stack trace:

    (first three lines)
    NullReferenceException
    UnityEngine.Rigidbody.set_velocity (UnityEngine.Vector3 value) (at <49ef468a114d4b5bb0d197c2b98f1cc4>:0)
    PlayerMovement_FPS.SetVelocityClientRpc (UnityEngine.Vector3 Velocity, Unity.Netcode.ClientRpcParams clientRpcParams) (at Assets/Project/Runtime/Main/Scripts/PlayerMovement_FPS.cs:153)
    PlayerMovement_FPS.SetVelocityServerRpc (UnityEngine.Vector3 Velocity, System.UInt64 ID) (at Assets/Project/Runtime/Main/Scripts/PlayerMovement_FPS.cs:145)

    it's probably something incredibly stupid and I'm tryna to figure it out but I don't have any ideas atm. any help? Thanks! :D

    edit: is there anyway to embed a stack trace on the forums to make it easier to read?
     
  8. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    As you can see from the stacktrace, the error is in your PlayerMovement_FPS script at line 153, so if you look there you'll figure out what the null reference is.

    The code tag
     
  9. GradientOGames

    GradientOGames

    Joined:
    Feb 8, 2021
    Posts:
    28
    Oops, forgot to add the line of code it was asking for,

    the line of code is
    Fields.Rigidbody.velocity = Velocity;


    and it is inside here
    Code (CSharp):
    1. [ClientRpc]
    2.     void SetVelocityClientRpc(Vector3 Velocity, ClientRpcParams clientRpcParams)
    3.     {
    4.         Debug.Log("send message to client with velocity: " + Velocity);
    5.  
    6.         Fields.Rigidbody.velocity = Velocity;
    7.     }
    the problem I'm facing is that I have no idea what is wrong with Fields.Rigidbody.velocity = Velocity; it is perfectly fine in all my use cases, perhaps it doesn't work in a clientrpc?

    Thanks :D
     
  10. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    Either Fields or its rigidbody is null on client, you can check which one is null by printing it with a Debug.Log. Then be sure to assign it somewhere in the rest of your code. It's not a netcode issue anymore :D
     
  11. GradientOGames

    GradientOGames

    Joined:
    Feb 8, 2021
    Posts:
    28
    Update: I've been working on this on my own on and off for a bit and I'm still confounded by a few weird things that I've been trying to solve but there's two parts that just don't work and I have no idea why.

    1. clients can't set their own velocity even though it works in every other script (now I write this I realise it might just be because the velocity is being overwritten by fixedupdate so I will have to check later)
    2. using ClientRpcParams prevents all clients from recieving the rpc; I take the target client's OwnerClientId which is passed to the server rpc which converts that into ClientRpcParams however it doesn't find any client which is very odd.

    (I'm gonna put the entire script here, the whole velocity stuff starts on line 141)
    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Netcode;
    4. using UnityEngine;
    5. using TMPro;
    6.  
    7. /*============================ ======================Script==================================================*/
    8.  
    9.  
    10. [RequireComponent(typeof(Rigidbody))]
    11. public class PlayerMovement_FPS : NetworkBehaviour
    12. {
    13.     [Header("Rigidbody Movement \n")]
    14.     [Tooltip("The variables that should be set manually.")] public PMFPSParameters Parameters;
    15.     [Tooltip("All variables that doesn't require manual intervention.")] public PMFPSFields Fields;
    16.  
    17.  
    18.  
    19.     //setup
    20.     public override void OnNetworkSpawn()
    21.     {
    22.         if (!IsOwner) { return; } else { GetComponent<Rigidbody>().collisionDetectionMode = CollisionDetectionMode.Continuous; }
    23.  
    24.         Camera.main.transform.SetParent(transform);
    25.         Camera.main.transform.localPosition = Parameters.CameraPosition;
    26.         Cursor.lockState = CursorLockMode.Locked;
    27.         Cursor.visible = false;
    28.         Fields.CursorLock = true;
    29.  
    30.         Destroy(transform.Find("Eye").GetComponent<MeshRenderer>());
    31.         transform.Find("Nametag").GetComponent<TextMeshPro>().fontSize = 0;
    32.         Destroy(transform.Find("nametag background").GetComponent<MeshRenderer>());
    33.  
    34.         Fields.NetworkObject = GetComponent<NetworkObject>();
    35.  
    36.         Fields.Rigidbody = GetComponent<Rigidbody>();
    37.         Fields.GroundPoint = new GameObject("GroundPoint").transform;
    38.         Fields.GroundPoint.parent = transform;
    39.         Fields.GroundPoint.localPosition = new Vector3(0, Parameters.BottomDistance, 0);
    40.  
    41.         SetGameObjectNameServerRpc(gameObject, GameObject.Find("UserName").GetComponent<TextMeshProUGUI>().text.Replace("Username: ", ""));
    42.     }
    43.  
    44.  
    45.  
    46.     //inputs and camera
    47.     private void Update()
    48.     {
    49.         if (!IsOwner) return;
    50.  
    51.         if (Input.GetKeyDown(KeyCode.Escape)) { Fields.CursorLock = false; }
    52.         else if (Input.GetMouseButtonDown(0) || Input.GetMouseButtonDown(1)) { Fields.CursorLock = true; }
    53.  
    54.         if (Fields.CursorLock)
    55.         {
    56.             Cursor.lockState = CursorLockMode.Locked;
    57.             Cursor.visible = false;
    58.  
    59.             Fields.MouseXY = new Vector2(Input.GetAxis("Mouse X") * Parameters.MouseSensitivity, Fields.MouseXY.y - Input.GetAxis("Mouse Y") * Parameters.MouseSensitivity);
    60.             Fields.MouseXY.y = Mathf.Clamp(Fields.MouseXY.y, -90f, 90f);
    61.  
    62.             Camera.main.transform.localRotation = Quaternion.Euler(Camera.main.transform.localRotation.x + Fields.MouseXY.y, 0, 0);
    63.             transform.rotation *= Quaternion.Euler(0, Fields.MouseXY.x, 0);
    64.         }
    65.         else
    66.         {
    67.             Cursor.lockState = CursorLockMode.None;
    68.             Cursor.visible = true;
    69.         }
    70.  
    71.         Fields.XYInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    72.         if (Fields.XYInput.magnitude > 1) Fields.XYInput.Normalize();
    73.         Fields.XYOutput = (Fields.XYInput * Parameters.MoveSpeed);
    74.  
    75.         Fields.JumpInput = Input.GetButton("Jump");
    76.         Fields.JumpOutput = (Convert.ToInt32(Fields.JumpInput) * Parameters.JumpHeight);    
    77.     }
    78.  
    79.  
    80.  
    81.     //physics
    82.     void FixedUpdate()
    83.     {
    84.         if (!IsOwner) return;
    85.  
    86.         Fields.OnGround_Walk = Physics.CheckSphere(Fields.GroundPoint.position, Parameters.MoveDistance, Parameters.GroundLayer);
    87.         Fields.OnGround_Jump = Physics.CheckSphere(Fields.GroundPoint.position, Parameters.JumpDistance, Parameters.GroundLayer);
    88.  
    89.         Vector3 move = transform.forward * Fields.XYOutput.y + transform.right * Fields.XYOutput.x;
    90.  
    91.         if (Fields.OnGround_Walk && Parameters.MovementType == PMFPSMovementType.realistic) Fields.Rigidbody.velocity = new Vector3(move.x, Fields.Rigidbody.velocity.y, move.z);
    92.         else if (Parameters.MovementType == PMFPSMovementType.forgiving)
    93.         {
    94.             if (Fields.OnGround_Walk)
    95.             {
    96.                 if (Mathf.Abs(Fields.Rigidbody.velocity.x) < Parameters.MoveSpeed && move.x != 0) Fields.Rigidbody.velocity = new Vector3(move.x, Fields.Rigidbody.velocity.y, Fields.Rigidbody.velocity.z);
    97.                 if (Mathf.Abs(Fields.Rigidbody.velocity.z) < Parameters.MoveSpeed && move.z != 0) Fields.Rigidbody.velocity = new Vector3(Fields.Rigidbody.velocity.x, Fields.Rigidbody.velocity.y, move.z);
    98.             }
    99.             else Fields.Rigidbody.AddForce(move.x, 0f, move.z);
    100.         }
    101.         else if (Parameters.MovementType == PMFPSMovementType.normal) Fields.Rigidbody.velocity = new Vector3(move.x, Fields.Rigidbody.velocity.y, move.z);
    102.  
    103.         if (Fields.JumpInput && Fields.OnGround_Jump) Fields.Rigidbody.velocity = new Vector3(Fields.Rigidbody.velocity.x, Fields.Rigidbody.velocity.y + Fields.JumpOutput, Fields.Rigidbody.velocity.z);
    104.     }
    105.  
    106.  
    107.  
    108.     //set a client's username
    109.     [ServerRpc]
    110.     public void SetGameObjectNameServerRpc(NetworkObjectReference Player, string Name)
    111.     {
    112.         ((GameObject)Player).name = Name;
    113.         DebugLog("Setting player name in server hierarchy");
    114.  
    115.         GameObject[] ServerNameObjects = GameObject.FindGameObjectsWithTag("Player");
    116.         string[] ServerNames = new string[ServerNameObjects.Length];
    117.         for(int i = 0; i < ServerNames.Length; i++)
    118.         {
    119.             ServerNames[i] = ServerNameObjects[i].name;
    120.         }
    121.         SyncNamesClientRpc(new NetworkStringArrayStruct { Array = ServerNames });
    122.     }
    123.     //sync all current usernames with all clients
    124.     [ClientRpc]
    125.     public void SyncNamesClientRpc(NetworkStringArrayStruct ServerNames)
    126.     {
    127.         DebugLog("Syncing Client Names");
    128.         GameObject[] NameTags = GameObject.FindGameObjectsWithTag("Nametag");
    129.         int i = 0;
    130.        
    131.         foreach (GameObject Tag in NameTags)
    132.         {          
    133.             Tag.GetComponent<PlayerNameTag>().Name = ServerNames.Array[i];
    134.             DebugLog("Sync Client Name: " + ServerNames.Array[i]);
    135.             i++;
    136.         }
    137.     }
    138.  
    139.  
    140.  
    141.     //send collision velocity to player that was collided with
    142.     void OnCollisionEnter(Collision collision)
    143.     {
    144.         if (!IsOwner) return;
    145.  
    146.         Vector3 V = Fields.Rigidbody.velocity;
    147.  
    148.         if (collision.gameObject.CompareTag("Player"))
    149.         {
    150.             DebugLog("Collided with player with velocity: " + V);
    151.  
    152.             ulong ID = collision.gameObject.GetComponent<NetworkObject>().OwnerClientId;
    153.  
    154.             SetVelocityServerRpc(V, ID);
    155.         }
    156.     }
    157.     //send velocity to server to then send to client
    158.     [ServerRpc]
    159.     void SetVelocityServerRpc(Vector3 Velocity, ulong ID)
    160.     {
    161.         DebugLog("send collision message to server");
    162.  
    163.         ClientRpcParams clientRpcParams = new ClientRpcParams
    164.         {
    165.             Send = new ClientRpcSendParams
    166.             {
    167.                 TargetClientIds = new ulong[] { ID }
    168.             }
    169.         };
    170.  
    171.         SetVelocityClientRpc(Velocity, clientRpcParams);
    172.     }
    173.     //Get velocity to set from server
    174.     [ClientRpc]
    175.     void SetVelocityClientRpc(Vector3 Velocity, ClientRpcParams clientRpcParams = default)
    176.     {
    177.         DebugLog("send message to client with velocity: " + Velocity);
    178.  
    179.         SetVelocity(Velocity);
    180.     }
    181.     //set velocity based on input
    182.     void SetVelocity(Vector3 Velocity)
    183.     {
    184.         if (!IsOwner) { DebugLog("Not owner and cannot do SetVelocity()"); return; }
    185.  
    186.         DebugLog("Velocity to send" + Velocity);
    187.         DebugLog(GetComponent<PlayerNameTag>().Name + " " + Fields.Rigidbody);
    188.  
    189.         Fields.Rigidbody.velocity += Velocity;
    190.     }
    191.  
    192.  
    193.  
    194.     //Debug
    195.     void DebugLog(object message)
    196.     {
    197.         if (!IsOwner) return;
    198.  
    199.         DebugLogServerRpc(message.ToString());
    200.     }
    201.     [ServerRpc]
    202.     void DebugLogServerRpc(string message)
    203.     {
    204.         DebugLogClientRpc(message);
    205.     }
    206.     [ClientRpc]
    207.     void DebugLogClientRpc(string message)
    208.     {
    209.         Debug.Log(message);
    210.     }
    211. }
    212.  
    213.  
    214.  
    215. //serialize player names array to use in RPC
    216. public struct NetworkStringArrayStruct : INetworkSerializable
    217. {
    218.     public string[] Array;
    219.  
    220.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    221.     {
    222.         var length = 0;
    223.         if (!serializer.IsReader)
    224.             length = Array.Length;
    225.  
    226.         serializer.SerializeValue(ref length);
    227.  
    228.         if (serializer.IsReader)
    229.             Array = new string[length];
    230.  
    231.         for (var n = 0; n < length; ++n)
    232.             serializer.SerializeValue(ref Array[n]);
    233.     }
    234. }
    235.  
    236.  
    237.  
    238. /*==================================================CustomClasses===========================================*/
    239.  
    240.  
    241.  
    242. [System.Serializable]
    243. public class PMFPSParameters
    244. {
    245.     [Header("Movement")]
    246.     [Tooltip("The speed of walking without any multipliers or adders.")] public float MoveSpeed;
    247.     [Tooltip("The distance the player has to be from the ground to walk.")] public float MoveDistance;
    248.  
    249.     [Header("Mouse")]
    250.     [Tooltip("The sensitivity of the FPS mouse movement.")] public float MouseSensitivity;
    251.     [Tooltip("Offset of the camera from the player.")] public Vector3 CameraPosition;
    252.  
    253.     [Header("Jumping")]
    254.     [Tooltip("The height of jumping without any multipliers or adders.")] public float JumpHeight;
    255.     [Tooltip("The distance the player has to be from the ground to jump.")] public float JumpDistance;  
    256.  
    257.     [Header("Types")]
    258.     [Tooltip("The type of movement that will be used.")] public PMFPSMovementType MovementType;
    259.  
    260.     [Header("Miscellaneous")]
    261.     [Tooltip("The lowest point of the player from the centre.")] public float BottomDistance;
    262.     [Tooltip("The layer in which the player can jump on.")] public LayerMask GroundLayer;
    263.  
    264.     [Header("Debug")]
    265.     [ReadOnly] [Tooltip("Broadcast all debug.log messages")] public bool DebugMode;
    266. }
    267.  
    268. public enum PMFPSMovementType
    269. {
    270.     realistic, //player has ZERO movement control in midair.
    271.     forgiving, //player has WEAK movement control in midair.
    272.     normal     //player has FULL movement control in midair.
    273. }
    274.  
    275. [System.Serializable]
    276. public class PMFPSFields
    277. {
    278.     [Header("Networking")]
    279.     [ReadOnly] [Tooltip("The network object of the player")] public NetworkObject NetworkObject;
    280.    
    281.     [Header("Objects")]
    282.     [ReadOnly] [Tooltip("The Rigidbody used for the player to interact with physics.")] public Rigidbody Rigidbody;
    283.     [ReadOnly] [Tooltip("Should be touching the ground when the player is on the ground.")] public Transform GroundPoint;
    284.  
    285.     [Header("Inputs")]
    286.     [ReadOnly] [Tooltip("The horizontal and vertical input.")] public Vector2 XYInput;
    287.     [ReadOnly] [Tooltip("The jump input.")] public bool JumpInput;
    288.  
    289.     [Header("Camera")]
    290.     [ReadOnly] [Tooltip("The X and Y movement of the mouse.")] public Vector2 MouseXY;
    291.     [ReadOnly] [Tooltip("If mouse is being used in game.")] public bool CursorLock;
    292.  
    293.     [Header("Movement")]
    294.     [ReadOnly] [Tooltip("The horizontal and vertical movement final values.")] public Vector2 XYOutput;
    295.     [ReadOnly] [Tooltip("The jump final output.")] public float JumpOutput;
    296.     [ReadOnly] [Tooltip("If the player is in jump mode.")] public bool JumpCooldown;
    297.  
    298.     [Header("Conditions")]
    299.     [ReadOnly] [Tooltip("Is the player on the ground or not? (to be able to walk)")] public bool OnGround_Walk;
    300.     [ReadOnly] [Tooltip("Is the player on the ground or not? (to be able to jump)")] public bool OnGround_Jump;
    301. }
    Thanks for reading, and hopefully helping ;)