Search Unity

How can I set initial info for an object created with SpawnWithClientAuthority?

Discussion in 'Multiplayer' started by isidro02139, Apr 3, 2017.

  1. isidro02139

    isidro02139

    Joined:
    Jul 31, 2012
    Posts:
    72
    In my example I have a Host (Server + Local Client) <-> Remote Client setup running on my laptop's local network (a standalone build as Host, and the Unity Editor as Remote Client).

    I can successfully create two network Players on both clients using the built-in "Player prefab" slot in NetworkManager component. Each players is then responsible for calling a Command to spawns the player's units across the networking using SpawnWithClientAuthority (a script on the on Player prefab checks for local Authority and calls the Command if true). The units are spawned correctly on both clients, but...

    ..for each unit, I want to store the networkId of the Player that spawned it. How can I set this information for a new unit as soon as it is spawned?

    At first I thought assigning a SyncVar right after NetworkServer.SpawnWithClientAuthority work, but for some reason the value on the remote client is always 0 (though the Host gets the correct value). I am happy to put a “bounty” on this issue, I will PayPal anyone $20 who helps me nail this issue, promise!!

    If there is any more information I can provide that helps to describe my situation please ask and I'll do my best to edit my post to clarify as much as possible.

    Thank you to all the UNET people of the world and the greater Unity community in advance.

    Arun
     
  2. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    The simple answer: you can't. The Spawn* methods are, as you would expect, designed to be used to spawn objects over the network.

    It looks like you've already started going in the right direction, so to help you a little further:
    • use Spawn* methods to create objects over the network (you wont find functionality related to the sending of data here)
    • use SyncVars to synchronize state values or values that change fairly infrequently (probably not what you want to use here, although you could make it work with syncvars...)
    • use RPCs to send remote calls to networked objects across player clients. If the network id originates on the server, and you want to send it to your clients, this is the one you'll want to use.
    • use Commands to send a remote call to the server object from a player client object. If the network id originates on the client-side, you would use this to initially send it to the server, and then an RPC to send it to all clients.
    On a side note, if all you are wanting to do is know the network id of the connection that an object belongs to on the client and server side, every object that you spawn over the network has a NetworkIdentity component, to which there is a netId property. You could use this, if that is all that you need..

    Hopefully this helps. Good luck!
     
  3. isidro02139

    isidro02139

    Joined:
    Jul 31, 2012
    Posts:
    72
    @donnysobonny First of all, thank you so much for taking the time to respond; the community is a little richer with every one of these exchanges :D

    I think I see the error of my ways... Just calling NetworkServer.SpawnWithClientAuthority on an Authorized player, whether on the remote client or the host, does not guarantee that the unit will be ready (i.e. have had time to spawn on the non-local device) when the next line of code is executed, and so calling a Cmd or setting a SyncVar is meaningless.

    Is that right?

    Also, above you said "the network id of the connection that an object belongs to." Is there some way to get the NetworkConnection for a NetworkBehaviour and use it to let my Unit know which Player spawned him once he has been fully spawned across the network on both clients? :rolleyes:...

    I really hope this makes sense... (time for some Zzz...)

    Arun
     
  4. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    Hmm this depends more on what you're actually doing. I'd need to see more of your code to know for sure, but as a rule of thumb you should ensure that the player is ready before spawning anything for them (for example spawning with authority) because the client and server need to communicate identifiers between each other before this can happen. OnStartLocalPlayer can be used on your player NetworkBehaviour script, for the initial player object being spawned by the network (when the player connects), if you are wanting to know when the client-side is ready. If you want to know when the client is ready on the server side, before spawning objects for the client/player, then use the MsgType.Ready event by using NetworkServer.RegisterHandler.

    Hopefully this helps, otherwise if you can provide more info on what it is that you are doing I may be able to help further.

    https://docs.unity3d.com/ScriptReference/Networking.NetworkBehaviour.html

    The first two properties documented here give you access to the NetworkConnection. Depending on whether your NetworkBehaviour is an object on the server or on the client, you would use one of those.

    From there you get information about the connection of the individual peer in question, however it's important to note that unet's HLAPI treats a connection and a player as two separate entities, simply because it is possible for a single connection to have more than one "player", in rare cases. So if you want information about the connection, you can use the netId (which is a unique id for the connection) or the connectionToClient/connectionToServer properties. If you want to uniquely identify the associated player however, use the playerControllerId, also listed in the link above.

    Hopefully this helps. Let me know if you have further questions.
     
  5. isidro02139

    isidro02139

    Joined:
    Jul 31, 2012
    Posts:
    72
    @donnysobonny, thank you so much for your detailed reply!!

    Regarding the 'readiness' of a player, am I to understand that if a player is 'ready' (i.e. OnStartLocal has been called on all clients for that player) then I can SpawnWithAuthority a unit and *immediately assign* info into the unit (via setting a SyncVar or Cmd/Rpc) within the scope of my player function CmdSpawnUnit?

    My Mac broke during the latest system update, omw to the Apple Store now in face to see if they can recover it >.< Will post code asap!

    p.s. I believe that my players are 'ready' when I call CmdSpawnUnit because I wait for both the netId to be non-zero and OnStartAuthority to be called first; though certainly a custom PlayerReady message seems much cleaner! Maybe I'll start there...

    Thx man :):):) I'm so happy to have a dialogue about this! I'm so damn close to actually getting back to game logic..
     
  6. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    Yeah we'll probably need to go over some of your code to clarify this so let me know if you're still stuck with things and we can go over it further, but again as a rule of thumb, just make sure that you are using the correct OnStart* method depending on whether you are handling the client-side or server-side logic. Read carefully the description of these methods here to make sure you are using the correct one: https://docs.unity3d.com/ScriptReference/Networking.NetworkBehaviour.html. As long as you are using the correct OnStart* method, you should be able to ensure that the object is ready for networking on either the client-side or server-side. You shouldn't attempt to do anything (syncvars/rpcs/commands etc) before this.

    Just to clarify, you almost definitely don't want to use syncvars here. Although you could, it would be pretty inefficient. Refer to my initial response on a description of when and where you would use an RPC or Command, and I'd suggest using one of them for immediately sending data when the object is ready.

    Hmm... OnStartAuthority is likely to be the right one to use for "player" objects, or objects that you spawn on the server with SpawnWithAuthority, so if this isn't working for you then we'll need to look into it further. There isn't actually a MsgType.Ready for the client-side, at least not easily available to the HLAPI. You could hook into the NetworkConnection and register a MsgType.Ready handler there, however I would recommend using the OnStart* methods on the client-side and server-side for clarity, and MsgType.Ready on the server-side if you want a more generic/low-level approach.

    Are you maybe expecting the "immediately assigned" info to be sent to the player before you call the CmdSpawnUnit? If this is the case then you will need to wait for the rpc to come in from the server-side with your immediately assigned info, then call CmdSpawnUnit...

    Not sure if any of this is actually helping but yeah, let me know if you're still stuck.
     
    isidro02139 likes this.
  7. isidro02139

    isidro02139

    Joined:
    Jul 31, 2012
    Posts:
    72
    Hey man, thx for your continued support!! I really appreciate it, and I'm sorry that I have not yet been able to apply your knowledge to solve my issue...

    Here are the gists of the relevant classes – LobbyManager, Battle, Player, and Unit – but first, here is the network initialization flow that (I think) I need:

    (1) Based on a user-defined network configuration (LAN or Match), the LobbyManager transitions to the online scene once the match requirements are complete (function ServerChangeScene, LobbyManager.cs line 144)

    (2) Each of the Players (it's PvP, so only two Players max) are automatically instantiated on each client.

    (3) The Battle class yields (function Init, Battle.cs line 80) until both players are 'ready' (I assume that once they have valid i.e. non-zero networkIds they are ready).

    (4) As soon as a Player is authorized (function OnAuthorized, Player.cs line 102) and has a non-zero netId (I have verified it's possible for a Player to get Authorized before its netId is set.. :mad:), the Player calls CmdSpawnUnit. At this point there should be one authorized Player on the Host and one authorized Player on the remote client, with both of their netIds set; I believe up to this point everything is working.

    (5) Things break down here: I try to instantiate the Player's Units (just one unit for now) across the networking using NetworkServer.SpawnWithClientAuthority (function CmdSpawnUnit, Player.cs line 171) and now I hope to pass the Player's netId, which we know is non-zero, into the newly spawned Unit (function CmdSpawnUnit, line 173) so that it knows what Player it belongs to! The Units are spawned on each client, but the Player netId isn't set on the remote client...


    Looking at this code and all the IEnumerators in it, I'm sure that I have over-complicated things. Please get back to me at your convenience, @donnysobonny – I know your time is valuable. But if I get this working (and understand why!) you can be sure I will pay it back to the community by helping others in these forums as much as possible.

    Oh yes, and fyi there is a UNET Slack channel in the official Unity devs Slack team (#network) in case you or anyone else wants to join it!

    Thanks in advance,
    Arun

    EDIT:
    I re-read all of your comments @donnysobonny and it looks like the solution may be right in front of me!!? According to the NetworkBehaviour docs:
    playerControllerId: The id of the player associated with the behaviour.

    Is "playerControllerId" the same value as a player's netId? If so... why the hell did they use 'controller' and not just playerNetworkId for this property...
     
    Last edited: Apr 6, 2017
  8. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    Okay so an initial look at your Battle class, it's hard to say but yeah I think the issue is the way that you are determining the ready-ness of the two players. The netId is only one of three identifiers that I know of that unet uses to identity objects, connections and HLAPI "players" over the network, there could well be more. So realistically using that to determine whether a player is ready will only partially work.

    The previous advice that I gave in relation to using the OnStart* methods wont work for you in the setup that you have, since you need a more generalized/low-level approach. Here's what I suggest instead:

    I assume the object that the Battle class is attached to is a networked object that only exists on the server? If so, here's what I suggest to do:
    • Within your Battle class, and within OnStartServer (when the object is started on the server) access the Battle's connectionToServer property (the NetworkConnection instance representing the connection on the server) and register two handlers using NetworkConnection.RegisterHandler. Register one for the MsgType.Ready message, and another for MsgType.NotReady
    • Create an integer in the Battle class, and in the Ready message, increment. In the NotReady message, decrement. Then you can use this integer to determine the number of ready players (and also handle un-readying due to disconnects etc)
    Give this a try, and if it still doesn't work I'll take a further look.

    Definitely don't fear your enumerators and co-routines!! Unity is a single-threaded framework by default (with limited multi-threading capabilities) so enumerators and co-routines are unity's way to help you steady the pace of heavy processes, or, in your case, wait for things to happen before continuing. They are very efficient so definitely use them as much as you need to.

    Keep your money haha, you're going to need it to pay for your servers etc! I'm just here to help :)

    Not quite. The netId and playerControllerId are two separate identifiers. The netId is a unique identifier for the object over the network (every networked object has a different netId). The playerControllerId is the id of the player that the object belongs to (if there is one). The reason why these are different is because a connection over the network can have more than one "player" object, or even no "player" object (take a look at ClientScene.AddPlayer, which is what the NetworkManager calls when the client connects. You could call this multiple times if you needed to have more than one "player" object per connection etc).

    Hopefully this helps, keep us posted if you're still stuck.
     
    isidro02139 likes this.