Search Unity

Create gamePlayer after gameScene isLoaded

Discussion in 'Multiplayer' started by Lynxxx, Feb 23, 2017.

  1. Lynxxx

    Lynxxx

    Joined:
    Sep 14, 2016
    Posts:
    1
    Dear Community

    EDIT: I know the topic is not well known and complicated. If you have any questions feel free to ask. @Unity: Please help us with UNET stuff!

    The last few weeks, I've spent hours to find a solution for the following problem:
    In my multiplayer game, there is a "Game"-object in the gameScene (among others) that contains the game logic. When I tried to reference this object (among others) to my gamePlayer-object in Start(), I ran into a NullPointerException:

    Code (csharp):
    1. void Start() {
    2.         Reference();
    3.         gameObject.GetComponent<Renderer>().material.color = playerColor;[/INDENT]
    4. }
    5.  
    6. private void Reference() {
    7.         ball = GameObject.FindGameObjectWithTag("Ball");
    8.         ballScript = ball.GetComponent<BallController>();
    9.         game = GameObject.FindGameObjectWithTag("Game");
    10.         gameScript = game.GetComponent<GameController>();
    11.         fallToDeath = GameObject.FindGameObjectWithTag("FallToDeath");
    12.         ability1UIScript = GameObject.FindGameObjectWithTag("CooldownAbility1").GetComponent<TextUI>();
    13.         ability2UIScript = GameObject.FindGameObjectWithTag("CooldownAbility2").GetComponent<TextUI>();
    14.         ability3UIScript = GameObject.FindGameObjectWithTag("CooldownAbility3").GetComponent<TextUI>();
    15.  
    16.         //Send ready command to server
    17.         if (!isLocalPlayer) {
    18.             return;
    19.         }
    20.         Debug.LogError("ClientReady");
    21.         CmdClientReady();
    22. }
    I found out that the gamePlayer-object is instantiated separately from the scene. Hence it is not guaranteed that its Start() is called after all Awake() are executed (and hence the objects instantiated), which led to the NullPointerException mentioned above.

    First I made a quick fix with a coroutine (which worked). But then I tried to fix it properly by creating the gamePlayer AFTER the scene was loaded by registering a method to the gameScene.isLoaded event:

    Code (csharp):
    1. void Awake() {
    2.         SceneManager.sceneLoaded += SceneLoaded;
    3. }
    4.  
    5. void SceneLoaded(Scene scene, LoadSceneMode mode) {
    6.         if (!isServer) {
    7.             ClientScene.AddPlayer(0);
    8.         }
    9. }
    For this to work, I had to uncheck the "Auto Create Player" check box and add the lobbyPlayer manually, which I did by changing the OnClientConnect() of the LobbyManager (I:

    Code (csharp):
    1. public override void OnClientConnect(NetworkConnection conn)
    2.         {
    3.             //base.OnClientConnect(conn);
    4.             if (SceneManager.GetActiveScene().name == base.lobbyScene) {
    5.                 ClientScene.AddPlayer(conn, 0);
    6.             }
    7.             [...]
    8.         }
    But now I had the problem that the gamePlayer STILL was created automatically. To find out why I downloaded the source code of the HLAPI and changed to NetworkManager.logLevel to Developer. With this, I found out that the gamePlayer was created in SceneLoadedForPlayer():

    Code (csharp):
    1. void SceneLoadedForPlayer(NetworkConnection conn, GameObject lobbyPlayerGameObject)
    2.         {
    3.             var lobbyPlayer = lobbyPlayerGameObject.GetComponent<NetworkLobbyPlayer>();
    4.             if (lobbyPlayer == null)
    5.             {
    6.                 // not a lobby player.. dont replace it
    7.                 return;
    8.             }
    9.  
    10.             string loadedSceneName = SceneManager.GetSceneAt(0).name;
    11.             if (LogFilter.logDebug) { Debug.Log("NetworkLobby SceneLoadedForPlayer scene:" + loadedSceneName + " " + conn); }
    12.  
    13.             if (loadedSceneName == m_LobbyScene)
    14.             {
    15.                 // cant be ready in lobby, add to ready list
    16.                 PendingPlayer pending;
    17.                 pending.conn = conn;
    18.                 pending.lobbyPlayer = lobbyPlayerGameObject;
    19.                 m_PendingPlayers.Add(pending);
    20.                 return;
    21.             }
    22.  
    23.             var controllerId = lobbyPlayerGameObject.GetComponent<NetworkIdentity>().playerControllerId;
    24.             var gamePlayer = OnLobbyServerCreateGamePlayer(conn, controllerId);
    25.             if (gamePlayer == null)
    26.             {
    27.                 // get start position from base class
    28.                 Transform startPos = GetStartPosition();
    29.                 if (startPos != null)
    30.                 {
    31.                     gamePlayer = (GameObject)Instantiate(gamePlayerPrefab, startPos.position, startPos.rotation);
    32.                 }
    33.                 else
    34.                 {
    35.                     gamePlayer = (GameObject)Instantiate(gamePlayerPrefab, Vector3.zero, Quaternion.identity);
    36.                 }
    37.             }
    38.  
    39.             if (!OnLobbyServerSceneLoadedForPlayer(lobbyPlayerGameObject, gamePlayer))
    40.             {
    41.                 return;
    42.             }
    43.  
    44.             // replace lobby player with game player
    45.             NetworkServer.ReplacePlayerForConnection(conn, gamePlayer, controllerId);
    46.         }
    With this knowledge I found out that the function above was called from OnServerSceneLoadedMessage():

    Code (csharp):
    1. void OnServerSceneLoadedMessage(NetworkMessage netMsg)
    2.         {
    3.             if (LogFilter.logDebug) { Debug.Log("NetworkLobbyManager OnSceneLoadedMessage"); }
    4.  
    5.             netMsg.ReadMessage(s_SceneLoadedMessage);
    6.  
    7.             PlayerController lobbyController;
    8.             if (!netMsg.conn.GetPlayerController((short)s_SceneLoadedMessage.value, out lobbyController))
    9.             {
    10.                 if (LogFilter.logError) { Debug.LogError("NetworkLobbyManager OnServerSceneLoadedMessage invalid playerControllerId " + s_SceneLoadedMessage.value); }
    11.                 return;
    12.             }
    13.  
    14.             SceneLoadedForPlayer(netMsg.conn, lobbyController.gameObject);
    15.         }
    Which itself was registered here:

    Code (csharp):
    1. NetworkServer.RegisterHandler(MsgType.LobbySceneLoaded, OnServerSceneLoadedMessage);
    MsgType.LobbySceneLoaded seems to be sent, when: "Internal networking system message for communicating a lobby player has loaded the game scene."


    I hope my explanation was not too confusing. Now to my questions:

    -Why is the scene not fully loaded, when "MsgType.LobbySceneLoaded" is triggered?
    -Why is the gamePlayer created ANYWAYS (even if I uncheck the "Auto Create Player" check box)?
    -Is there a way to create the gamePlayer manually?
    -Am I doing everything wrong and it's easy to reference stuff from the scene? xD

    Thank in advance for your replies,
    Simon

    PS: Another way to solve this would be to make a singleton out of the "Game"-object and do all the referencing there. But I would like to know why things are not working as they should (as much as I know).
    PPS: It would be nice if someone from Unity could give his/her opinion on this matter, as the documentation is somewhat lacking and no one seems to know detailed stuff about Networking.
     
    Last edited: Mar 4, 2017
  2. Rodrigo-Magnus

    Rodrigo-Magnus

    Joined:
    Oct 4, 2013
    Posts:
    2
    I do not understand how this topic has not yet been answered. I started working with the Lobby and am having the exact same problem. I'm trying to figure it out and I do not see anything in documentation or in the community about it.
     
  3. alejandro-unity

    alejandro-unity

    Unity Technologies

    Joined:
    Apr 17, 2015
    Posts:
    7
    Sorry for the delay, I would like to help you but I need to see the issue that you are experimenting.

    Is it feasible for you to send me a minimal reproduction project which I can run here and see the same error occurring?