Search Unity

Resolved [Netcode] Creating a new default world

Discussion in 'NetCode for ECS' started by Samsle, Oct 23, 2020.

  1. Samsle

    Samsle

    Joined:
    Mar 31, 2020
    Posts:
    110
    Hello,
    I have a menu, from where I can go into a multiplayer game and back.
    I'm using Unity Netcode with a custom ClientServerBootstrap, the same shown in the documentation to prevent the auto-startup of client & server worlds. The problem with this, the default world is lacking of some important systems to render some entities in the menu (or a singleplayer section).
    defaultWorld.PNG

    So my idea was, adding a new world (MenuWorld) for the menu, so it should look like this:

    plan.PNG

    But the problem I face now is that I don't know how to populate my MenuWorld like a "normal" DefaultWorld (without NetCode) with all the basic systems to render entities etc., without breaking the already existing DefaultWorld. If I try to do the following, the existing DefaultWorld will have no systems anymore, but they are needed to initialize client/server worlds:
    defaultWorldEmpty.PNG
    Code (CSharp):
    1. public class ExampleBootstrap : ClientServerBootstrap
    2. {
    3.     public override bool Initialize(string defaultWorldName) {
    4.         var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);
    5.         GenerateSystemLists(systems);
    6.  
    7.         var world = new World(defaultWorldName);
    8.         World.DefaultGameObjectInjectionWorld = world;
    9.  
    10.         DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, ExplicitDefaultWorldSystems);
    11.         ScriptBehaviourUpdateOrder.UpdatePlayerLoop(world);
    12.  
    13.         CreateMenuWorld();
    14.  
    15.         return true;
    16.     }
    17.  
    18.     public void CreateMenuWorld {
    19.         World menuWorld = new World("MenuWorld");
    20.         DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(
    21.             menuWorld,
    22.             DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default)
    23.         );
    24.         ScriptBehaviourUpdateOrder.UpdatePlayerLoop(menuWorld);
    25.     }
    26. }
    I could not find any official documentation regarding this issue. But I was reading this from 5argon which was at least extremely helpful for understanding the basic ideas of worlds.

    Another thing I don't get is, what happens if I have 2 worlds with each of them having a RenderSystem? Afaik we can have multiple worlds running at the same time, like client-& server world. Which entities from which world would I see? Both weirdly mixed in one screen?
     
    Last edited: Oct 23, 2020
  2. Samsle

    Samsle

    Joined:
    Mar 31, 2020
    Posts:
    110
    The mistake I made was calling ScriptBehaviourUpdateOrder.UpdatePlayerLoop() twice (one time for each world).
    Instead we have to reuse the playerLoop like this:

    Code (CSharp):
    1. var playerLoop = PlayerLoop.GetDefaultPlayerLoop();
    2. ScriptBehaviourUpdateOrder.AddWorldToPlayerLoop(world, ref playerLoop);
    3. ScriptBehaviourUpdateOrder.AddWorldToPlayerLoop(menuWorld, ref playerLoop);
    4. PlayerLoop.SetPlayerLoop(playerLoop);
    5.  
    Another mistake was the menuWorld. It seems that we cannot have another non-client/server world in addition to defaultWorld. Because otherwise MultiplayerPlayModeControllerSystem::OnUpdate():229 will break (calling on null)

    Code (CSharp):
    1. if (simulationGroup != null && simulationGroup == PresentedClient)
    2.                 world.GetExistingSystem<ClientPresentationSystemGroup>().Enabled = true;
    3.  
    Its kind of solvable with adding a ClientPresentationSystemGroup to our 2nd default world (menuWorld), since the menu should only be on "client", but it actual means much more, it would become literally a new client, which gets ghosts loaded etc. So nothing I want to achieve actual.

    In the end, I just stick now to my 3 worlds: Default, Client & Server and use the DefaultWorld for all the other stuff, like menu etc.
    To get something rendered in the menu, there is a way to load another system-list to the world via
    DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, DefaultWorldSystems);

    instead of "ExplicitDefaultWorldSystems".
    Now entities are getting rendered in the menu too. Problem only is, that the menu entities from the menu scene, are also getting rendered in the client world. And since I cannot just shutdown & recreate the default world when I need to (or can I?), I need to find a better way for handling this issue.
     
  3. Krooq

    Krooq

    Joined:
    Jan 30, 2013
    Posts:
    196
    @Samsle wondering what you eventually did here?

    I'm thinking I'll just run menu stuff from the client world.. would that not work?
     
  4. Samsle

    Samsle

    Joined:
    Mar 31, 2020
    Posts:
    110
    Hey @krooq,
    I can't remember exactly the reasons for having my mainmenu outside of the client world. I guess some netcode systems of client world were interfering with entities of my menu. Also I do not have to clean up my gameplay stuff after going back to menu -just throwing the client world away and recreate a new one later on.

    But I think it's up to you, if it works for you within the client world, then just go for it ;)
     
  5. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    900
    The multi-world flow in general can get tricky because is is hard to isolate which systems to run in a specific world.

    The easiest and robust approach right now is probably just instantiating a new client world as "MenuWorld". Since no connection are present, the NetCode systems would not run and everything else should be already setup as you expect.
    When you will transition from menu to game, you will throw away you MenuWorld and instantiate the new gameplay world (that is again a client world).

    If you want to have more control, you can create a new MenuWorld using the
    Code (csharp):
    1.  
    2. DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, ExplicitDefaultWorldSystems);
    3.  
    And then add any extra / missings systems :
    - TransformSystems
    - SceneSystems
    - Rendering
    - You menu systems
    using the world.GetOrCreateSystemsAndLogException(systemTypes)
    The problem is that this is pretty much a manual process and you need to know what systems to include. It is something we really want to improve but it is not the priority right now.

    You may also use
    Code (csharp):
    1.  
    2. DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(world, DefaultWorldSystems);
    3.  
    The second one actually create a full default world with all the rendering systems etc but no netcode systems.
     
    Krooq and Samsle like this.
  6. Ali_Bakkal

    Ali_Bakkal

    Joined:
    Jan 26, 2013
    Posts:
    90
    Hello,

    @Samsle i am facing kind of the same issue.

    The difference is my menu scene does not use any systems, it is fully in Monobehaviour.
    But the issue i am facing, it is when i launch the Netcode scene the first time, it works perfectly and when i come back the second time, it does not work..

    So i guess i am not disposing the client world properly or i am not loading the Client World correctly..
    Can you please share me you code on that ? (if you explain it, it will be awesome.)

    I have this assumption : when you load a scene, it starts and loads automatically with a fresh reset.
    But i guess that when we open Netcode in a scene some systems/world keeps following us to other scenes.
    There is anything to destroy everything when you leave a scene with Netcode/ECS inside? @CMarastoni
     
  7. Samsle

    Samsle

    Joined:
    Mar 31, 2020
    Posts:
    110
    @CMarastoni Your advice sounds really good, it makes it easier to write "client" systems. For my case e.g., since I wrote my menu system for the default world -which is also running on server side, I had to make a lot of usage of
    #if !UNITY_SERVER
    to exclude it from running on the server.
    But one special advantage of the default world was, that I could render my ghosts in the menu (e.g. for letting the player choose a certain Ghost, without creating a new prefab for that unit), which wasn't possible back then with the client world. But if I think about it now.. maybe it would be possible with just removing some ghost components :oops:

    @Ali_Bakkal I can't exactly understand your scene flow. In my case I have just one Unity scene for everything and different subscenes for different levels and even my menu, since they are loading way faster then the usual Unity scenes.
    But to add some amateur knowledge (maybe wrong), your ECS worlds & systems should be consistent over all Unity scenes afaik, since ECS is independent from the old Unity workflow.


    For deleting my client world I do
    Code (CSharp):
    1. clientWorld.Dispose();
    And for creating a new one
    Code (CSharp):
    1. World clientWorld = ClientServerBootstrap.CreateClientWorld(
    2.      World.DefaultGameObjectInjectionWorld,
    3.      "ClientWorld"
    4. )
    And for finding the right world you can use something like this
    Code (CSharp):
    1. foreach (World world in World.All) {
    2.             if (world.GetExistingSystem<ClientSimulationSystemGroup>() != null) {
    3.                 clientWorld = world;
    4.             } else if (world.GetExistingSystem<ServerSimulationSystemGroup>() != null) {
    5.                 serverWorld = world;
    6.             }
    7.         }
     
  8. Ali_Bakkal

    Ali_Bakkal

    Joined:
    Jan 26, 2013
    Posts:
    90
    Thank you @Samsle

    Your code helped me to fix my issue ;)

    My mistake was that I did not use the correct code for creating new client world.
     
  9. Krooq

    Krooq

    Joined:
    Jan 30, 2013
    Posts:
    196
    @Samsle I'm wondering how you handle global manager type data in this setup with multiple worlds?
    For example, I need to decide when to show/hide the cursor.
    In the main menu world it should always be shown but in the client/gameplay world it should be hidden conditionally.
    Where would one store this data and the logic to update it?

    As much as I'd prefer a homogeneous programming style, I get the feeling that some of these more "application" level tasks should live in a non DOTS context, i.e. standard mono singleton type manager classes that live outside of any world.

    The other alternative I see is to simply stick with the main worlds, default, client and server.
    Code (CSharp):
    1.  
    2. #if !UNITY_SERVER
    3. // create client world at startup (from bootstrap perhaps) and use it for menu-y & application level things.
    4. #endif
    5.  
    If the application decides to host a game at runtime it can simply create the server world and start doing that job too.
    @CMarastoni would love to get your input how how these worlds are intended to be used in NetCode? Especially the default world.
     
    Last edited: Jul 26, 2021
  10. Krooq

    Krooq

    Joined:
    Jan 30, 2013
    Posts:
    196
    It would be really neat if you could create "SubWorlds" in DOTS. That would solve this issue really neatly.
    Essentially Netcode could run in it's own SubWorld which could be disposed when connecting/disconnecting.
     
  11. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    900
    You can do that already by using a custom bootstrap that only create the DefaultWorld using the ExplicitDefaultWorld systems.
    And then it is the default world system (or some UI or whatever) that create the client or server (or both) worlds using the CreateClientWorld and CreateServerWorld methods.
    When you disconnect or exit the game, you can completely destroy both worlds by just creating a request to some DefaultWorld system that will then to tier down the client and server world.
     
  12. Krooq

    Krooq

    Joined:
    Jan 30, 2013
    Posts:
    196
    Yeah this is possible.
    The challenge is that the Client world needs information from the Default world, for example, when a level is loaded or a menu is closed. Manually moving that data between worlds is not trivial to manage.
    If we could have "SubWorlds" then shared data is easy since everything technically lives in one outer world and the subworld just contains a grouping of systems and data that can easily be managed collectively i.e. disposed. Perhaps subworld is the wrong terminology, what I'm talking about is really just a grouping of systems and data from a world that can easily be disposed.
    I feel like DOTS is really missing this concept of modularity that the CustomBootstrap somewhat tries to solve with its system lists.

    I'm basically trying to avoid this mess
    https://github.com/Unity-Technologi...b6/Assets/Scripts/Game/Main/ClientGameLoop.cs
    Shouldn't need a custom loop composed of many loops to integrate with vanilla C# with DOTS.
     
    Last edited: Oct 15, 2021
  13. CMarastoni

    CMarastoni

    Unity Technologies

    Joined:
    Mar 18, 2020
    Posts:
    900
    When it comes to sharing data in between world that is definitively a messy point.
    It is possible to have just one world that contains both the default and the client world (so all systems, with all clients system belonging to a ClientSystemGroup). But then it is getting a little problematic split the world data. You can of course manually do that by adding shared component but become annoying to maintain.
     
    Krooq likes this.