Search Unity

generate sub scenes programmatically

Discussion in 'Entity Component System' started by RoughSpaghetti3211, Apr 16, 2020.

  1. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,709
    Is it possible to generate subscenes programmatically? And is that a bad idea.
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Feel like I'd need to know why you want to do this before deciding if it's a bad idea!

    I can totally see there being use for an editor tool to setup new subscenes with default objects etc.
     
  3. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    I had a usecase for this, converting to ECS a game that has a map editor / level design tool for a tile based game.

    It use to generate the map tiles at runtime but could be so much more efficient to create subscene at edit time to benefit from the static optimizations...
     
  4. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    I have tried to do the procedural approach to creating subscenes for my own open world project but with limited brain, limited time, rapid changes, and already knowing the scope of the project for now I just pooled a total number of subscenes based on map location. It works for now but is not flexible.
     
  5. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    We are working on a sample where we do procedural dungeon generation.

    Our approach is:
    * A dungeon tile is an entity scene
    * A procedural generator high level streaming rig (User code) has a list of SceneAsset references that define the possible tiles stored in a scriptable object.

    At load time the scriptable objects defines all possible scenes, we load the meta data of each scene. (Eg. what connectivity of tiles do we have) Based on that it determines the placement of every possible tile in the world. Each represented by an entity representing the scene.

    Then it uses normal streaming functionality to load the data. Eg. some scene sections are always loaded, other scene sections are loaded on demand based on distance.


    To Unity.Entities we are adding the following things to support this:
    * We are adding the ability to load & offset entity scenes (By letting users modify the loading staging world, before it is moved to the simulation world)
    * We are adding the ability to load the same entity scene multiple times (Each one offset differently)
    * We are adding the ability to generate meta data from the scene that can be used during placement

    I expect those will land in a release in a couple of weeks in dots.
     
    Orimay, Piefayth, Haneferd and 17 others like this.
  6. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Wouldn't happen to be this would it? https://github.com/Unity-Technologies/DOTS-StreamingSamples/tree/dungeon

    Was just randomly looking at it the other day

    (before anyone tries to run it, it requires access to releases that aren't publicly available yet.)
     
    MNNoxMortem likes this.
  7. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    What about a single subscene for the entire world, without the need to offset its entities, this is possible in the current state of things right ? just a matter of reusing what the editor already does to generate subscenes or is there more to it ?
     
  8. Zec_

    Zec_

    Joined:
    Feb 9, 2017
    Posts:
    148
    That sounds more or less exactly what we've hacked around doing in our project. Everything works great for us in our editor mode, but we have been having issues with getting it working in builds. Hearing that the support for loading the same entity scene multiple times with offset becoming officially supported is thus great!
     
    T-Zee likes this.
  9. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,709
    My use case wast procedural world
    This is pretty much what my use case is. I want to build predefined tiles and load then on demand. My game is on a spherical board proceduraly generated at run time and the board size determinants the number of tiles and sub scenes I need. Im doing all my tests at 65k tiles so that a lot of sub-scenes but only 100 unique tiles. I have all the tile relationships and position on a blob graph and was thinking I could just load, position and swap a sub scene during runtime. Im just not sure of the limitation of this method and what number of tiles and unique tiles are realistic, targeting pc and mobile with the device setting the number of tiles
     
  10. vildauget

    vildauget

    Joined:
    Mar 10, 2014
    Posts:
    121
    <3 Procedural gets love in DOTS <3

    What a day! I don't fancy tiles myself, it's too limited, but will check the the code with great interest. thanks!
     
  11. Mockarutan

    Mockarutan

    Joined:
    May 22, 2011
    Posts:
    159
    Wow... about 1000 line of code in vain for me :(
    But for real, I love this! My code was such a hack anyways! :D
     
    Last edited: Apr 17, 2020
  12. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,709
    It would be kinda cool to have sub scene variants
     
    Nyanpas likes this.
  13. rz_0lento

    rz_0lento

    Joined:
    Oct 8, 2013
    Posts:
    2,361
  14. Pigalot

    Pigalot

    Joined:
    Sep 1, 2018
    Posts:
    1
    Is there any information regarding when the required dependencies for the dungeon-hybridv2 will be in preview packages? I looks to do exactly what I am trying to do but I can see from loading it up that some key parts are missing from the preview packages.

    Thanks
     
  15. Zec_

    Zec_

    Joined:
    Feb 9, 2017
    Posts:
    148
    This functionality works since what I believe is Entities 0.13. I've started instancing subscenes and translating them individually. The only thing you should need is:

    When you load scenes, load them with the SceneLoadFlags.NewInstance flag. You also need to attach a PostLoadCommandBuffer on the sceneEntity. This will be executed in the streaming world when the scene has been loaded, before your processing systems run. You use this buffer to inject the data needed to translate or process your scene instance. Something like this:
    Code (CSharp):
    1. struct MyProcessingComponent : IComponentData
    2. {
    3.     public float4x4 SceneInstanceOffset;
    4. }
    5.  
    6. void LoadMyNewSceneInstance(SceneSystem sceneSystem, Unity.Entities.Hash128 sceneGuid, float4x4 sceneOffset)
    7. {
    8.     var loadParams = new SceneSystem.LoadParameters()
    9.     {
    10.         Flags = SceneLoadFlags.NewInstance
    11.     };
    12.  
    13.     Entity sceneEntity = _SceneSystem.LoadSceneAsync(sceneGuid, loadParams);
    14.  
    15.     PostLoadCommandBuffer postLoadCommandBuffer = new PostLoadCommandBuffer();
    16.     postLoadCommandBuffer.CommandBuffer = new EntityCommandBuffer(Allocator.Persistent, PlaybackPolicy.MultiPlayback);
    17.     EntityManager.AddComponentData(sceneEntity, postLoadCommandBuffer);
    18.  
    19.     Entity postLoadDataEntity = postLoadCommandBuffer.CommandBuffer.CreateEntity();
    20.     postLoadCommandBuffer.CommandBuffer.AddComponent(postLoadDataEntity, new MyProcessingComponent() { SceneInstanceOffset = sceneOffset });
    21. }
    The entity with the custom scene instance data (the "MyProcessingComponent") that we created above will exist in the scene streaming world after we've loaded the scene. Since we've only created one instance, we can treat it as a singleton. You then inject Systems that handles the moving and/or other processing of your subscene using WorldSystemFilterFlags.ProcessAfterLoad:
    Code (CSharp):
    1.  
    2. [WorldSystemFilter(WorldSystemFilterFlags.ProcessAfterLoad)]
    3. public class MoveMySceneAfterLoadSystem : SystemBase
    4. {
    5.     protected override void OnCreate()
    6.     {
    7.         RequireSingletonForUpdate<MyProcessingComponent>();
    8.     }
    9.  
    10.     protected override void OnUpdate()
    11.     {
    12.         var myProcessingComponent = GetSingleton<MyProcessingComponent>();
    13.         // Do your processing.
    14.     }
    15. }
    16.  
    The sample project has unfortunately not been updated so it still relies on some internal Entities package branch, so it doesn't work for us outsiders. I found the sample to be quite large and "messy" for the functionality it was trying to display. The core parts that are needed to get it working are what I wrote above.

    Edit:
    What you can extend this with is that you can load the scene template metadata as an entity, and then later on instantiate that entity with the NewInstance flag instead. This is what they do in the sample.

    Code (CSharp):
    1. private Entity LoadInstantiableSceneTemplate(SceneSystem sceneSystem, Unity.Entities.Hash128 subSceneGuid)
    2. {
    3.     SceneSystem.LoadParameters loadParams = new SceneSystem.LoadParameters()
    4.     {
    5.         Flags = SceneLoadFlags.DisableAutoLoad // Only loads the metadata entity
    6.     };
    7.  
    8.     // You can load this entity to access general any metadata you've attached to the scene you want to instantiate.
    9.     var sceneTemplateEntity = sceneSystem.LoadSceneAsync(subSceneGuid, loadParams);
    10.     return sceneTemplateEntity;
    11. }
    12.  
    13. private void InstantiateSceneTemplate(SceneSystem sceneSystem, Entity sceneTemplateEntity)
    14. {
    15.     Entity sceneInstance = EntityManager.Instantiate(sceneTemplateEntity);
    16.     SceneSystem.LoadParameters loadParams = new SceneSystem.LoadParameters()
    17.     {
    18.         Flags = SceneLoadFlags.NewInstance
    19.     };
    20.     sceneSystem.LoadSceneAsync(sceneInstance, loadParams);
    21.  
    22.     // And then you add the commandbuffer etc to the sceneInstance instead.
    23. }
     
    Last edited: Sep 5, 2020
  16. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @Zec_ thank you for this thorough response, probably the most detailed info on subscene/world instantiation I can find.

    When moving between scenes, what is the order of world creation/entity instantiation? When in a scene with no entities and only a DefaultWorld and then switching to a scene that contains a subscene (with entities), how are the subscene entities loaded into existing worlds?

    If when switching to a scene I would like to create worlds (like client/server worlds in netcode) and then have the subscene entities contained in both those worlds, what is the "order" to make this occur?

    Running

    SceneManager.LoadSceneAsync(m_SceneName);

    m_SceneName has a script that runs on Awake:

    ClientServerBootstrap.CreateServerWorld(world, "ServerWorld");
    ClientServerBootstrap.CreateClientWorld(world, "ClientWorld");

    Somehow the entities from the subscene in m_SceneName don't make it into "ServerWorld" and "ClientWorld".

    How can I control the order of: scene loading, subscene entity loading, world creation?
     
  17. Zec_

    Zec_

    Joined:
    Feb 9, 2017
    Posts:
    148
    We're using a similar workflow where we create server and client worlds in OnEnable on a script, and then load SubScenes into both worlds. We're not using the NetCode package though, so I don't know how that package handles server/client worlds.

    Are you using the "Auto Load Scene"-option in the SubScene? We're not, and I believe it does not load SubScene into custom created worlds that are created after the SubScene monobehaviour has had it's OnEnable executed. We don't use that option, we've taken manual control over when the SubScenes are loaded. You can do the following:
    Code (CSharp):
    1. public class BootstrapScript : MonoBehaviour
    2. {
    3.     public SubScene MySubScene;
    4.  
    5.     void Awake()
    6.     {
    7.         World defaultWorld = null;
    8.  
    9.         // Don't know if you can get the server/client worlds like this, but you need a reference to them
    10.         // for the steps below
    11.         World serverWorld = ClientServerBootstrap.CreateServerWorld(defaultWorld, "ServerWorld");
    12.         World clientWorld = ClientServerBootstrap.CreateClientWorld(defaultWorld, "ClientWorld");
    13.  
    14.         // I'm not sure if your ClientServerBootstrap includes the scene systems in the target worlds,
    15.         // but they're required to load SubScenes into worlds.
    16.         SceneSystem serverSceneSystem = serverWorld.GetExistingSystem<SceneSystem>();
    17.         SceneSystem clientSceneSystem = clientWorld.GetExistingSystem<SceneSystem>();
    18.  
    19.         // If you need to unload the scene, these functions return an entity that acts as a scene instance reference.
    20.         // This function also contains an optional parameter to control how the scene is loaded (synchronous loading etc)
    21.         Entity serverScene = serverSceneSystem.LoadSceneAsync(MySubScene.SceneGUID);
    22.         Entity clientScene = clientSceneSystem.LoadSceneAsync(MySubScene.SceneGUID);
    23.  
    24.         // If you need to unload scenes, you do this.
    25.         serverSceneSystem.UnloadScene(serverScene);
    26.         clientSceneSystem.UnloadScene(clientScene);
    27.     }
    28. }
    29.  
    30.  
     
  18. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @Zec_ you rock so hard, going to try this out thank you.

    "We're not, and I believe it does not load SubScene into custom created worlds that are created after the SubScene monobehaviour has had it's OnEnable executed."

    I was wondering why the subscene wasn't loaded into the custom created worlds, this makes sense.
     
    Last edited: Dec 8, 2020
  19. Lieene-Guo

    Lieene-Guo

    Joined:
    Aug 20, 2013
    Posts:
    547
    This is how SubScene Monobehavior add Entity SubScene
    Code (CSharp):
    1.         void AddSceneEntities()
    2.         {
    3.             Assert.IsTrue(_AddedSceneGUID == default);
    4.             Assert.IsFalse(_SceneGUID == default);
    5.  
    6.             var flags = AutoLoadScene ? 0 : SceneLoadFlags.DisableAutoLoad;
    7. #if UNITY_EDITOR
    8.             flags |= EditorApplication.isPlaying ? SceneLoadFlags.BlockOnImport : 0;
    9. #else
    10.             flags |= SceneLoadFlags.BlockOnImport;
    11. #endif
    12.             foreach (var world in World.All)
    13.             {
    14.                 var sceneSystem = world.GetExistingSystem<SceneSystem>();
    15.                 if (sceneSystem != null)
    16.                 {
    17.                     var loadParams = new SceneSystem.LoadParameters
    18.                     {
    19.                         Flags = flags
    20.                     };
    21.  
    22.                     var sceneEntity = sceneSystem.LoadSceneAsync(_SceneGUID, loadParams);
    23.                     sceneSystem.EntityManager.AddComponentObject(sceneEntity, this);
    24.                     _AddedSceneGUID = _SceneGUID;
    25.                 }
    26.             }
    27.         }
    and this part states only those world created with an active SceneSystem can receive new entity with from SubScene
    Code (CSharp):
    1.             foreach (var world in World.All)
    2.             {
    3.                 var sceneSystem = world.GetExistingSystem<SceneSystem>();
    4.                 .....
    5.              }
    6.  
    So check your custom world if it has SceneSystem. And you need these three systems in your custom world to load a subsence to the world.
    upload_2020-12-8_9-51-27.png

    SceneSystem: place the load command
    Resovle: find all scene sections to load
    Streaming: do the loading job

    And the entity are loaded into these worlds first
    upload_2020-12-8_9-55-7.png
    if AutoLoadScene is not checked, the loaded entity will not be copied to the target world.
    upload_2020-12-8_9-55-59.png
    They are copied to the target world when a RequestSceneLoaded component is added to the
    sceneEntity in target world.

    Detail are all in the source code of the SceneSystems(in SceneSystemGroup)
     
    adammpolak likes this.
  20. adammpolak

    adammpolak

    Joined:
    Sep 9, 2018
    Posts:
    450
    @Lieene-Guo this is terrific, super appreciate the breakdown!
     
  21. joshrs926

    joshrs926

    Joined:
    Jan 31, 2021
    Posts:
    116
    Here is how you can create Sub Scenes from Editor code. I've included a sample prefab-instantiator. Basically you just create a normal scene, add whatever GameObjects you want to it, then create a GameObject to act as the sub scene (don't put that in the scene you just created) and add the SubScene MonoBehaviour to it and set its SceneAsset member equal to the normal scene you created. Then you can unload the normal scene and you are left with a SubScene. I was getting an "ArgumentException: The scene is not loaded." when I spawned 40,000+ GameObjects across 4+ subScenes. I suspect this was just the Editor trying to render the normal scene object text in the hierarchy but it had already unloaded.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using UnityEngine.SceneManagement;
    4. using UnityEditor.SceneManagement;
    5. using Unity.Scenes;
    6.  
    7. public static class SubSceneUtility
    8. {
    9.     /// <summary>
    10.     /// Path must be relative to the Assets folder eg: 'Assets/NewSubScene.unity'.
    11.     /// </summary>
    12.     public static Scene CreateScene(string path = "Assets/NewSubScene.unity")
    13.     {
    14.         Scene activeScene = EditorSceneManager.GetActiveScene();
    15.         path = AssetDatabase.GenerateUniqueAssetPath(path);
    16.         int indexOfName = path.LastIndexOf('/') + 1;
    17.         string sceneName = path.Substring(indexOfName, path.Length - indexOfName);
    18.         Scene scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
    19.         scene.name = sceneName;
    20.         EditorSceneManager.SaveScene(scene, path);
    21.         EditorSceneManager.SetActiveScene(activeScene);
    22.         return scene;
    23.     }
    24.  
    25.     public static void DeleteSubScene(GameObject subSceneGO)
    26.     {
    27.         SubScene subScene = subSceneGO.GetComponent<SubScene>();
    28.         if (subScene == null || subScene.SceneAsset == null) { return; }
    29.         string path = AssetDatabase.GetAssetPath(subScene.SceneAsset);
    30.         AssetDatabase.DeleteAsset(path);
    31.         MonoBehaviour.DestroyImmediate(subSceneGO);
    32.     }
    33.  
    34.     public static GameObject CreateSubScene(Scene scene)
    35.     {
    36.         EditorSceneManager.SaveScene(scene);
    37.         SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scene.path);
    38.         GameObject subSceneGO = new GameObject(scene.name);
    39.         SubScene subScene = subSceneGO.AddComponent<SubScene>();
    40.         subScene.SceneAsset = sceneAsset;
    41.         EditorSceneManager.UnloadSceneAsync(scene);
    42.         return subSceneGO;
    43.     }
    44. }
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEditor;
    4. using Unity.Mathematics;
    5. using UnityEngine.SceneManagement;
    6.  
    7. public class PrefabSpawner : MonoBehaviour
    8. {
    9.     public GameObject prefab = null;
    10.     public float2 prefabSize = new float2(1);
    11.     public int2 countsPerSubScene;
    12.     public int2 subSceneCounts;
    13.     int sqrtSubSceneCount;
    14.     public int TotalCount { get; private set; }
    15.    
    16.  
    17.     public void SpawnPrefabs()
    18.     {
    19.         if (prefab == null)
    20.         {
    21.             Debug.LogWarning("Prefab missing.");
    22.             return;
    23.         }
    24.         if (TotalCount <= 0)
    25.         {
    26.             Debug.LogWarning("Total count is 0.");
    27.             return;
    28.         }
    29.         DeleteSpawnedPrefabs();
    30.         List<Scene> scenes = new List<Scene>();
    31.         for (int sceneY = 0; sceneY < subSceneCounts.y; sceneY++)
    32.         {
    33.             for (int sceneX = 0; sceneX < subSceneCounts.x; sceneX++)
    34.             {
    35.                 int2 sceneCrds = new int2(sceneX, sceneY);
    36.                 Scene scene = SubSceneUtility.CreateScene();
    37.                 scenes.Add(scene);
    38.                 for (int instanceY = 0; instanceY < countsPerSubScene.y; instanceY++)
    39.                 {
    40.                     for (int instanceX = 0; instanceX < countsPerSubScene.x; instanceX++)
    41.                     {
    42.                         int2 instanceCrdsInScene = new int2(instanceX, instanceY);
    43.                         int2 instanceCrds = sceneCrds * countsPerSubScene + instanceCrdsInScene;
    44.                         float3 position = new float3(
    45.                             (instanceCrds.x + .5f) * prefabSize.x,
    46.                             0,
    47.                             (instanceCrds.y + .5f) * prefabSize.y);
    48.                         GameObject instance = PrefabUtility.InstantiatePrefab(prefab, scene) as GameObject;
    49.                         instance.transform.position = position;
    50.                         instance.AddComponent<Instance>();
    51.                     }
    52.                 }
    53.             }
    54.         }
    55.         foreach (var scene in scenes)
    56.         {
    57.             GameObject subSceneGO = SubSceneUtility.CreateSubScene(scene);
    58.             subSceneGO.AddComponent<Group>();
    59.         }
    60.     }
    61.  
    62.     void DeleteSpawnedPrefabs()
    63.     {
    64.         var groups = FindObjectsOfType<Group>();
    65.         foreach (var group in groups)
    66.         {
    67.             SubSceneUtility.DeleteSubScene(group.gameObject);
    68.         }
    69.         var instances = FindObjectsOfType<Instance>();
    70.         foreach (var instance in instances)
    71.         {
    72.             DestroyImmediate(instance.gameObject);
    73.         }
    74.     }
    75.  
    76.     private void OnValidate()
    77.     {
    78.         TotalCount = countsPerSubScene.x * countsPerSubScene.y * subSceneCounts.x * subSceneCounts.y;
    79.     }
    80. }
    81.  
    82. [CustomEditor(typeof(PrefabSpawner))]
    83. public class SpawnPrefab_Editor : Editor
    84. {
    85.     public override void OnInspectorGUI()
    86.     {
    87.         PrefabSpawner p = target as PrefabSpawner;
    88.         if (GUILayout.Button("Respawn"))
    89.         {
    90.             p.SpawnPrefabs();
    91.         }
    92.         base.OnInspectorGUI();
    93.         GUILayout.Label("Total count: " + p.TotalCount.ToString());
    94.     }
    95. }
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Instance : MonoBehaviour
    4. {
    5.    
    6. }
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Group : MonoBehaviour
    4. {
    5.    
    6. }
     
    bb8_1 and colin_young like this.
  22. lclemens

    lclemens

    Joined:
    Feb 15, 2020
    Posts:
    761
    How do you open/expand a subscene programmatically in the editor? I have an editor tool and I figured out how to add an object to the subscene and save it, but it will fail if I try to add the object if the subscene is closed.

    EDIT: I figured it out.

    Code (CSharp):
    1. UnityEditor.SceneManagement.EditorSceneManager.OpenScene(m_subScene.EditableScenePath, UnityEditor.SceneManagement.OpenSceneMode.Additive);
     
    Last edited: Jul 25, 2023