Search Unity

Network players disappear from hierarchy on new scene load? Unet

Discussion in 'UNet' started by larswik, Aug 3, 2018.

  1. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    As the title mentions my problem, I found a fix but I think my fix is causing other issues on the new level. I wanted to make sure that my fix is a correct way of doing it.

    When the server/client clicks a button this code is called and successfully loads the new scene.
    Code (CSharp):
    1.  
    2. if(NetworkServer.active){
    3.    GameObject.Find("LobbyManager").GetComponent<LobbyManager>().ServerChangeScene("desert");
    4. }
    This loads the new scene on both host and client. But the network players disappear, looks like they are getting destroyed when the new scene loads. To fix this I added this DontDestroyOnLoad() when the networkPlayers are instantiated on their script.
    Code (CSharp):
    1. void Start () {
    2.     DontDestroyOnLoad(this.gameObject);
    3. }
    But now on the new level I am having problems with the NetworkServer.Spawn() not spawning any object on the host or client machines? Each side has a 2D castle that each player can relocate their position. But they are not even being created.

    This is called in the Awake() method of the game controller in the new scene.
    Code (CSharp):
    1.     [Command]
    2.     public void Cmd_setUpMapNetwork(string level){
    3.         Debug.Log("The load type is: " + level);
    4.         if(level == "winter"){
    5.             GameObject casRight = Instantiate(WinterCastleRight);
    6.             GameScript.levelCastleRightScript = casRight.transform.GetChild(0).GetComponent<cannonController>(); // pass this to the GameScript Objects to use.
    7.             casRight.name = "CastleRight";
    8.  
    9.             GameObject casLeft = Instantiate(WinterCastleLeft);
    10.             GameScript.levelCastleLeftScript = casLeft.transform.GetChild(0).GetComponent<CannonLController>(); // pass this to the GameScript Objects to use.
    11.             casLeft.name = "castelLeft";
    12.  
    13.             NetworkServer.Spawn(casRight);
    14.             NetworkServer.Spawn(casLeft);
    15.         }
    16.     }
    Any help is appreciated.

    Thank you!
     
  2. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    By default, the network manager is set up to spawn the local players when the level starts up, each with local authority on their respective machines. This usually means that you get a new player prefab for each new level for each connected client.

    The way you are doing it in above example will probably not work because you are spawning both players as server objects, none with local authority (i.e. these objects are server objects, not player objects, and hence can't be controlled by the players). You should instead spawn single player objects as commands (e.g. during OnStartClient) from the connected client, since you then also have the connectionID you need to assign authority to. Think of it this way: in your example, what would happen if one player disconnects prior to execution of the command? The command would still (incorrectly) spawn left and right castle, even though there is only one player present. To which player would you assign authority of the second castle?

    Hope this helps,
    -ch
     
  3. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    Hey csoFranz, I took the weekend off to let my brain rest, this Networking has been driving me nuts.

    I see what you are saying about spawning this GO on the server and should be spawned by the client on the OnStartClient(). Those castles that they are spawning are like cheese pieces, the player can control it, but that is not the player itself.

    What I don't understand is that when I start the game from the LobbyManager, it launches my map room scene, which they pick the game level to play on. In the Hierarchy I can see the 2 network players created by the LM, but once the host selects the level to play and launches the next scene both of the network players get destroyed and new ones are not created for the next level automatically? So I can not use the OnScreenClient() if the network players get destroyed. So is the correct thing to do when a network player is created to put a DoNotDestroyOnLoad() to keep it around as you load new scenes, or to create a new network players for the new scene and let the other ones get destroyed?

    A assumed that I was not able to spawn those castle GameObjects because when I loaded a new scene, those network players in the Hierarchy got detached from the new scene, and didn't know where to spawn the castles.

    I hope what I wrote made sense. :)
     
  4. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    Lars,

    I currently don't have access to my dev machine, so everything I write is from Memory. I'll try and confirm when I get back.

    IIRC, the Lobby Manager allocates Lobby Players during matchmaking - These are not correct Player objects, and they get allocated from lobbyPlayerPrefabs when Players connect to a host, and destroyed when they disconnect, or when the Network Level is loaded. The LobbyManager will be the only object that carries over from other Levels (DontDestroyOnLoad). This may become a problem later when you re-renter your lobby, but let's cover one issue at a time :) .
    After loading a new level, the LobbyManager (which I believe is an instance of the Network Manager) instantiates Player objects from the playerPrefabs (not Lobby prefabs), and issues them correct authority - provided that you have enabled auto-spawing players.

    So you may want to check the following:
    - you have defined playerPrefabs and filled the correct slot in the Lobby Manager
    - made sure you have the correct (use the Default) lobbyPlayer objects
    - enable auto-spawning Players from the prefabs

    This should ensure that you correctly spawn new Player objects with each Level.

    Cheers,
    -ch
     
  5. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    Thanks for sticking in there with me cosfranz as I am figuring this out.

    So what you described is basically what I have. I have the playerPrefabs connected to the LobbyManager and auto-spawning checked. When both player join the countdown happens and the LobbyManager starts the game. Both players are then sent to the Map Room Scene, which is the LobbyManager gameScene. I can see the 2 playerPrefabs in the hierarchy that were auto spawned.

    But after this point lies my problem. The host/client player selects a UIButton which loads the next scene photo 3, and is where the battle (game play) happens. I included a photo and you can see the Map room Scene 2 and you can see the playerPrefab is created with the red arrow, but there is only one playerPrefab right now, I didn't create a seperate build to demonstrate 2 playerPrefabs for this issue.

    The player clicks one of the buttons and it loads the next scene which is photo 3. Attached to the button is this code.
    Code (CSharp):
    1.  
    2. if(NetworkServer.active){
    3.     GameObject.Find("LobbyManager").GetComponent<LobbyManager>().ServerChangeScene("desert");
    4. }
    Now both Playersprefab get the ServerChangeScene and both switch to the next scene, photo 3 in this example. But, as you can see in the scene 3 photo, in the Hierarchy, the new scene is loaded but the playerPrefab is destroyed, as to be expected on a new scene load. But the playerPrefab or prefabs needs to stick around to play the next scene I would think?

    So if I have explained myself correctly you can see where I am stuck. I don't think the multiplayer game can continue without playerPrefabs, those are the links to the players in the real world. So would I add Don'tDestroyOnLoad to my playerPrefabs to keep them around when Scene 3 loads, so they are not destroyed? Also, would they be connected to that new loaded scene and be able to spawn objects in to it.

    So this is where my grey hair is coming from at the moment. If you can help me get passed this I would be very thankful!
     

    Attached Files:

  6. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
  7. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    Hey, that first link ServerChangeScene I have read many times, but this line was confusing in the description.
    Not ready means it is deleting the playerPrefabs, so how could I make them ready I was thinking? Also the line after says.
    I assumed the 'Clients' were the playerPrefabs, so how can I make them ready if they are deleted, again confusing? So I am coming to the understanding that the clients are not the playerPrefabs?

    The second link you sent I have not seen, and I think this is the missing link I need to move forward. So that code needs to be on a script that derives from LobbyManager, which derives from NetworkManager which should located on a GO on new scene that loads.

    Thanks cosfranz. I will see what happens over the next couple of days as I have time to work on it. Hopefully the second link you sent will push me forward on the game, it has been very frustrating and I am very thankful for your continued help!
     
  8. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    In Networking Terms, the Client is usually the entire computer (or process) that is connected to the Server (computer). Each Player Computer is either the Client or Server (let's disregards self-Hosting for the Moment).

    In Unity Networking, each PlayerObject is nothing more than a GameObject representing each Player, usually created by instancing the Player prefab (one each for each Player). So if you have three Players, each Scene on each Client (Computer) has three different copies of a Player object, one for each Player connected. The one that represents you on your machine (and only on your machine) usually has 'local authority', meaning that scripts belonging to this (and only this) copy of the Player object may issue Commands to the Server (machine).

    Each time a new Scene is allocated, all objects are deleted, with the exception of those objects that have a Special flag set. If you Change a Level with three networked Player objects, These objects all get deleted upon loading the new Level. The entire Scene gets initialized by unity, and as one step of this process, the Client's (the entire machine's) Network Status is set to not-ready.
     
  9. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    So Tuesday I drove 3.5 hours to LA to the LA Unity Meet up hoping to find a UNET knowledgeable person to help and know one there I met had much or any experience.

    So most everything you outlined, I understand. It is the very last paragraph that is where I am stuck. So the Next scene loads after calling.

    Code (CSharp):
    1. NetworkManager.singleton.ServerChangeScene("desert");
    I originally used this code which also worked, but the above seems cleaner.
    Code (CSharp):
    1. GameObject.Find("LobbyManager").GetComponent<LobbyManager>().ServerChangeScene("desert");
    Everything deletes as you said except the new scene and what is not in the DontDestroyOnLoad(). But now what is the next step to establish the connections again, that is my problem? I read the docs that you sent the links to for the ClientScene.Ready(). I thought, where do I call that? I created a new script and pretty much copied the code in the ClientScene.Ready() hoping something was going to call the OnClientConnect() when the scene loaded but nothing did.

    This function is from the ClientScene.Ready() Docs.
    Code (CSharp):
    1.  public override void OnClientConnect(NetworkConnection connection)
    2.     {
    3.         ClientScene.Ready(connection);
    4.         ClientScene.AddPlayer(0);
    5.  
    6.         m_ClientStarted = true;
    7.         //Output text to show the connection on the client side
    8.         Debug.Log("Client Side : Client " + connection.connectionId + " Connected!");
    9.  
    10.         //Register and receive the message on the Client's side (NetworkConnection.Send Example)
    11.         client.RegisterHandler(MsgType.Ready, ReadyMessage);
    12.     }
    It looks like it makes the client ready and adds the player what I need. But nothing calls that method when the new scene loads. OK, I thought, then I need to call it from the start function, but that didn't work. I wish I could find a web page that said, "you just loaded a new scene and everything is disconnected and destroyed. Here is a step by step on the order of events to connect everything again." But instead everything I read, like the docs, just say what things do. I have no idea what or where my first line of code should be to start to connect everything up again and spawn new network player prefabs? I am pretty sure my calls to get things running again are through the singleton, which is the NetworkManager. But how?

    If I can't get this solved by October 22nd I will have to try and get back to LA for the Unity Developers Conference and try and work directly with a Unity representative to get it working if anyone has time there. It really sucks and is depressing that it is months of time trying to figure it out. But I am thankful for your constant help cosfranz!
     
  10. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    Looks like we are homing in on the real issue.

    A note before we continue:

    GameObject.Find("LobbyManager").GetComponent<LobbyManager>().ServerChangeScene("desert");

    may work for you, but it is very, very dangerous code, as it can fail for no less that three four different reasons. Since your code is currently already unstable (or not doing what you want it to), I recommend you do the additional effort of coding safely:

    GameObject myLobbyManager = GameObject.Find("LobbyManager");
    if (myLobbyManager != null) {
    // get LM component
    ...
    } else { Debug.Log("Can't find 'LobbyManager' object."); }

    (getting off soap box)

    Ok, now with that important message of the day out of the way, let's get to the one thing you did not realize you had all along, and that probably is causing you so much head-scratching:

    > It looks like it makes the client ready and adds the player what I need.
    > But nothing calls that method when the new scene loads.

    Your NetworkManager will. The object that contains that component should be made Don'tDestroyOnLoad, and you should override OnClientConnect there (if you read the docs, you'll note that the very first attribute is 'Don't Destroy On Load'). If you are using the LobbyManager, that class inherits from NetworkManager, and should be used. It's disingenious, but the lobby manager should persist though all levels from start to finish (until the network session is complete)
     
    Last edited: Aug 18, 2018
  11. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    Hey Csofranz, I finally had some time the other day to look it over and there is a part that confuses me. This piece of code.
    Code (CSharp):
    1. public override void OnClientConnect(NetworkConnection connection)
    I understand the ‘override’, I have never used an override before but I know that instead of referencing the original method, it is being told to be overridden with this version of it. But OnClientConnect() is a member of the NetworkManager class. So in order to use it, without getting an error, my script class needs to derive from NetworkManager, or LobbyManager since it inherits from NetworkManager.

    Code (CSharp):
    1. public class multiplayerNetwork : NetworkManager
    When I do this, in Unity it automatically creates a NetworkManager set up in the Components of the GameObject, like in the LobbyManager that I set up originally. So even if I ignore that component and don’t link anything to it, when I build the game I would have created another instance of the NetworkManager, instead of referencing my original instance of the LobbyManager that is running the game.

    I do not know how to ‘override’ the original OnClientConnect() member of the LobbyManager instance that is controlling the game? I tried to make the script inherit from NetworkBehaviour and then use something like

    Code (CSharp):
    1. using.UnityEngine.NetworkManager;
    But I could not find anything that would work when I serched.

    So how would I 'override' the original NetworkManager's OnClientConnect() method that is running the game? As for checking to make sure the LobbyManager existed before calling it, I added that, thanks.

    I hope my explanation of my problem was clear enough, thanks again for your support.
     
  12. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    Oh, man, I envy you - for you are about to have an eye-opening experience: you'll find out that Unity is so much cooler that you already think it is :)

    Here's the deal - something you probably already know in an abstract sense, but something you haven't yet exploited: C#, the programming language in Unity, is object oriented. This means that it provides you with means that scripts can 'inherit' capabilities from other scripts that you have written before. You have used this everytime you wrote something like

    Code (CSharp):
    1. public class coolClass : MonoBehaviour
    This means that the 'coolClass' builds on top of a class called MonoBehavior, and as such instantly gains access to all methods and attributes that MB already has defined. Here is an important way to look at this: from the computer's perspective, your coolClass *is* a MonoBehavior, except with some more stuff added on. The computer guys now always come with the example of classes: say you have all mammals in this world, and now you look at the dog. The dog is a mammal, and all dogs are mammals, but not all mammals are dogs. Anyway...

    That is the way we build complex systems: by building small parts that work, and then build on those functioning blocks by extending, or inheriting from, those blocks: Most of your classes will be built upon MonoBehavior. But sometimes, you already have class (in Unity, usually also MonoBehavior based) that has 90% of what you want. Let's say, this class is your DogAI, a script that makes your dog object behave like a dog. You now want to create a Fox AI, that essentially behaves the same, with some exceptions. You could either start from scratch, and copy tons of code from DogAI, or you could take the DogAI class, base your FoxAI on it, and only 'override' some of the functions, saving you lots of work -- plus, since in game terms FoxAI is a class of DogAI, all scripts that can handle DogAI, automatically can handle the FoxAI as well (once you get more comfortable with object oriented coding you'll find out that this is the main, and most helpful advantage). Because (to repeat): in this example, since FoxAI inherits from the DogAI, FoxAI is a DogAI.

    So when you wrote

    > When I do this, in Unity it automatically creates a NetworkManager set up in the Components
    > of the GameObject, like in the LobbyManager that I set up originally

    You missed an important feature of Unity/C# -- Unity didn't add a Network Manager. The class

    Code (CSharp):
    1. public class multiplayerNetwork : NetworkManager
    *IS* a NetworkManager - you created a new version of a network manager, and by adding this component, YOU added the network manager because (again): that script IS a network manager - with all its attributes and abilities.

    Please try this illuminating example:
    Define a new class

    Code (CSharp):
    1. public class multiplayerNetwork : NetworkManager {
    2.  
    3. public bool isCool = true;
    4.  
    5. }
    Then attach it to an object in Editor. Look at the component you just added in Inspector. It seems like the NetworkManager before, because it is a network manager. But look at the last line. You should see a new checkbox attribute 'isCool' that is checked. That's because you new multiplayerNetwork component is an extended version from the Network manager: it has all the features the network manager has, all the methods the network manager has, PLUS your new isCool boolean.

    You probably see where this is heading. What you need to do is the following: look at the lobby manager that you are using. Find the script that defines it. Note the class it uses (If you are using Unity's LobbyManager asset, that would be 'LobbyManager'.

    Now create a new class

    Code (CSharp):
    1. public class MyLobbyManager : LobbyManager {
    2.  
    3. public bool soCool = true;
    4.  
    5. public override void OnClientConnect(NetworkConnection connection) {
    6.     // Do new stuff here
    7.     Debug.Log("Look, I was called!");
    8.  
    9.     base.OnClientConnect(connection); // now call my ancestor to handle the rest
    10. }
    11.  
    12. }
    And REPLACE the Lobby Manager in the prefab with your new one. Now the lobby manager has hooks for you to do anything you want, and is called when the client connects.

    So the secret was not to create a new object, but to extend an existing one. Change the LobbyManager script to a new script that inherits from LobbyManager, and add your own code, then place it in the prefab. This script will stay across all scenes, and it will have OnClientConnect called.

    Of course, you could have directly modified the Unity Asset's LobbyManager (that one too has a OnClientConnect), but then I would not have been able to show off :)
     
    Last edited: Aug 24, 2018
  13. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    OK Csofranz, I have to admit, I am a little excited here. I have a free weekend where I will try this out and this should move me forward after all these months! When I get through all of this I will have to take you out to lunch or something, if you live close. :)

    The first part on Inheriting from the 'super class', I knew already from my Java class I took in 2011 and use that concept often. But the last part on how to use the override, I did not even think of how to implement that. So my player can create dragons, or class instances (if I said that right) from the dragon class which creates the GameObject. That is where I was stuck, how can I override a method on a class instance, or object from another script. But I see where you are going with this now, thanks you.

    So you are correct. I am using the Lobby Manager asset that I downloaded from the asset store. If I understand the events, I am going to create a new class that derives from LobbyManager, myLobbyManager. So the current default LobbyManager script on the prefab has a lot going on with it (I included the photo), if I replace it I will need to relink the items for the script. So I guess I can duplicate the current LobbyManager Prefab, rename the old one for now, and swap out the LM script with my new myLM script with the override method, in my new prefab. Then I have the hook, so to speak, to execute this override method. This way I should only have to relink what the script needs but all the other Prefab GameObject components stay in tact, in the copied / duplicated version of the LM prefab.

    Thanks again SO MUCH for hanging in there as I learn this!
     

    Attached Files:

  14. larswik

    larswik

    Joined:
    Dec 20, 2012
    Posts:
    312
    Hey csofranz, I worked on it this weekend and created a myLobbyManager from the Lobby Manager, but it was not straight forward. Normally you just type the name and it comes up, not so. After some more time and research I found I had to use a namespace.
    Code (CSharp):
    1.  
    2. namespace Prototype.NetworkLobby
    3. {
    4.     public class myLobbyManager : LobbyManager {
    But after I got that part done, I duplicated the Lobby Manager prefab and renamed the old one, instead of deleting it. I reconnected everything since all the links disappeared, launched the game, and the Lobby Manager prefab that was on the screen, just disappeared and removed it's self from the hierarchy.

    So at this point I am just going to stop with uNet after all these months of frustration. It also turns out the uNet is going to be depreciated anyway 2019 version. I will focus on tightening up my game and look into Photon Bolt for peer to peer games or see what new networking system Unity has in store.

    So thank you for all your consistent support on this subject and getting me as for as you did. If we ever cross paths, I owe you a nice lunch.

    Thanks csofranz!