Search Unity

Utilizing Command Attribute

Discussion in 'Multiplayer' started by Bigbeef, Mar 2, 2017.

  1. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31
    I want to remotely control my game from a client, and have both the client (who sent the command) and the server/client execute the function called. I'm trying to use the command attribute, but I can't seem to figure out how to utilize it properly.

    I put a network manager and networkmanager Hud in my scene.

    I have a "PlayerController" object that contains a PlayerController script, which I am deriving from NetworkBehavior, and also contains a network identity checked to client authority.

    Basically I just want the function CmdRayCast(), which controls my game units, to execute on both the host/client as well as on the client, when the client clicks the screen. (hopefully moving my selected unit on both the server and on the client when it runs)


    Code (CSharp):
    1. public class PlayerCrewController : NetworkBehaviour
    2. {
    3.    void Update()
    4.     {
    5.         if (Input.GetMouseButtonDown(0))
    6.         {
    7.             Debug.Log("Pressed left click, casting ray.");
    8.             CmdCastRay();
    9.         }
    10.  
    11.     }
    12.  
    13.  
    14.     [Command]
    15.     void CmdCastRay()
    16.     {
    17.      //selects units and sets their destination
    18.     }
    19.  
    20. }

    Right now it seems like the code only executes on the host/client window, but not on the client. Am I on the right path to get this done? Am I missing a step? Any help or advice would be great.

    Thank you.
     
  2. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    I think I have the same problem. Maybe we can find out together

    mgr is my GameManager object, ofc it exists on all clients under a different instance ID.
    I want the server to wait for all clients to put a meeple on a tile. Then, remove all meeples from that very tile on each client.

    Code (CSharp):
    1. mgr.setWaitingForRPCNumber(); // e.g. 3 for 3 players
    2. RpcOnTileClick_DropAnother(); // RPC call, all clients need to have computed this before we can continue
    3. while(!mgr.allClientsFinishedRPCs()){
    4.     yield return new WaitForSeconds(0.025f); // some clients are still working off the RPC, the part that doesnt work
    5. }
    6. mgr.doMeepAction(tile.takeAllMeepsOfType()); // another RPC, only do this after all clients finished DropAnother
    The first RPC ends with
    Code (CSharp):
    1. mgr.CmdClientFinishedRPC();
    which is only
    Code (CSharp):
    1. [Command]
    2. public void CmdClientFinishedRPC(){
    3.     waitingForRPCtoComplete--; // a SyncVar int
    I think our problem is that there are different instance IDs and we expect the [Command] to reach the server.
     
  3. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31
    Well right now my CmdRayCast() function is incapable of being invoked from the client exe running the game. If I do a Host/Client, the game works as normal. If I connect to the Host/Client as a client, and in the client only exe when I click a tile, I get the warning "Trying to send command without authority".

    I feel like I'm close, but not quite understanding the flow of how Command attribute works, or some setting in the networkmanager / network identity that I'm not checking correctly.
     
  4. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,529
    The error is accurate, they don't have authority. You should be interested in who does have authority, because that is who controls the object.

    It's a little confusing to wrap your head around the way unet WANTS you to do things, but it's better to just do it that way and conform rather than try to beat it into doing what you want.

    Here's some notes I wrote down for myself
    Code (csharp):
    1.  
    2. // Notes:
    3. // [Command] is for a CLIENT telling the SERVER to run this method.
    4. // [ClientRpc] is for a SERVER telling ALL CLIENTS to run this method.
    5. // [Server] means the code can only be run on the SERVER.
    6. // [SyncVar] forces the SERVER value of the variable to ALL CLIENTS
     
    Bigbeef likes this.
  5. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31
    Thank you so much for your response! Looking at your notes (which is very handy btw, thank you for that), if I wanted a client to send a message to make a change on all clients, maybe I would mix and match attributes?

    For example, make a [Command] function, which would allow a client to tell the server to run a [ClientRpc] function which would make that RpcFunction run on all clients?

    Would this be a reasonable way to allow a client to tell the server to tell all clients to run a specific function?!
     
  6. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,529
    Yes, that's basically what I'm doing at this point. It gets tricky since due to authority issues you have to kinda passively issue commands between clients, server and vice versa. I think I have 4 methods with different unet attributes for each single function, just so it can all be handled on any client/server in the same component while respecting authority.

    If you add more clients, it's easier to test and you'll start seeing the patterns. That was the only way I figured it out.
     
    Bigbeef likes this.
  7. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    261
    The code is only executing on the host (server) because of the [Command] attribute. That's explicitly what the attribute does. If you want it to run in both places, put your logic in a function RayCast, and in CmdRayCast, call RayCast(). Then when your client clicks, call:

    RayCast(); //executes on client
    CmdRayCast(); //executes on server/host
     
    Last edited: Mar 2, 2017
  8. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    261
    BTW, you mentioned that this code is supposed to move your players. You probably don't really want to call your function on both "sides" if you are using built-in physics. Instead, your GameObjects would have a NetworkTransform. When you call CmdRayCast on the server and it (presumably) moves your player around on the server, that movement will sync to the client via NetworkTransform. Object with authority will sync to the server and then from there to the other clients (if any.)
    If you are NOT using NetworkTransform, and are letting each instance do these calculations themselves, they will probably wind up being different on the different machines, which is why you'd want to set up the correct authority for the object and only call the code once, either on the server if the object does NOT have authority, or on the client if the object does have authority.
    If you are NOT using built in physics and transform syncing, you'd actually have to make sure that RayCast gets called on ALL clients (otherwise only your local player and the copy of it on the server will move,) and you will almost certainly wind up with conflicts where the position is not the same on all clients.
     
  9. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    So would that be the right way to deal with a server waiting for all clients:
    Manager calls RPCs and waits in while(true). Clients in return call each a Command to increase the counter on the server at the end of their RPC method.
    The server waits until counter == numPlayers, then calls another RPC?

    Or this is not how it is done to wait for clients?
     
  10. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    261
    Well, I know from talking to you on your thread that you aren't using physics or don't need to. In your case, where you are manipulating the position directly with your own logic instead of using the semi-authoritative unet syncing, that is as good a way as I have come up with to tell when all clients have completed something, though you should have timeouts and retries if needed. Commands and messages are the only way to talk back to the server other than authoritative transform syncing, I believe. You have to think about cheating ahead of time, though. If you do allow the client to perform their own logic, what do you do when they intentionally or unintentionally get a different result than you? Who "wins"? At the very least you have to verify any win-type conditions against the authoritative data (on the server.)
    From the original poster's comments, and the fact they are talking about raycast led me to think it was a physics-based action that was occurring, though I could be easily wrong. In that case you basically assume that transform syncing takes all responsibility for motion and authority, though smoothness is apparently hard to achieve that way.
     
  11. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31

    I rewrote my unit move function so that it finds out what tile the player(any client) selected and then passes that game object to a [Command] function, and in turn that Command function calls a [RpcClient] function that handles moving/selecting the unit to the selection game object tile. My hope is that this will make the server tell all the clients to perform the same move. It's still not working though.

    My issue now is that when I debug it, the data member IsLocalPlayer is never false. I run the game as a Host (client and server combo), I attach the debugger to it and I even put in a simple if check at the top of the script's update, put a break point on it and it never hits.

    Here is the code I put in the script's update just to see if the server side ever comes through and runs the script:

    Code (CSharp):
    1. void Update()
    2.     {
    3.  
    4.         if (isLocalPlayer == false )
    5.         {
    6.             int checkForServer = 1; //break point here never hits
    7.         }
    8.  
    isLocalPlayer is never false. Ever

    What am I missing to make the server come in and run this? If I'm not mistaken, a [ClientRpc] function won't do much good if the server doesn't execute the function right?

    Also, I have the RpcClient function and the Command function in the same script, is there a problem with this? Do I need to break them up into different scripts and attach different things to them in the inspector or something?
     
  12. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    261
    IsLocalPlayer is true because you are calling the method on a host. It is a local player.
     
  13. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31
    Isn't the host both a server and a client at the same time? I thought that the code on the host is being executed basically twice? Once for the server and once for it's client?
     
  14. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    261
    Yes, it's a server AND a client. The client part is what causes IsLocalPlayer to return true. Generally you don't want the code to execute twice on one process, but if there is code that a client executes that causes code to execute on the server, you still need to let both pieces run on a host.

    It's good to have the client and server code separate enough that a method with server logic doesn't have to be conditioned out by checking flags to prevent it from running on a client. That method should just never be called on the client if possible. That doesn't have to mean separate classes or files, but if that helps someone organize effectively, that's what classes and files are for.
     
  15. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31
    Thanks for your reply. So the question remains, how come on the host, isLocalPlayer is never false? Shouldn't the server be present on the host?

    I notice a lot of code puts in things like

    If(!isLocalPlayer)
    {
    return;
    }

    in update so that server doesn't execute code. But when I am dubugging on a Host that variable, isLocalPlayer is never false.

    How come the server doesn't ever come into my script?
     
  16. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    261
    The server is getting to that, but you are checking is local player, which is true on a host, always, and going around it. If you want code to only run on server, use isServer.

    ---edit---
    I get it now. No, the code does not execute twice, once for server, once for client. It will only run again if you call it again.
     
    Last edited: Mar 6, 2017
  17. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31
    How do I get the server to execute a ClientRcp attributed function to make all the clients execute a function?
     
  18. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    261
    You just call it.

    Code (CSharp):
    1. Update() {
    2.  
    3.     if (isServer) Rpc_DoSomethingOnClient()
    4.     else Cmd_DoSomethingOnServer()
    5.  
    6. }
     
  19. Bigbeef

    Bigbeef

    Joined:
    Apr 6, 2013
    Posts:
    31
    Thanks for the reply. The confusion I had was that I thought that the server was tagged as simply false when it came to "isLocalPlayer". That bool is only referring to clients, and checks if the client is the one on the current machine.

    I got it working. Thanks for the help.
     
    angusmf likes this.