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

Sending ServerRpcParams on a button's onclick method

Discussion in 'Netcode for GameObjects' started by BurnEmDown, Oct 14, 2022.

  1. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    Hi, I'm trying to have a UI panel that is displayed to two players, when a player clicks on a "ready" button, a toggle is marked for both of them (there is one for each player). In order to know which player was the one to click on the button, I need to get the clientid from the ServerRpcParams, however when I add this parameter to the method's signature, I can't use the method as a button's onClick Listener method.

    I tried to get around this by having a "middle method", which looks like this:


    Code (CSharp):
    1.     public void OnEnable()
    2.     {
    3.         controller = BattlePopupController.Instance;
    4.        
    5.         fightButton.onClick.AddListener(ReadyClicked);
    6.  
    7.         playersCheck = 0;
    8.     }
    9.  
    10.     private void ReadyClicked()
    11.     {
    12.         PlayerSetReadyServerRpc();
    13.     }
    14.  
    15.     [ServerRpc(RequireOwnership = false)]
    16.     private void PlayerSetReadyServerRpc(ServerRpcParams serverRpcParams = default)
    17.     {
    18.         ulong clientId = serverRpcParams.Receive.SenderClientId;
    19.         if (NetworkModel.Instance.GetFactionFromClientId(clientId) == attackerFaction)
    20.         {
    21.             SetPlayerReadyToggleClientRpc(true);
    22.         }
    23.         else if (NetworkModel.Instance.GetFactionFromClientId(clientId) == defenderFaction)
    24.         {
    25.             SetPlayerReadyToggleClientRpc(false);
    26.         }
    27.         else
    28.         {
    29.             return;
    30.         }
    31.         playersCheck++;
    32.         OnPlayersCheckValueChangedReady(playersCheck);
    33.     }
    34.  
    35.     [ClientRpc]
    36.     private void SetPlayerReadyToggleClientRpc(bool attacker)
    37.     {
    38.         if(attacker)
    39.             attackerReadyToggle.isOn = true;
    40.         else
    41.             defenderReadyToggle.isOn = true;
    42.     }


    However this doesn't work either. Using the debugger I see that the ServerRpc method never gets called.
    Is there a better way to know which client clicked on button which calls a ServerRpc method?
     
  2. pradotech

    pradotech

    Joined:
    Oct 17, 2019
    Posts:
    35
    In my game I use something like this to determine which player called ServerRpc:

    Code (CSharp):
    1.     public override void OnNetworkSpawn()
    2.     {
    3.         PlayerJoinedServerRpc(NetworkManager.Singleton.LocalClientId);
    4.     }
    5.  
    6.     [ServerRpc(RequireOwnership = false)]
    7.     private void PlayerJoinedServerRpc(ulong clientId)
    8.     {
    9.         NetworkObject networkObject = Instantiate(menuPlayerNetworkPrefab).GetComponent<NetworkObject>();
    10.         networkObject.SpawnAsPlayerObject(clientId, true);
    11.  
    12.         ClientRpcParams clientRpcParams = new ClientRpcParams
    13.         {
    14.             Send = new ClientRpcSendParams
    15.             {
    16.                 TargetClientIds = new ulong[] { clientId }
    17.             }
    18.         };
    19.  
    20.         PlayerJoinedClientRpc(clientRpcParams);
    21.     }
    22.  
    23.     [ClientRpc]
    24.     private void PlayerJoinedClientRpc(ClientRpcParams clientRpcParams)
    25.     {
    26.         mainMenuPlayerNetwork = NetworkManager.Singleton.LocalClient.PlayerObject.GetComponent<MainMenuPlayerNetwork>();
    27.     }
    When player join, the client calls ServerRpc telling his local client id, which is unique. The server receives his id and does whatever it needs to do, then send a "callback" to the client that called it using ClientRpc parameters.

    Don't know if this is the best way to achieve this, but I hope this might help somehow.
     
  3. lavagoatGG

    lavagoatGG

    Joined:
    Apr 16, 2022
    Posts:
    229
    Are you sure the object is network spawned?
    You can also use a bool network variable and OnValuechanged
     
  4. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    656
    Something should be happening, do you have an EventSystem in the scene? It might be worth adding a listener in the button inspector just to check click events are firing.
     
  5. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,533
    I can see into the future that this might be an issue when someone can't make up his mind and changes between attacker and defender. ;)

    This will fix it:
    Code (CSharp):
    1.     [ClientRpc]
    2.     private void SetPlayerReadyToggleClientRpc(bool attacker)
    3.     {
    4.         if(attacker) {
    5.             attackerReadyToggle.isOn = true;
    6.             defenderReadyToggle.isOn = false;
    7. }
    8.         else {
    9.             attackerReadyToggle.isOn = false;
    10.             defenderReadyToggle.isOn = true;
    11. }
    12.     }
    With some refactoring you can make it simpler and more DRY:
    Code (CSharp):
    1.     [ClientRpc]
    2.     private void SetPlayerReadyToggleClientRpc(bool attacker)
    3.     {
    4.             attackerReadyToggle.isOn = attacker;
    5.             defenderReadyToggle.isOn = !attacker;
    6.     }
    Also, do you want all clients to change their local ready toggle state when one client changes it?
    I guess that might be another bug lurking ....

    This is what I use to "return to sender" conveniently, alloc-free by reusing the same array:
    Code (CSharp):
    1.         private static readonly ulong[] ClientId = new ulong[1];
    2.  
    3.         public static ClientRpcParams SendTo(ServerRpcParams rpcParams)
    4.         {
    5.             ClientId[0] = rpcParams.Receive.SenderClientId;
    6.             return new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = ClientId } };
    7.         }
    8.  
    9.         public static ClientRpcParams SendTo(ulong clientId)
    10.         {
    11.             ClientId[0] = clientId;
    12.             return new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = ClientId } };
    13.         }
    Used like this:
    Code (CSharp):
    1. void SomeServerRpc(ServerRpcParams rpcParams = default)
    2. {
    3.     ResponseClientRpc(SendTo(rpcParams));
    4.     ...
     
  6. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    Thanks guys, it was indeed spawning the object that was missing...

    Now I have a different issue since the object might be instantiated on clients which aren't the host, but I'll try to find a solution if needed open a different thread later since it's a different subject
     
  7. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28

    I also did something like this at first, but then I read on this page:
    https://docs-multiplayer.unity3d.co...ed-topics/message-system/serverrpc/index.html

    In the best practice section regarding RpcParams that it's best to get the clientId through that and not via sending the clientId as a ulong.
     
  8. CosmoM

    CosmoM

    Joined:
    Oct 31, 2015
    Posts:
    204
    What I do is to have the object attached to the UI not be spawned, but instead call Rpcs on a different spawned object when necessary (or call functions on other objects which then call Rpcs). That way, the UI itself does not need to be a networkobject.
     
  9. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    656
    I'll throw in what I do as well for more food for thought. I have associated UI elements on separate canvases and at the start of each scene a Gui manager scans in each canvas into its own UI manager, then the UI elements on each canvas are scanned, stored in lists in each UI manager and initialised with starting values.

    For updating the UI I have a single update controller that has a list of update services which creates a list of interface update details that the controller passes on to the Gui manager to pass on to the correct UI manager which updates the canvas elements.

    Essentially the idea is to have the UI handled in a general-purpose way, but it is rather complicated as you would expect and has a lot of limitations.
     
  10. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    So I managed to get the UI panel to properly be spawned, and the issue remains.

    When I use the debugger, both as a client and as a host, I see that the ReadyClicked() Method is being called, but it never gets into the PlayerSerReadyServerRpc().
    I first tried without changing anything in the code from what I copied here, the only changes were in other classes, and then also tried with an onClick like this, skipping the ReadyClicked() method:

    Code (CSharp):
    1. fightButton.onClick.AddListener(() =>
    2.         {
    3.             PlayerSetReadyServerRpc();
    4.         });
    Which is similar to the code I use on my buttons for joining as a client or hosting:

    Code (CSharp):
    1. startClientButton.onClick.AddListener(() =>
    2.         {
    3.             if (NetworkManager.Singleton.StartClient())
    4.             {
    5.                 Debug.Log("Client started");
    6.             }
    7.             else
    8.             {
    9.                 Debug.Log("Client could not be started");
    10.             }
    11.         });
    However, that didn't seem to work as well. The debugger never goes into the PlayerSerReadyServerRpc() method.
     
  11. lavagoatGG

    lavagoatGG

    Joined:
    Apr 16, 2022
    Posts:
    229
    And the PlayerSerReadyServerRpc() is in a script that is attached to the spawned UI or to the button that is not NetworkSpawned?
     
  12. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    656
    I'm not exactly sure where you are with this so I'll start from the beginning. I am making some assumptions of what happens when a rpc isn't working as I've not had to test it.

    Check the onClick event of the button is working, I think you've already established it is. To make it clearer for testing use a real function as a listener rather than a lambda.

    Create the server rpc with only a debug log line and call that from your listener function. If the rpc works the output will appear on the server, if it's not working it will appear on the client. If there's no output the rpc isn't being called.

    You can't really debug a working server rpc as the code is executed on the server. What you will likely see is the debugger go into the NetworkManager as it will need to send a message to the server to execute the rpc there. If the rpc isn't working the debugger should at least step through the code on the client and display the output.
     
  13. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    The script is attached to the spawned UI panel, it contains the button as a member and adds the OnClick listener in its OnEnable method.
    Does this mean the button must also be a spawned networkobject in order to call Rpcs from its OnClick?
     
  14. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    I'm running 2 unity clients, one as a host and one as a client, and have a debugger attached to one of them, I can debug both behaviors because I can switch which unity client is the host and which one is the client each time I debug.

    From what I saw now after making the ServerRpc only have a debug log line, without the
    ServerRpcParams as well in the signature, it still doesn't get called. However I also just noticed that when the client clicks on the button, I'm getting this warning in the console of the host:

    Code (CSharp):
    1. [Netcode] Deferred messages were received for a trigger of type OnSpawn with key 0, but that trigger was not received within within 1 second(s).
    2. UnityEngine.Debug:LogWarning (object)
    3. Unity.Netcode.NetworkLog:LogWarning (string) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.1/Runtime/Logging/NetworkLog.cs:28)
    4. Unity.Netcode.DeferredMessageManager:PurgeTrigger (Unity.Netcode.IDeferredMessageManager/TriggerType,ulong,Unity.Netcode.DeferredMessageManager/TriggerInfo) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.1/Runtime/Messaging/DeferredMessageManager.cs:98)
    5. Unity.Netcode.DeferredMessageManager:CleanupStaleTriggers () (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.1/Runtime/Messaging/DeferredMessageManager.cs:83)
    6. Unity.Netcode.NetworkManager:OnNetworkPostLateUpdate () (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.1/Runtime/Core/NetworkManager.cs:1588)
    7. Unity.Netcode.NetworkManager:NetworkUpdate (Unity.Netcode.NetworkUpdateStage) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.1/Runtime/Core/NetworkManager.cs:1479)
    8. Unity.Netcode.NetworkUpdateLoop:RunNetworkUpdateStage (Unity.Netcode.NetworkUpdateStage) (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.1/Runtime/Core/NetworkUpdateLoop.cs:185)
    9. Unity.Netcode.NetworkUpdateLoop/NetworkPostLateUpdate/<>c:<CreateLoopSystem>b__0_0 () (at Library/PackageCache/com.unity.netcode.gameobjects@1.0.1/Runtime/Core/NetworkUpdateLoop.cs:268)
    I tried googling the first line here but didn't find anything that could help me. Curiously there was one post on January where you were trying to help someone with this warning message as well, but it seems his issue was different than mine.
     
  15. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    656
    I'm sure I've seen that message after updating Netcode, and it had me stumped for a while. Try deleting the PackageCache folder and Reimport All on the Assets folder. It shouldn't be necessary but you could try removing and re-adding the Netcode package.
     
  16. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    I tried deleting the PackageCache folder and Reimport All on the assets folder, but the same message appears.

    I also tried to remove adding the method to the button's OnClick by script and manually add it through the unity editor, same thing happens.

    It's a shame that there's pretty much no info about this "[Netcode] Deferred messages were received for a trigger of type OnSpawn with key 0, but that trigger was not received within within 1 second(s)." warning message. I have a feeling that it's related to my issue about not triggering the ServerRpc.
     
  17. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    656
    Netcode 1.1.0 was released today, you could give that a go.

    The error suggests to me the rpc is being sent to the server but its network manager isn't able to deal with it for some reason. Without delving into the source code really only a Unity developer can tell us more. I'd recommend creating a minimal test project with just a player and try testing a rpc through that. Alternatively if you make your project available I can take a look at it.
     
  18. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    I've updated to 1.1.0 but the issue still remains.

    I unfortunately can't make the project available but I might just open a new test project with a similar setup and have it available publicly.
     
  19. BurnEmDown

    BurnEmDown

    Joined:
    Dec 29, 2019
    Posts:
    28
    So I finally got it to work... But in a really cumbersome way.
    First of all, on a test project I tried to do basically the same thing that I tried before, with a panel containing a button and an onclick method that is a ServerRpc, and got no errors. I tried to figure out what was different from my main project but couldn't find anything.

    Then on my main project, what I did was make the button its own prefab, gave it its own script, made it networked, and instantiated & spawned it right after the main panel was instantiated, then set the main panel as its parent, and viola, the onclick ServerRpc method of the new button works smoothly!

    I would have much preferred to not have the button as its own prefab tho, so I hope I could find a fix for this issue later on. For now I will have to continue working on my project with this workaround.