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

I can't seem to call server functions on the server. What am I missing?

Discussion in 'Multiplayer' started by MrDude, Aug 31, 2017.

  1. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    Hi So I have a NetworkBehaviour class and a bunch of functions setup as server commands. I place all these commands inside of a Dictionary and in an external script I scan for keyboard input. When I detect KEY X I simply execute whatever is in the relevant slot in the Dictionary.

    This works perfectly fine... but then I run into a curiosity...

    Code (csharp):
    1.  
    2.     [Command]
    3.     void CmdConfirmTargets()
    4.     {
    5.         int targets = active_turn.targets.Count;
    6.         if (targets > attack.max_targets || targets < attack.min_targets)
    7.         {
    8.             if (targets == 0 && attack.min_targets > 0)
    9.                 {
    10.                     tbbData.inputs[tbbData.char_sel_state.CurrentState].Trigger(mbseButtons.D);
    11. //                  CmdSoftSelectTarget();
    12.                 }
    13.                 return;
    14.             //the rest of the function here...
    15.         }
    16.     }
    17.  
    18.     [Command]
    19.     void CmdSoftSelectTarget()
    20.     {
    21.         if (active_turn.targets.Contains(target))
    22.         {
    23.             active_turn.targets.Remove(target);
    24.         } else
    25.         {
    26.             active_turn.targets.Add(target);
    27.         }
    28.     }
    29.  
    If I press the relevant button to call either one of these two functions, both functions run just fine. If I call CmdSoftSelectTarget from within CmdConfirmTargets then I get an exception telling me that there is no network identity on the object. This is true, there is no network identity on the object as there is a NetworkManager component on the object and thus , as I understand it, the NetworkIdentity is part of that.

    That Trigge function of mine, that just does a check to see if there IS anything at the index and if so it runs it. Absolutely nothing fancy about that. So by now my question should be very clear...

    If I can call those functions and not get any warnings about missing NetworkIdentities, why then am I getting that error when I am calling the exact same function directly? As you can see, I simply commented out the direct call and once again call the function from the Dictionary and there you go... the error is gone and all is well again...

    Why?
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    NetworkManager doesn't have NetworkIdentity. In fact - it shouldn't be attached to it, because of various reasons.
    Use a different object to pass Cmd's to the server. One that has authority on it (a.k.a. player object).

    When you call Cmd's with [Command] attribute UnetWeaver interprets it as a remote command call for the server. That's why it tries to send a message to it. That's why you get no NetId error. If you want to direct call something on the server, bypassing Cmd's - do it. Just wrap what you want in a separate method, and call it instead in both Cmd and place you want.

    E.g.

    Code (CSharp):
    1. [Command]
    2. public void CmdCommand(){
    3. ...
    4. MyMethod();
    5. ...
    6. }
    7.  
    8. public void MyMethod(){ // <----- Call this one instead
    9. }
    Also, don't use Cmd's on the server. It will say you shouldn't, and you shouldn't. Since it's a remote call.
     
  3. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    Okay, you lost me... I also thought about just moving the code into a separate function but, just like my own solution to get it working, it doesn't explain why I need to.

    The project that I am making does not use an authoritative server and is a simple case of one player being the host while the others are not. I have one object which is called by both players but I want the host to ... okay well I guess I want the host to be authoritative. From what I read in the docs the Command attribute means that whichever player is calling that function, the host is the one that will execute it.

    According to the example in the docs you can call them just like any other function, except it will be run on the server, always...
    Code (csharp):
    1.  void Update()
    2.     {
    3.         if (!isLocalPlayer)
    4.         {
    5.             return;
    6.         }
    7.  
    8.         // input handling for local player only
    9.         int oldMoveX = moveX;
    10.         int oldMoveY = moveY;
    11.  
    12.         moveX = 0;
    13.         moveY = 0;
    14.  
    15.         if (Input.GetKey(KeyCode.LeftArrow))
    16.         {
    17.             moveX -= 1;
    18.         }
    19.         if (Input.GetKey(KeyCode.RightArrow))
    20.         {
    21.             moveX += 1;
    22.         }
    23.         if (Input.GetKey(KeyCode.UpArrow))
    24.         {
    25.             moveY += 1;
    26.         }
    27.         if (Input.GetKey(KeyCode.DownArrow))
    28.         {
    29.             moveY -= 1;
    30.         }
    31.         if (moveX != oldMoveX || moveY != oldMoveY)
    32.         {
    33.             CmdMove(moveX, moveY);
    34.         }
    35.     }
    36.  
    37.     [Command]
    38.     public void CmdMove(int x, int y)
    39.     {
    40.         moveX = x;
    41.         moveY = y;
    42.         isDirty = true;
    43.     }
    44.  
    So why exactly is it now that I can't do what they show in the documentation and that I need to create a workaround for doing what they show? Here is anothe example of what I mean
    Code (csharp):
    1. public class Player : NetworkBehaviour
    2. {
    3.     int moveX = 0;
    4.     int moveY = 0;
    5.  
    6.     void Update()
    7.     {
    8.         if (!isLocalPlayer)
    9.         {
    10.             return;
    11.         }
    12.  
    13.         // input handling for local player only
    14.         int oldMoveX = moveX;
    15.         int oldMoveY = moveY;
    16.  
    17.         moveX = 0;
    18.         moveY = 0;
    19.  
    20.         if (Input.GetKey(KeyCode.LeftArrow))
    21.         {
    22.             moveX -= 1;
    23.         }
    24.         if (Input.GetKey(KeyCode.RightArrow))
    25.         {
    26.             moveX += 1;
    27.         }
    28.         if (Input.GetKey(KeyCode.UpArrow))
    29.         {
    30.             moveY += 1;
    31.         }
    32.         if (Input.GetKey(KeyCode.DownArrow))
    33.         {
    34.             moveY -= 1;
    35.         }
    36.         if (moveX != oldMoveX || moveY != oldMoveY)
    37.         {
    38.             CmdMove(moveX, moveY);
    39.         }
    40.     }
    41.  
    42.     [Command]
    43.     void CmdMove(int dx, int dy)
    44.     {
    45.         // move here
    46.     }
    47. }
    Both clients can call the same command but the code is always run on the server. Looking at the samples and based on what you replied, though, it SOUNDS to me like a normal method calling a command and a command calling a command is two completely different matters and it simply should not or cannot be done... but what about the case of a client pressing the Up arrow, this calls the command on the host and in response to the up arrow being pressed the host sends out an Rpc call to the client who pressed the button... does this mean that Commands can't call Rpc functions either?

    What I want to do is very simple indeed: Both clients can press the up arrow but doing so will change a value only on the client who is acting as host / server. That works just fine. Now In the case of my original post, all I want to do is say "You pressed the up arrow but the value is foo so instead of running this function I am going to pretend you pressed the down arrow and run THAT key's associated function instead". Obviously since I am still trying to change a value on the host only and this should never be run on normal clients, I figured just calling the Cmd that is called usually is the sensible thing to do... only I'm not allowed to. Why?

    Clearly I am completely lost by this. Your solution is to create a workaround but my very question is "Why?". It sounds like you are trying to explain that but my mind is having a hard time meshing what you are saying and what I am seeing in the examples provided by Unity where they teach us by example.

    I simply don't understand why Unity is saying "Methods marked as a command will be run on the server" and why you are saying "Commands will be treated as a message to the server so you should not call the command".... I don't get it. That is exactly what I want to do, call the function on the server. Why must I NOT call the command on the server when I need to run the command on the server, but rather call it via a normal function that can be run on the client and server both, instead?

    There are two players and one of them is the server so ONE of them will always run that code on themselves. It sounds like you are saying "When the player who is the host is trying to call the command, you can't do that and shouldn't do that"... yet isn't that exactly how it is supposed to be done? What am I missing here? What is the missing piece that I am not seeing?
     
    Last edited: Aug 31, 2017
  4. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    p.s.....

    If the client presses button X and in response the Command gets called on the server where the server then runs a normal, simple, vanilla private method, this will still yield the required result of the code being run only on the server. Thus, please don't misunderstand me... I don't disagree with what you are suggesting, as I can see it will work as expected, I am only trying to make sense of the reason WHY that extra step is needed...

    Thanks :)
     
  5. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    Maybe this is a good time to ask the other thing that I was going to need to figure out seeing as this whole command and rpc thing might now become an issue...

    At the end of each turn I want to call a command on the server to check it's variables and determine whether or not the game is won or lost. Once it has determined that I want the one player to see the victory screen while the other one sees the defeat screen. How do I execute a command on the server that triggers the two players to see different screens? Since the variables are stored on the server only (not synced) I was thinking of calling an RPC that passes along a boolean indication whether or not the server is the victor... Inside that function I can then simply check to see if the current client is or isn't the server and then load one of two new scenes. Sounds simple enough... but I haven't yet figured out the actual logic in doing that and now it sounds like a Command might not be allowed to call Rpc functions so now I am REALLY screwed over...

    See? This is why I am trying to understand the process, rather than just hacking my way through. I want to understand it so I can do things right.
     
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Because you still need to execute code on the server to retranslate actions on it to the clients. In order to do so, you need to check whether the client is actually server or not. You can do that via "isServer" bool to prevent execution of Cmd's on the server.

    Because it is two separate things. Calling Cmd creates a message for a specific object with same netId on the server, and sends it for that netId. Only when received, it executes on the server.
    When you actually call Cmd inside Cmd you executing client code on the server. Meaning you're sending it over again to itself. Which you shouldn't do in any scenario. That's why you get errors. You don't need a Cmd to execute server code on the server. You just call default method.

    I haven't read the whole thing. Really, that's just too much text on the end of the day. I'll give it a go tommorrow. If you have a specific questions - feel free to ask. Otherwise, I suggest reading on manuals how Unet is actually working.
     
  7. MrDude

    MrDude

    Joined:
    Sep 21, 2006
    Posts:
    2,569
    I think you pretty much nailed it. "Don't do it or you will get errors", that is a pretty clear enough message to read and understand. :)
    Thx.