Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Failing to control other client objects

Discussion in 'Multiplayer' started by chanfort, Dec 14, 2015.

  1. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    I am currently working on the situation, where I have a host (server+client) and a client. Let's say the client in a host is Client 1 and another client is Client 2. As I am working on RTS game, each of the clients can create multiple objects (units). Problems arise when I do hit damage from one client into another. I spawn each object to be with client authority. So when I am feeding damage from Client 2 into Client 1 unit it works fine, as there is a server together with Client 1. Now if Client 1 is damaging Client 2 unit, it doesn't work so well. Firstly I see from Client 2 that at the point when damage is taken, it shows for a small fraction of second the actual health with damage taken, but very quickly it restores to the original value, which was on Client 2.

    I suspect that health on Client 2 units can be updated only from calls made from Client 2, but I couldn't find out how it would be possible to trigger from Client 1 that Client 2 would update its unit health to make damage to be taken. Is there a way to go around this?

    P.S. I am using [syncVar] 's for health, which are attached onto each unit object (units themselves are not the player, but they are spawned with client authority by calling command from player script).
     
  2. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    From what I understand, the best way to use [syncVar] is if the server holds the variable. Change the variable on the server, and the server spreads the news.

    Which would lead me to believe that Client2's units are being syncVar-ed back from the Client1's data. If Client2 damages a unit from Client1, well... Client1 is already the server so the syncVar updates to Client2.
     
  3. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Is there a way to check to which client [syncVar] belongs or do I miss something here?

    P.S. [syncVar] is always being changed inside the command
     
  4. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    I'm not sure if the server (receiving the command) can update a variable it doesn't have authority on. I'm not too familiar with [syncVar] outside of keeping the variables completely under the server's control.

    For an RTS, what I'd be more comfortable doing is to keep all units on the server, store an extra playerId syncVar (use THAT to check if the player "owns" it - you could even do cool tricks like give unit controls to other players by just changing that value) and have the clients use RPC calls to command to the server to move the units by just sending Vectors, orders, and the playerId.

    Server just has to check if playerId matches the GameObject's playerId, take the orders and execute them on the Vectors.

    All combat computations would be done on the server and the syncVar would update on all clients appropriately.
     
  5. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    That what I was trying to do in the first approach - to put all units on server and from clients to send commands. However, there were problems when I was dealing with movement that non-player objects has to have client authority in order to be controlled. So I ender up by rewriting everything with "Client Authority for Non-Player Objects" (explained at the bottom of page http://docs.unity3d.com/Manual/UNetConcepts.html). In that case I got instantiated all objects through command by using SpawnWithClientAuthority(). And it worked quite well - on both clients objects are visible to be moving. However, it was the opposite approach to what I am trying to do now - object movement is controlled by the player to which object belongs, while health damage is always being applied from another client to which object does not belong.

    By the way, isn't there possible to create [syncVar], which could be changed from any clients - it would probably solve all problems with that...
     
    Last edited: Dec 14, 2015
  6. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    Then I suggest sending Client1's damageNumber in an [Command] with Client2's NetworkIdentity to the server, have it compute the damage, [Rpc] to all Clients so that they get the memo that NetworkIdentity-GameObject's health changed.

    At this point, don't sync the var, just update it on use.
     
  7. ObliviousHarmony

    ObliviousHarmony

    Joined:
    Jul 3, 2014
    Posts:
    79
    While a [SyncVar] itself cannot be changed on the client, you can create a [Command] on the client. This method attribute causes a method to be called on the server, if the caller is the client and has authority over the object. In this way, you can have a method that decreases the SyncVar on the server, which is called from the controlling object.

    In the specific case of an attack, you can have something like:

    Code (csharp):
    1.  
    2.  
    3. [Client]
    4. public void AttackOtherUnit(GameObject object)
    5. {
    6.     // have us attack the other unit on the server!
    7.     NetworkIdentity ident = object.GetComponent<NetworkIdentity>();
    8.     CmdAttackUnit(ident);
    9. }
    10.  
    11. [Command]
    12. public void CmdAttackUnit(NetworkIdentity otherUnit)
    13. {
    14.     // this method is called on the server, so long as we call the method from the proper client
    15.     // if called by a client without authority, it will simply return immediately and do nothing!
    16.     UnitComponent comp = otherUnit.GetComponent<UnitComponent>();
    17.     comp.syncVarForHealth -= 10;
    18. }
    19.  
    Something like this would be called on the unit the player is attacking with (the one they control), with the target object as the param. The end result is a message sent to the server that called "CmdAttackUnit" on it, and decreases the health SyncVar. This will be automatically updated on all clients then, as per standard SyncVar behaviour.
     
    GixG17 likes this.
  8. GixG17

    GixG17

    Joined:
    Aug 18, 2013
    Posts:
    105
    Thanks for clearing that up, ObliviousHarmony.
     
  9. ObliviousHarmony

    ObliviousHarmony

    Joined:
    Jul 3, 2014
    Posts:
    79
    I'll clarify that I don't necessarily know if you can either! I don't really use the [Command] [ClientRpc] attributes, and instead make use of the messaging system. I know that the messaging system allows for sending network identities, so I just made an assumption here :D
     
  10. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    Oh, I didn't knew you could pass NetworkIdentity as a command argument. Thanks, I will try this out!
     
  11. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    I was very curious about using messages in UNET, however, I didn't found any good examples about them - do you know any examples, tutorials, etc on how messaging works?
     
  12. ObliviousHarmony

    ObliviousHarmony

    Joined:
    Jul 3, 2014
    Posts:
    79
    According to a little searching, you can indeed send a Networkidentity. You can also send a GameObject if it has a NetworkIdentity component attached!
     
  13. ObliviousHarmony

    ObliviousHarmony

    Joined:
    Jul 3, 2014
    Posts:
    79
    The documentation has a relatively good demonstration of how to use the system: http://docs.unity3d.com/Manual/UNetMessages.html

    Essentially, the idea is that you register a callback on the NetworkServer or NetworkClient, which is specifically designated to handle messages of a given type (using the short). When you call ".Send()" on the NetworkClient or NetworkServer, whatever relevant recipient will have that method called, passing in the message. You then parse it in that method and handle it accordingly :D
     
  14. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I continued to look through this page by using 3rd example there. My full code is as follows:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.Networking;
    4.  
    5. public class DemoNetwork : MonoBehaviour {
    6.  
    7.     NetworkManager netManager;
    8.    
    9.     NetworkClient myClient;
    10.  
    11.     public class MyMsgType {
    12.         public static short Score = MsgType.Highest + 1;
    13.     };
    14.    
    15.     public class ScoreMessage : MessageBase
    16.     {
    17.         public int score;
    18.         public Vector3 scorePos;
    19.         public int lives;
    20.     }
    21.    
    22.     public void SendScore(int score, Vector3 scorePos, int lives)
    23.     {
    24.         ScoreMessage msg = new ScoreMessage();
    25.         msg.score = score;
    26.         msg.scorePos = scorePos;
    27.         msg.lives = lives;
    28.  
    29.         NetworkServer.SendToAll(MyMsgType.Score, msg);
    30.     }
    31.    
    32.    
    33.     public void OnScore(NetworkMessage netMsg)
    34.     {
    35.         ScoreMessage msg = netMsg.ReadMessage<ScoreMessage>();
    36.         Debug.Log("OnScoreMessage " + msg.score);
    37.     }
    38.  
    39.     public void OnConnected(NetworkMessage netMsg)
    40.     {
    41.         Debug.Log("Connected to server");
    42.     }
    43.    
    44.    
    45.     void Start () {
    46.         NetworkManager[] allObjects = Object.FindObjectsOfType<NetworkManager>();
    47.         netManager = allObjects[0];
    48.     }
    49.    
    50.    
    51.    
    52.     void Update(){
    53.        
    54.         if(Input.GetKeyDown("z")){
    55.             myClient = netManager.StartClient();
    56.             myClient.RegisterHandler(MyMsgType.Score, OnScore);
    57.         }
    58.         if(Input.GetKeyDown("x")){
    59.             myClient = netManager.StartHost();
    60.             myClient.RegisterHandler(MyMsgType.Score, OnScore);
    61.         }
    62.        
    63.         if(Input.GetKeyDown("a")){
    64.             SendScore(15, Vector3.zero, 12);
    65.         }
    66.     }
    67.    
    68. }
    My idea was to start with no client or server connected. The first player can press "x" to start Host. Host should be started from NetworkManager, which is attached to one of scenes game object. Next player could press "a" and the number "15" should appear in every other player debug console box. However, the problem is that nothing appears. In fact, there is something wrong with StartHost() and StartClient() functions as it looks like that it doesn't connect to network (p.s. there are also no errors thrown from it). Do I miss something here?
     
  15. ObliviousHarmony

    ObliviousHarmony

    Joined:
    Jul 3, 2014
    Posts:
    79
    Hello,

    After taking a look at your code, I think the issue is that you're not registering a server handler. According to the source for NetworkManager (https://bitbucket.org/Unity-Technologies/networking), it seems that the NetworkClient "StartHost" returns is simply the "local client" You would want to also do a "NetworkServer.RegisterHandler" for it as well.
     
  16. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    So I added NetworkServer.RegisterHandler in Start() function that it looks as the following:
    Code (CSharp):
    1.     void Start () {
    2.         NetworkManager[] allObjects = Object.FindObjectsOfType<NetworkManager>();
    3.         netManager = allObjects[0];
    4.      
    5.         NetworkServer.RegisterHandler(MsgType.Connect, OnConnected);
    6.         NetworkServer.RegisterHandler(MyMsgType.Score, OnScore);
    7.     }
    8.  
    9.     void Update(){
    10.      
    11.         if(Input.GetKeyDown("z")){
    12.             myClient = netManager.StartClient();
    13.             myClient.RegisterHandler(MyMsgType.Score, OnScore);
    14.         }
    15.         if(Input.GetKeyDown("x")){
    16.             myClient = netManager.StartHost();
    17.             myClient.RegisterHandler(MyMsgType.Score, OnScore);          
    18.         }
    19.      
    20.         if(Input.GetKeyDown("a")){
    21.             SendScore(15, Vector3.zero, 12);
    22.         }
    23.     }
    24.  
    However, the situation didn't changed. I also tried to put NetworkServer.RegisterHandler after StartHost(), but it's the same. I thought that StartHost() itself might be not working, but it appears that myClient is not null anymore after StartHost() is being called.

    Another thing is that I registered OnConnected function as well, which should print out its own text when first connection is being established. But there is still a complete silence - no errors, no Debug.Log() printouts. I suspect that OnScore() and OnConnected() functions are not being triggered or I might be missing something here...
     
  17. ObliviousHarmony

    ObliviousHarmony

    Joined:
    Jul 3, 2014
    Posts:
    79
    Hello,

    The last thing I can think of would be that it isn't connecting at all. Perhaps the configuration of your NetworkManager is incorrect? Personally, I wrote my own NetworkManager class, so I'm not entirely sure how to use Unity's component.
     
  18. chanfort

    chanfort

    Joined:
    Dec 15, 2013
    Posts:
    641
    That's getting interesting. So, do you know if there exist any tutorials / pages which would explain very simple creation of something like NetworkManager? I noticed that in this example about messages it was also shown how to connect client through IP address, but I can't find examples on how to start a server or a host (server+client on the same computer)...
     
  19. ObliviousHarmony

    ObliviousHarmony

    Joined:
    Jul 3, 2014
    Posts:
    79
    Hey there,

    The best suggestion I could give would be to review the NetworkManager source on BitBucket. That'll show you how to use NetworkServer and NetworkClient, which would be the heart of setting up your own manager in the HLAPI.