Search Unity

Unity Multiplayer Difference between Hosting on Built version vs Editor

Discussion in 'Connected Games' started by deLord, Feb 7, 2017.

  1. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    Hello

    I have a strange issue. I used the Multiplayer Lobby to start a game. If I host my game from the Editor, I will not get any error on the Editor version but on the built version.
    But if I host the game from the built version I will get this error on both editor (running) and built version (running) when entering the main scene:

    NullReferenceException: Object reference not set to an instance of an object
    PlayerGUIManager.Awake () (at Assets/scripts/PlayerGUIManager.cs:21)
    UnityEngine.Networking.NetworkIdentity:UNetStaticUpdate()


    The code at that line would be in the Awake() function of the NetworkBehavior PlayerGUIManager
    Code (CSharp):
    1. GameObject gameMgr = GameObject.Find("GameManager");
    2. if(gameMgr == null) Debug.LogError("gameMgr in PlayerGUIManager#Awake() is null");
    3. mihMgr = gameMgr.GetComponent<MeepsInHandManager>();
    The GameManager Awake() function is definately called before this code so I can't understand how the GameManager (NetworkBehavior) can not be found.

    The GameManager is in scene 2 and thus not present from the very beginning (lobby scene). He has a NetworkIdentity where both boxes are unticked. It's the same behavior if server-only is ticked (btw it should be ticked, right?).
    The NetworkManager is in scene 2, the LobbyManager in the lobby scene. NetworkManager won't exist anymore if scene 2 is loaded. The GameManager should exist only once on the server.

    Maybe I got something totally wrong about the Networking API. Is it wrong that my GameManager is a NetworkBehavior, server-only? If not, how do I accomplish it that every client finds this GameManager via NetworkManager.Find()?

    Can someone explain this different behavior and help make this clear? Thanks in advance
     
    Last edited: Feb 8, 2017
  2. LittleRainGames

    LittleRainGames

    Joined:
    Apr 7, 2016
    Posts:
    97
    When you run it, is the GameManager disabled in the hierarchy? I kept having that problem, I would fix it, turn my computer off. Turn it on the next day, then boom, GameManager starts disabled when it loads the scene even though it was active in the editor and no scripts disabling it.

    You can't use GameObject.Find on an object that is disabled, so that's why your problem arises.
     
    deLord likes this.
  3. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    WOW

    Indeed, the GameManager is disabled when I host the game on the built version (not when hosting from editor).
    What is that????
     
  4. leizzer

    leizzer

    Joined:
    Jun 26, 2013
    Posts:
    39
    Are you trying to find the GameManager object from the client or server?

    If the NetworkIdentity is ticked as ServerOnly and the object is part of the scene it will be only active on server side. So if you try to find it on client side you won't find it.

    If the object is Local Authority, it will appear on both sides but for some reason I cant send a Command to the server from the client.

    My 2 cents here is that you should think why you need that object that way and see if you can't find an other way to achieve a similar result. It happened to me, to think in one way without fully understanding the Unet-way. Most of the times you just need to rethink why you want to do it that way and analyze other options.
     
  5. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    If I start developing a game, I will have a class called "GameManager". This class will handle the main state of the game (whose turn it is etc).
    Now when I switch to Multiplayer, this GameManager somehow needs to be present on both machines but only the server should send the state updates to the clients.
    Both boxes are not ticked as from my understanding even the GameManager, which exists only once even on a local MP game, no player controls it yet it holds references to objects in the scene.

    To your question: I am trying to find it from both clients. Both clients need to know how to reach the GameManager.

    Having read another thread people seem to complain that even easy things like this seem extremely complicated with unet. Maybe I just think in a wrong way but this turn-based stuff should be one the very first things that are explained in the documentation. Yet, there is not much useful information about it :/
     
  6. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    Make sure you are trying to reference the game manager AFTER your client is logged in, preferably after the scene is loaded.
     
  7. leizzer

    leizzer

    Joined:
    Jun 26, 2013
    Posts:
    39
    As @angusmf said. Stop using awake unless you know that the object exist in the scene from the benginning. Use OnServerServer and things like that are meant to be used with networking.

    I'm not in my pc right now but you can solve that creating a Singleton class, I did that in my game.
     
  8. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    So the moment I implement Networking into my game I have to rewrite all the code since I cannot use Awake() anymore??

    Thanks for the help, will try it out the next days :)
     
  9. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    Awake is still useful, it just can't reference anything that hasn't been enabled yet. "Normal" objects are enabled when the scene loads like normal, but objects that have a networking component load after the server starts (if you are a server) or after you connect to the server (if you are a client.)

    Basically, just chase down the null ref errors as they occur and find a way to interact with that object later. Sometimes even Start is too soon, particularly if you are trying to communicate over the network. In that case, you have to watch for the scene loaded hook to fire.
     
    deLord likes this.
  10. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    OK having put a million debug statements everywhere after your hints I ran into the following when loading the new scene. I output the number of NetworkIdentities and the netIds.
    Now, all the NetworkIds that are 0 in MarketResourceCanvas::Awake() are disabled in my scene, which they shouldn't. What is this? Even stranger is the fact that the number starts out with 6, then gets magically reduced to 1 ô.O
    Moreover, I don't get any further log output from my class MarketResourceCanvas, which has Start(), OnStartServer() and OnStartClient() besides Awake().
    There are no additional error messages whatsoever. Just all the objects are disabled in the scene and I don't know why. May it have to do with registering something at the NetworkManager?

    PlayerGUIManager::Awake() is called TWICE from netId 0. Why/how?

    Moreover, no matter how often I call NetworkServer.SpawnObjects(), the connected client will always give me "OnObjSpawn netId: 100 has invalid asset Id" when I Spawn an object live via a [Command]
     
    Last edited: Feb 14, 2017
  11. leizzer

    leizzer

    Joined:
    Jun 26, 2013
    Posts:
    39
    I don't know your setup and script to answer all the questions that you are asking.

    For seeing disabled objects on client side it could be that it has some kind of error in scripts or they have the network authority set to server (that way the only the server sees them).

    I don't know if you are spawning the object or are part of the scene; if they are part of the scene and you think that setting the checkboxes to local authority, the server will grab the authority first, and even if you see it checked as local on client side only ONE can own the authority (in this case the server because it runs first. You can check it printing "hasAuthority").

    If you want to spawn the object you have to declare the prefab in the NetworkManager first.

    My suggestion is to create a simple project just to test the networking without thinking in your game. Nail it there and then go back to your game with what you learned there. I had to do that when I was migrating.

    This guy explains it the networking pretty good and simple (has a list dedicated to networking find it in youtube if you want)

     
    deLord likes this.
  12. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    Thanks for your help so far. I feel like getting further

    One problem seems to be the already in scene 2 existent NetworkBehaviour gameobjects. They are disabled and are never spawned, since they already exist in the scene and are not prefabs. So what to do with them? Why are they disabled? And still, it depends on whether I start from the built version or from the Editor... *sigh*
     
    Last edited: Feb 15, 2017
  13. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    This behaviour really drives me mad!
    In the documentation, there is nothing said about NetworkIdentity GOs that already exist in the scene and are NOT prefabs.... god, is it me or the documentation?
     
  14. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    In my network test project this problem does not arise. I can start from Editor and built version and none of the NetworkIdentities is disabled from the beginning.

    So what could cause objects to be disabled in general? And what 's the difference between Editor and built version in general?
     
  15. leizzer

    leizzer

    Joined:
    Jun 26, 2013
    Posts:
    39
    I can't help you any further without knowing your code. Sorry...

    But probably is your code or how you are trying to solve the things
     
  16. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    Having solved a whole lot of my problems. Had to do with the [ClientRpc] tags and the order in which objects have been searched.

    But maybe someone can explain me if NetworkTransforms make any sense in a 2D game where people will have different aspect ratios. Imho this Component doesnt make sense for a simple 2D board game, does it? I almost got mad until I finally found that this component was trying to update the position so it matches the host's but the host has a totally different resolution than the client.
    Or did I miss important components? The objects are spawned
     
  17. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    If you aren't using physics for the objects, there's no need for the network transform, thus no need to spawn using unet. You can control your spawning and position functions through MESSAGES sent from the server/host. Do not try to use RPCs because the messages will be sent indiscriminately to ALL clients. (Although if you aren't concerned about cheating, it's easier to use RPCs and just include a piece of information in the RPC that tells the clients which one of them the message is intended for.)
     
  18. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    What exactly do you mean by MESSAGES? And why shouldn't I use Rpc here?
     
  19. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    Messages:
    https://docs.unity3d.com/Manual/UNetMessages.html

    The problem with all the client syncing stuff like syncvars, synclists and ClientRPC is that the data is synced to all clients. If the data is only pertinent to one client, you have to filter it yourself in the function or hook by checking that the message was addressed to that client. A hacked client could see data that was intended for other clients by ignoring the filter. With Unet MessageBase, the data can be addressed to a specific client id.

    My pattern is this:

    Player initiates some action which calls a Command or sends a MessageBase-derived message to the server, which is what a Command does under the covers. The network id or some unique ID that maps the network ID of that client is used to look up the user on the server in the Command or MessageBase method so that the server knows A) which client is asking for something which leads to B) the server knows who to send a response to, if needed.

    Example in pseudo-code:
    Code (CSharp):
    1. Client:
    2. OnLogin() {
    3. CmdLogMeIn()
    4. }
    5.  
    6.  
    7. Server:
    8. CmdLogMeIn() {
    9.  
    10. string uid = GenerateUID(connectionToClient.connectionId) ; //where this creates an association between the conn id and a new GUID or something in a lookup dictionary
    11. SendUID(connectionToClient.connectionId, uid);
    12.  
    13. }
    14.  
    15. CmdDoSomething() {
    16.  
    17. string uid = LookUpConnId(connectionToClient.connectionId); //where LookUpConnID gets the uid based on the conn id
    18. string result = DoSomeStuff(uid); //a method that returns a result that pertains to this user
    19. SendDoSomethingResponse(result, connectionToClient.connectionId); //where we call a function that returns the result to the user
    20.  
    21. }
    22.  
    23.  
    24. MessageBase-derived Login class:
    25.  
    26. ...
    27. SendUID(int connection_id, string uid) {
    28.  
    29. NetworkServer.SendToClient(connection_id, CreateLoginMsg(uid), LoginMsgType);
    30.  
    31. }
    32.  
    33. LoginMsg CreateLoginMsg(string uid){
    34.  
    35. LoginMsg msg = new LoginMsg();
    36. msg.uid = uid;
    37. return msg;
    38.  
    39. }
    40.  
    41. ...
    42.  
    43. MessageBase-derived DoSomething class:
    44.  
    45. ...
    46.  
    47. SendDoSomethingResponse(string result, int connection_id){
    48.  
    49. NetworkServer.SendToClient(connection_id, CreateDoSomethingResponseMsg(result), DoSomethintResponseMsgType);
    50.  
    51. }
    52.  
    53. ...
    54.  
    And so on. While looking up SendToClient, I noticed there is also a method named SendToClientOfPlayer which saves you the trouble of creating your own lookup dictionary as long as you have the correct player object to give it.

    My apologies if the code is not helpful or leads anyone down the wrong path. This is just the general solution I came up with since there is no "private" ClientRPC.
     
    Last edited: Mar 2, 2017
  20. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    Hmm I dont understand the difference between a message and an RPC :( I don't the need functionality to communicate with a certain player.

    My clients cannot call the [Command] on the Manager because they dont have authority. But for other objects (Tiles) they can call [Command]s on the Manager.
    I think my problem is I just want to send a message to the REAL server (who started the first RPC) but I will only target the local GameManagers. Thing is I need to wait for all clients to complete their RPCs before the server can continue.
     
  21. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    That is the only real difference between a message and ClientRPC. They can be sent to specific users.

    I don't understand your error. You are calling a command, but it is executing on the local client without a server instance? Should not be possible unless it is a host. The host is your "REAL server" (and there should only be one!) and all commands will only execute there, so that's the way to talk back to the server. If you are having trouble executing your commands because the object doesn't have authority, the thing I find easiest is just to have one player object that has authority and use it for all my network traffic. If ObjectA and ObjectB both need to make something happen on the server, I just have them call methods on the player object.

    As I mentioned before, calling things in Awake can cause problems. The actual reason for this is what you were asking about in the first place. What is different between the editor and standalone? It has to do with when objects become active. In the editor, objects with a NetworkIdentity, aka ojbects derived from NetworkBehaviour instead of MonoBehaviour, behave pretty much like "normal" GameObjects in that they are active as soon as the scene is loaded. In standalone, the networked objects are not active until the server is started (if you are the server) or until you connect to a server (if you are a client.)

    Another issue is that ClientRPCs and probably other network functionality will not reliably work until the scene is completely loaded. One thing I have done is have a count or dictionary of clients on the server and don't consider the game started until all clients have reported back that their scene is loaded. You do this by calling a Command from NetworkManager.OnClientSceneChanged that tells the server "Hey, I'm ready!" so it can cross you off the list of uninitialized clients. By the time that fires, you should be able to communicate.
     
  22. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    OK let me try to explain as easy as possible what I am trying to do.

    There is a board made of several tiles that players can click on. Tiles (abstract) are NetworkBehaviours and each derivate is registered as a prefab in the lobby scene LobbyManager. Their state is correctly synched between client/host and client.
    But whenever the client clicks on a tile, even if I use your method of delegating it to the Player, I get "Trying to send command for object without authority."
    I'm not even considering checking the GameManager for the activePlayer yet

    Tile code
    Code (CSharp):
    1. public void onClick(){
    2.     gameMgr.activePlr.makeCmdOnTileClick(GetComponent<NetworkIdentity>());
    3. // or the next line
    4.     gameMgr.CmdOnTileClick(GetComponent<NetworkIdentity>());
    5. }
     
    Last edited: Mar 4, 2017
  23. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    Were your tiles spawned with authority? By default, only your player object will have authority. It's not possible (or sensible) for all players to have authority over all objects.

    It's still not clear what you are trying to do. Why are you sending the NetworkIdentity to your Command method? That won't work. You can only send "basic types" What is a click on a tile actually supposed to accomplish? If you want the "id" of the tile, you should assign one yourself, such as an int or string and send that via your command.
     
  24. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    The tiles are spawned without authority. But every player should be able to click on them (if the GameManager allows it) and then this should invoke a Command on the server.
    In addition to basic types, you can also send NetworkIdentity, that's possible.

    The click for example removes meeples on that tile and makes the current player have those in his hand. He can then put those meeples onto other tiles on the board. At any time, the board needs to be synched between all clients.
     
  25. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    Ok, I'm still confused, but at least I'm starting to understand your game, I think. Turn-based game with non-moving tiles that can contain meeples or not (gonna call that meeple-state.)

    When you said earlier that the tile state is correctly synchronized between client and server, what state are you talking about? Position, meeple-state, or other? If the meeple-state code is working correctly but you're getting the authority warning, we can figure that out separately.

    If your meeple-state code doesn't work, this is generally what I think you need to do. (Please let me know how far off we are.)


    For the "current player":

    1. In OnClick, call method on player object that sends the ID of the tile (which you are doing)
    2. Player method calls command on server/host with tile ID as param (Does your command execute on the server??)
    3. Server/host looks up the tile by ID and the player by their network connection (you will need to keep a mapping between your player and their connectionToClient.connectionId so you know who sent you the command)
    4. you make the state change on the server/host and send that new state back to the clients using syncvars, clientRPCs, whatever.

    If they are only allowed one click, then you would advance "current player" to the next player when that is complete.
     
  26. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    257
    First of all, thank you very much for your help, really appreciate it :)

    The meeples on the tile are synchronized because each time something changes the meeples (themselves NetworkBehaviours since they are spawned) every player needs to know that.

    What you described is exactly what is not working either (the second line of my code in Tile). It tries to delegate the function call to the activePlayer (Player class) which then tries to call the command with the "calling" tile on GameManager. The result is the same! That's why I am so confused :D

    The TileManager spawns the Tiles and the Meeples before the game starts. When the host/client makes any actions, every bit of it is synchronized with the client. Position will never change. Only the meeples on it (and some other game-related stuff).

    GameManager
    Code (CSharp):
    1. /** A tile was clicked, statemachine */
    2. [Command]
    3. public void CmdOnTileClick(NetworkIdentity tileNetId){
    4.     onTileClick(tileNetId);
    5. }
    6.  
    7. public void onTileClick(NetworkIdentity tileNetId){
    8.     Tile tile = tileNetId.GetComponent<Tile>(); // this is why I am passing the NetworkIdentity!
    9.     if(gameState.Equals(GameState.AWAITING_PICKUP)){
    10.         onTileClick_justPickedUp(tile);
    11.     } else if(gameState.Equals(GameState.PICKED_UP)){
    12.         onTileClick_PickedUp(tile);
    13.     } else if(gameState.Equals(GameState.DROPPED)){
    14.         onTileClick_Dropped(tile);
    15.     }
    16. }
    TileManager, only on server
    Code (CSharp):
    1. void createServerTile(int x, int y){
    2.     Tile newTile = tiles[x,y];
    3.     if(...){
    4.         newTile = (CastleTile)Instantiate(castleTilePrefab);
    5.     } else {
    6.         newTile = (PalmTile)Instantiate(palmTilePrefab);
    7.     }
    8.  
    9.     NetworkServer.Spawn(newTile.gameObject);
    10.  
    11.     newTile.RpcInitTile(Random.Range(4,15),x,y);
    12.  
    13.     // Spawn Meeples
    14.     List<int> meepcode = meepTypes[x,y];   // e.g. 422 or 513
    15.     List<Meeple> meepList = new List<Meeple>();
    16.     meepList.Add(mgr.getMeepFromSack(codedInt2meepType(meepcode[0])));
    17.     if(meepcode.Count > 1) meepList.Add(mgr.getMeepFromSack(codedInt2meepType(meepcode[1])));
    18.     if(meepcode.Count > 2) meepList.Add(mgr.getMeepFromSack(codedInt2meepType(meepcode[2])));
    19.     foreach(Meeple meep in meepList){
    20.         NetworkServer.Spawn(meep.gameObject);
    21.         newTile.RpcPutMeeple(meep.GetComponent<NetworkIdentity>());
    22.         RpcSetMeepParent(meep.GetComponent<NetworkIdentity>(), newTile.GetComponent<NetworkIdentity>());
    23.     }
    24.     tiles[x,y] = newTile;
    25. }
    Tile (my different tile prefabs have LocalAuthority set)
    Code (CSharp):
    1.  
    2. public List<Meeple> meeples = new List<Meeple>(); // synched with RPCs everytime something changes this list
    3. ...
    4. [ClientRpc]
    5. public void RpcInitTile(int camelValue, int x, int y){
    6.     this.camelValue = camelValue;
    7.     camelValueText.text = camelValue + "";
    8.     posX = x;
    9.     posY = y;
    10.  
    11.     transform.localScale = Vector3.one;
    12.     transform.localPosition = new Vector2( 0,  0 );
    13.     transform.position = new Vector2( x*100,  y*100 );
    14.     Transform trns = NetworkManager.FindObjectOfType<TileManager>().transform;
    15.     transform.SetParent(trns);
    16. }
    Player
    Code (CSharp):
    1. public void makeCmdOnTileClick(NetworkIdentity tileNetId){
    2.     NetworkManager.FindObjectOfType<GameManager>().CmdOnTileClick(tileNetId);
    3. }
    Even in my NetworkTest project this behaviour does not work. If I host in the built version, join from Editor, then set localAuthority while running, I will still get the same error "Trying to send command for object without authority."
     
    Last edited: Mar 5, 2017
  27. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    Have you tried sending tileNetId.netId instead of the whole NetworkIdentity?
     
  28. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    Also, how many GameManagers do you see in the editor? One for each player in the game? WIth FindObjectOfType<GameManager> you're just getting back ANY game manager. Not necessarily YOUR game manager. You need to call that command on the instance that has authority for your connection, so you need a reference to your player object. I don't remember exactly, but I believe I get my reference to my player by watching the events in NetworkManager. OnConnected or something?

    ---edit---

    Here it is:
    Code (CSharp):
    1.   if (isLocalPlayer)
    2.                 {
    3.                     SomeClass.SingletonInstance.PlayerGameObj = gameObject;
    4.                 }
    Call something like that from your player object class. The important part is (isLocalPlayer). If you want to find the object every time (which is needlessly slow) you'd need to loop through all of the objects and do that check on each one. So you probably want to store a reference. Just make sure that wherever you store the reference (my example is a singleton, but find the option that suits you) already exists. Because of what you already discovered about Awake and Start not behaving exactly the same for NetworkBehaviours, it is very possible to get null reference errors while trying to set up your references after the scene loads.
     
    Last edited: Mar 5, 2017
  29. angusmf

    angusmf

    Joined:
    Jan 19, 2015
    Posts:
    255
    I've since not been able to get this method to work and it turns out that these hooks are only called when you change scenes using a certain method in the API. I worked out another way to do this by waiting for NetworkServer.active to become true. It really only takes one frame, and there are a number of ways to get there, but this works for me currently:

    Code (CSharp):
    1.  
    2.  
    3.        //somewhere in code-land...
    4.         StartCoroutine(StartSpawningInScene());
    5.  
    6.  
    7.         IEnumerator StartSpawningInScene()
    8.         {
    9.             //despite the adorable log messages, we are waiting for the network server to be active
    10.             Debug.Log("StartSpawningInScene(): Waiting for prince/princess to rescue me..."); //this is too cute not to leave in
    11.             yield return new WaitWhile(() => !NetworkServer.active); //I wonder if this can point to a delegate
    12.             Debug.Log("StartSpawningInScene(): Finally I have been rescued! Let the spawning begin!");
    13.             GameControl.Instance.scene.Spawn();
    14.         }
     
unityunity