Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Multiplayer game, loading levels for clients

Discussion in 'Editor & General Support' started by roger0, Jul 26, 2012.

  1. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    How can I make sure players about to join a multiplayer game will end up on the same map the server is on?

    function OnConnectedToServer(){
    Application.loadLevel (//what do I put in here?)
    }
     
  2. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    I'm trying to make my question simple enough to answer easily. More specifically, how would I request something from the server. Like a string name from a level playlist the server is cycling through. Or just plain load the level the server is currently on.
     
  3. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    Does anyone know or know of a place I can find out?
     
  4. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    If it's a join-in-progress game, make the server send a buffered RPC just before loading the new level, with a new level prefix number and the name of the scene to load. Then any clients that join later on will receive that RPC. If your server cycles between levels, cancel previous RPCs before sending new ones, otherwise the clients will go through a sequence of level loads unnecessarily. There are a lot of other things you need to take care of, too... join-in-progress is the harder case. You must read this page and try to understand all of the code - even then, it doesn't cover all the angles, but it takes care of most of the non-obvious things: http://docs.unity3d.com/Documentation/Components/net-NetworkLevelLoad.html

    If it's not join-in-progress then the clients are all sitting in a lobby, and the server decides it's time to play - so it should send a non-buffered RPC to all the clients, telling them which level to load. This is much easier. Read the documentation on RPCs and it should be pretty clear how to do this.
     
  5. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    I'm trying to implement a join in progress method, there's not really any lobbies, just a server list. What I don't get is how the server is suppost to know when a player connects. There's the OnConnectedToServer() function but that only gets triggerd on the clients side I think.

    Doing a non join in progress game is not that hard to see. I have not done it yet, but I could imagine the host would have a gameObject instantiated once they create a lobby, which would have a server controller script attached to it. When the players connect to the lobby they have technically joined the server already. And once the host presses play, it sends a RPC to a function the players have to load levels.

    I read the documentation but I need a little more help then that. In the loadLevel script they use some techniques that I have not understood yet, even though I read their meanings. Such as networkView.groups, Network.peerType, Network.SetSendingEnabled, Network.isMessageQueueRunning. The only thing I know so far is how to send RPC's, a few networkView extentions and masterServer events.
     
  6. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    For the server to get notification when a new client connects, use OnPlayerConnected. However the server will already be doing a bunch of work for you at that point - in particular, any buffered RPCs will be dispatched, including creation of Network.Instantiated objects. So any data you send to clients in buffered RPCs doesn't need to be explicitly sent to new joiners, they get their own copies of those messages automatically.

    You could make a chess game, for example, and send every move as a buffered RPC. Then any spectators joining halfway through the game would receive a stream of RPCs bringing their board up to date with the players'. That's a bit over the top, but it is a convenient system which you might as well make use of.

    You can leave out the networkview group stuff at first. The important aspect there is that general object state traffic is disabled until the client has loaded the right level, otherwise the traffic will refer to networkviews that might not exist. The idea behind using groups is that your "DontDestroyOnLoad" networkviews are still fair game even during level loading, at least for non-RPC stuff, so you can put them in a separate group and let them keep communicating, allowing chat messages, progress indication, and so on to continue while the level loads. But it's not important, you can just disable everything instead.

    As the comments say, SetSendingEnabled is used to prevent the client from sending data until the old level has been destroyed and the new level fully loaded. Similarly, the message queue is paused - this prevents RPCs (especially buffered ones) from being erroneously interpreted before the networkviews that process them have been instantiated (as a result of level load). If they're interpreted too early, they just cause an error and get discarded, which is no good if you're relying on them to set up important state.
     
  7. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    thanks for the info. It helps alot.

    Something I stumbled upon though, once someone connects to the server and OnPlayerConnected gets activated, how am I going to make them load the level the server is on?

    function OnPlayerConnected(player: NetworkPlayer){

    Application.LoadLevel("Marslevel")
    }

    The above code would just load the level on the servers side. Not the client. So how would I make it happen for the client instead?
     
  8. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    Right, OnPlayerConnected probably isn't the best place to do that. It's easier to send a buffered RPC, which is what the example I linked to earlier does - in the OnGUI method on the server, when the player has chosen a level, it sends a buffered RPC to initiate the level load both on the server and on the clients. As the RPC is buffered, any clients who join later will also be sent a copy automatically.
     
  9. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    I think I get it now how sending a bufferd RPC works. I tried it and I think it went through to the client. Although, a bunch of errors showed up that are very foreign to me. The errors dont point to any specific script or gameObject so im not sure what they are. The client is trying to join the game mid way.

     
    Last edited: Aug 1, 2012
  10. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    These errors mean that the client received data about two networkviews that the it doesn't have in its scene. It usually means either that the client loaded the wrong scene, or used the wrong level prefix code, or failed to disable receiving of data while the level was being loaded. In this case it's state data, not RPCs.

    It can also mean that your network manager object got destroyed during the scene load (e.g. you forgot DontDestroyOnLoad), though it's unlikely in this case given the scene ID numbers.

    Most likely, to me, is that you're not using level prefixes properly, as these are set to zero in your error messages and really ought to be something else.
     
  11. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    I believe I am saving the game Objects I cant have destroyed with DontDestroyOnLoad(). And I've added Network.SetSendingEnabled and Network.isMessageQueueRunning to my script so they enable and disable at the right times when the level is being loaded. But I still get the same errors.

    What is a level prefix, and how can I solve that?
    Code (csharp):
    1.  
    2. function loadLevel (level : String ){
    3. Network.SetSendingEnabled(0, false);
    4. Network.isMessageQueueRunning = false;
    5. print("loading " + level);
    6. Application.LoadLevel(level);
    7. Network.isMessageQueueRunning = true;
    8. Network.SetSendingEnabled(0, true);
    9.  
    10. }
     
  12. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    Application.LoadLevel will return immediately, but schedule the (blocking) level loading to occur at the end of the current update loop. So you're actually turning the message queue back on a little early here.

    The example I linked to earlier gets around this by yielding, as a coroutine, immediately after the LoadLevel call - it does it twice, I'm not sure why, perhaps it helps some objects to get their Start() methods called, but left uncommented it looks more like a probably-unnecessary safety net.

    The thing with level prefix is important to ensure NetworkViews baked into the new level don't have IDs that conflict with old (dead or DontDestroyOnLoad) NetworkViews. So for NetworkViews defined in a scene, Unity ensures they have IDs different to each other, but it doesn't make the IDs unique between scenes. This means that in the transition from one scene to another, messages targeted at the old views might end up being interpreted by the new views with the corresponding IDs, which causes a lot of trouble.

    Even if the new level is the same scene as the old one, you still want to reinitialise the views and have them act independently of what happened in the previous level, so you don't want them receiving stale packets. It's even worse if the old view is DontDestroyOnLoad, because then you have two views with the same ID both active at once.

    Setting a new level prefix gets around that. Generally you want the server to maintain an integer, starting at 1, and every time it loads a new level it should increment the integer first, then include the integer in the LoadLevel RPC call. Receivers of the RPC then call Network.SetLevelPrefix before calling Application.LoadLevel, and all of the views in the new level will be guaranteed not to clash with the views in the old level - in a consistent way across all the clients, so they still agree with each other about the view IDs.
     
  13. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    I am trying to change the levelPrefix now before the level loads, but I get a new error. Followed by a whole bunch of repetitive errors.

    Failed to invoke arriving RPC method because the parameters didn't match the function declaration. 'loadLevel' of 'networkManagerScript'.

    I tried pasting the code thats inside of the load level function from the network level loading example into my load level function. But it doesn't change anything.

     
  14. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    When I unpause the game after the errors, I can actually run around on the level with the other player. Although theres a few things that arn't right.
     
  15. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    If you've changed the function declaration in the client, you need to change the call in the server as well to have the right parameters. The error is just saying that they don't currently match.
     
  16. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    I don't know what you mean by function declaration. I'm not sure what is not matching. The level Prefix?
     
  17. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    Make sure the declaration of your RPC receiver called "loadLevel" matches the parameters which the server is providing when it sends the RPC.

    i.e. these two bits of code need to match:

    Code (csharp):
    1.  
    2.     // client
    3.     function loadLevel(name : String, levelPrefixIndex : int)
    4.  
    Code (csharp):
    1.  
    2.     // server
    3.     networkView.RPC("loadLevel", RPCMode.OthersBuffered, "myLevel", currentLevelPrefixIndex);
    4.  
    The parameters after the RPC mode must match the parameters used by the client. Remember to update the server's loadLevel method too, if it's not the same component that the client uses.
     
  18. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    The argument count is the same though for the RPC and the function in my script.

    Code (csharp):
    1. networkView.RPC("loadLevel",RPCMode.AllBuffered,MapPlaylist[0], lastLevelPrefix + 1);
    Code (csharp):
    1.  
    2. @RPC
    3. function loadLevel (level : String, levelPrefix : int ){
     
  19. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    Am I missing something?
     
  20. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    could someone explain to me please?
     
  21. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    I really don't have a clue as to what the error means.

    Failed to invoke arriving RPC method because the parameters didn't match the function declaration. 'loadLevel' of 'networkManagerScript'.

    the arguments for the RPC and the function loadlevel has the same amount.
     
  22. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    550
    You're doing something wrong but it's not clear what. Do you have more than one loadLevel function? How are you running the client and server, since you can't do them both in the same Unity instance. Are you sure they both have the new code?
     
  23. roger0

    roger0

    Joined:
    Feb 3, 2012
    Posts:
    1,208
    For some reason i'm not getting the error messages now, and im able to load the level. It could of been I just had to redo the build.

    But I stumbled upon new problems so i'll probably be posting about those really soon.

    thanks for the help!