Announcement

Collapse
No announcement yet.

Loading Sub-Level multiple times, possible?

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    Loading Sub-Level multiple times, possible?

    I want to load a sublevel multiple times (load multiple instances of one level). I dont want to copy the level file and rename it e.g. "house_1.umap, house_2.umap, house_3.umap, ...", because then i define the max number of instances and if i want to change something i need to do it in all levels or recopy them, its realy dirty (it should be consistent). I know that the level-umap-name is unique to address the sublevel inside the streaming, thats an obstacle.
    Someone has an idea how to solve this problem?
    Last edited by Nhorth; 02-24-2015, 08:01 AM.

    #2
    Using streaming levels would be difficult to load them multiple times, but you could just spawn the actors contained in the level multiple times

    I did such a test and it works kind a nice, though you need a lot of pre-setup and logic for it. But lets go with the step by step idea:

    The way I did it was creating levels that are used as a template levels, each level must have its center in the same place so will be able to offset it between levels and with the actors it contains. This is useful once you spawn all actors of that level.

    A template level contains only actors that implement an interface I defined (I called it ICloneAble ), every Actor of an ICloneAble as a method which takes in in parameter to copy over all it values, this seams easy at first but can be very hard to do for each value you want. You could use reflection if you want all or even add a new property flag which dictates if it will be cloned or not.

    Another difficult task is references in the template level, I resolved it by creating my new clones following the reference graph of the actors, I did it the simple way by just adding a list in each template actor to maintain references, again this can be automated using reflection.

    When cloning the actor within the template level make sure to disable physics (set them all to phys_none and then again to the correct one once you are done with all of'em). Knowing the offset of each level and that the center you can set the translation of the cloned actor easily buy maintaining the same relationship with the new center point where you would like to place your copy level.

    While you are doing all your stuff make sure to cleanup any references that you might hold to any actor of your template level, if not you will get a leak in a shipping build or a crash in a development build, the good think is that the crash will tell you where you are leaking ^^.

    To be able to optimize memory consumption I always loaded the template level, did the copy/clone process and unloaded it, this way you will only hold it in memory while you are cloning it's content. If your are reusing that level again and again you could hold them in memory. Make sure to not make your template level visible, you do not want any BeginPlay event to get called on it ^^.

    Btw, I used this little system to play around and build a 'prefab' like system.

    Cheers,
    Moss
    Last edited by Moss; 02-24-2015, 09:44 AM.
    Sr. Engine Programmer @ www.playspace.com - moritzwundke.com
    Remember: be polite and respect other peoples opinions - Join the Unofficial Unreal Discord Channel - Found a bug? Then use the Bug Report Form to get it fixed ^^

    Comment


      #3
      Just as a side node, the idea in doing in runtime is not very nice performance wise. Doing such a thing in editor time though is similar to what the guys a Yager might be doing with their AssetGroups, in editor time you also have access to every level and you do not need to stream it in.
      Sr. Engine Programmer @ www.playspace.com - moritzwundke.com
      Remember: be polite and respect other peoples opinions - Join the Unofficial Unreal Discord Channel - Found a bug? Then use the Bug Report Form to get it fixed ^^

      Comment


        #4
        It can be done, this is how Fortnite loads in some of its dynamically generated level layout, however, it is a bit manual.

        This is roughly the Fortnite code for doing so, you may well have to fiddle around with it to get it to work the way you want, I'm afraid there isn't really a document on how this all works at the moment so your mileage may vary and it may take some fiddling to get it right, but here's where you'd start with it at any rate.

        Code:
        	ULevelStreamingKismet* StreamingLevel = static_cast<ULevelStreamingKismet*>(StaticConstructObject(ULevelStreamingKismet::StaticClass(), GetWorld(), NAME_None, RF_NoFlags, NULL));
        
        	// Associate a package name.
        	StreamingLevel->SetWorldAssetByPackageName(LevelToStream);
        	if (GetWorld()->IsPlayInEditor())
        	{
        		FWorldContext WorldContext = GEngine->GetWorldContextFromWorldChecked(GetWorld());
        		StreamingLevel->RenameForPIE(WorldContext.PIEInstance);
        	}
        
        	StreamingLevel->LevelColor = FColor::MakeRandomColor();
        	StreamingLevel->bShouldBeLoaded	= true;
        	StreamingLevel->bShouldBeVisible = true;
        	StreamingLevel->bShouldBlockOnLoad = false;
        	StreamingLevel->bInitiallyLoaded = true;
        	StreamingLevel->bInitiallyVisible = true;
        
        	StreamingLevel->LevelTransform = // where to put it
        
        	StreamingLevel->PackageNameToLoad = // PackageName containing level to load
        
        	FString PackageFileName;
        	if( !FPackageName::DoesPackageExist( StreamingLevel->PackageNameToLoad.ToString(), NULL, &PackageFileName ) )
        	{
        		UE_LOG(LogStreamingLevel, Error, TEXT("trying to load invalid level %s"), *StreamingLevel->PackageNameToLoad.ToString());
        		return false;
        	}
        
        	StreamingLevel->PackageNameToLoad = FName(*FPackageName::FilenameToLongPackageName(PackageFileName));
        
        	// Add the new level to world.
        	GetWorld()->StreamingLevels.Add( StreamingLevel );

        Comment


          #5
          Originally posted by Marc Audy View Post
          It can be done, this is how Fortnite loads in some of its dynamically generated level layout, however, it is a bit manual.

          This is roughly the Fortnite code for doing so, you may well have to fiddle around with it to get it to work the way you want, I'm afraid there isn't really a document on how this all works at the moment so your mileage may vary and it may take some fiddling to get it right, but here's where you'd start with it at any rate.

          Code:
          	ULevelStreamingKismet* StreamingLevel = static_cast<ULevelStreamingKismet*>(StaticConstructObject(ULevelStreamingKismet::StaticClass(), GetWorld(), NAME_None, RF_NoFlags, NULL));
          
          	// Associate a package name.
          	StreamingLevel->SetWorldAssetByPackageName(LevelToStream);
          	if (GetWorld()->IsPlayInEditor())
          	{
          		FWorldContext WorldContext = GEngine->GetWorldContextFromWorldChecked(GetWorld());
          		StreamingLevel->RenameForPIE(WorldContext.PIEInstance);
          	}
          
          	StreamingLevel->LevelColor = FColor::MakeRandomColor();
          	StreamingLevel->bShouldBeLoaded	= true;
          	StreamingLevel->bShouldBeVisible = true;
          	StreamingLevel->bShouldBlockOnLoad = false;
          	StreamingLevel->bInitiallyLoaded = true;
          	StreamingLevel->bInitiallyVisible = true;
          
          	StreamingLevel->LevelTransform = // where to put it
          
          	StreamingLevel->PackageNameToLoad = // PackageName containing level to load
          
          	FString PackageFileName;
          	if( !FPackageName::DoesPackageExist( StreamingLevel->PackageNameToLoad.ToString(), NULL, &PackageFileName ) )
          	{
          		UE_LOG(LogStreamingLevel, Error, TEXT("trying to load invalid level %s"), *StreamingLevel->PackageNameToLoad.ToString());
          		return false;
          	}
          
          	StreamingLevel->PackageNameToLoad = FName(*FPackageName::FilenameToLongPackageName(PackageFileName));
          
          	// Add the new level to world.
          	GetWorld()->StreamingLevels.Add( StreamingLevel );
          Wow, Epic employees sure are wizards!
          KITATUS
          "Information shouldn't be behind a paywall, It should be free for all!"

          Comment


            #6
            Wow nice! Are there any drawback using the steaming level approach? The only thing that I worry about it is how you position the levels content.
            Sr. Engine Programmer @ www.playspace.com - moritzwundke.com
            Remember: be polite and respect other peoples opinions - Join the Unofficial Unreal Discord Channel - Found a bug? Then use the Bug Report Form to get it fixed ^^

            Comment


              #7
              Wow so many good answers, big Thanks! I realy like the streaming level approach and i want to implement it. But i need to lern more about, how this hole streaming process works internally on the c++ side. I only find explanations how to use it in the editor but i want to know how it works under the hood.

              About Marc's code: I think i got a rough idea how this code works. It will be great if you guys can help me to understand the details:

              Code:
              ULevelStreamingKismet* StreamingLevel = static_cast<ULevelStreamingKismet*>(StaticConstructObject(ULevelStreamingKismet::StaticClass(), GetWorld(), NAME_None, RF_NoFlags, NULL));
              Here we get a basic Level named "StreamingLevel", which we want to configure and load later?

              Code:
              StreamingLevel->SetWorldAssetByPackageName(LevelToStream);
              "LevelToStream" is the name of the level we want to load into the persistent level. What exactly does this set function do? Is this the part where we declare which "LevelToStream".umap we want to load? The "StreamingLevel" gets a reference to the umap file? Does PackageName means umap level name?

              Code:
              if (GetWorld()->IsPlayInEditor())
              {
              	FWorldContext WorldContext = GEngine->GetWorldContextFromWorldChecked(GetWorld());
              	StreamingLevel->RenameForPIE(WorldContext.PIEInstance);
              }
              What does this part do?

              Code:
              StreamingLevel->PackageNameToLoad = // PackageName containing level to load
              
              FString PackageFileName;
              if( !FPackageName::DoesPackageExist( StreamingLevel->PackageNameToLoad.ToString(), NULL, &PackageFileName ) )
              {
              	UE_LOG(LogStreamingLevel, Error, TEXT("trying to load invalid level %s"), *StreamingLevel->PackageNameToLoad.ToString());
              	return false;
              }
              
              StreamingLevel->PackageNameToLoad = FName(*FPackageName::FilenameToLongPackageName(PackageFileName))
              What is a package for level streaming? PackageName ?

              Code:
              // Add the new level to world.
              GetWorld()->StreamingLevels.Add( StreamingLevel );
              "StreamingLevels" is the persistent level, where we want to load our new "StreamingLevel" ? how do i get the persistent level?

              How do i address the different levels? Normally the level name is unique and i can get a reference by the name. Where do i make sure the name is unique and how do i know the unique level name, if i want to unload a level? Or is there a better way to address a level?

              My plan is too make a custom ALevelScriptActor class with a new load function which implements this code. This way i can use the function in code, but also in the level blueprint.
              Last edited by Nhorth; 02-25-2015, 02:33 PM.

              Comment


                #8
                Can someone help me with my questions? I want to understand this. Maybe Marc or Moss?

                Comment


                  #9
                  Here we get a basic Level named "StreamingLevel", which we want to configure and load later?
                  Basically. It is something that can be loaded in and out. You can see how it is set up to immediately start loading, though it could be set up in such a way that you dynamically toggle it in later. You could also potentially use ULevelStreamingAlwaysLoaded if there is no bringing in and out, but I will caveat that with I'm not clear on why Fortnite would want the ability to stream them in and out, so it may be necessary to use the Kismet version for this dynamic purposes, I'm not sure.

                  "LevelToStream" is the name of the level we want to load into the persistent level. What exactly does this set function do? Is this the part where we declare which "LevelToStream".umap we want to load? The "StreamingLevel" gets a reference to the umap file? Does PackageName means umap level name?
                  I looked at the internals of that function, without a lot of investigation I can't say immediately what it accomplishes, but I do believe that this is your map file name

                  Code:
                  if (GetWorld()->IsPlayInEditor())
                  {
                  	FWorldContext WorldContext = GEngine->GetWorldContextFromWorldChecked(GetWorld());
                  	StreamingLevel->RenameForPIE(WorldContext.PIEInstance);
                  }
                  What does this part do?
                  When you play a level in the editor (PIE) we have to put some prefixes on the map name so that they don't conflict with the loaded editor name and so we can have multiples of them for the multiplayer PIE feature. This just sets up the streaming level so that it is using the correctly prefixed name.

                  What is a package for level streaming? PackageName ?
                  This is probably your map name again.

                  "StreamingLevels" is the persistent level, where we want to load our new "StreamingLevel" ? how do i get the persistent level?
                  StreamingLevels is an array on the persistent level (i.e. the level in the main map that has the rest of the sublevels streamed in to it) that manages which levels are in memory and unloaded at a given time. Normally it is manipulated from the Levels panel in the editor, but here you are dynamically adding entries to it.

                  How do i address the different levels? Normally the level name is unique and i can get a reference by the name. Where do i make sure the name is unique and how do i know the unique level name, if i want to unload a level? Or is there a better way to address a level?
                  Probably need to hang on to this StreamingLevel pointer. From there you can specify for it to be unloaded by setting the bShouldBeLoaded to false, I believe you can also get a direct reference to the ULevel from the ULevelStreaming object, if you need to look at its specific Actors or some such thing.

                  As I said, this isn't necessarily a direct roadmap on getting this working, but hopefully it will point you in the right direction. If I find some time I'll start up fortnite (though in debug that can be painfully slow) and drop a breakpoint in this function to see what the values that are being fed to the different pieces are.

                  Good luck.

                  Comment


                    #10
                    Marc, is there a simple way to populate the list of levels of a persistent level auto-magically in run-time? I'm thinking about the 'Asset Groups' feature the guys at Yager did, using streaming levels as the base template for prefabs would be really nice but adding all levels in a persistent level could be a hell to maintain.
                    Sr. Engine Programmer @ www.playspace.com - moritzwundke.com
                    Remember: be polite and respect other peoples opinions - Join the Unofficial Unreal Discord Channel - Found a bug? Then use the Bug Report Form to get it fixed ^^

                    Comment


                      #11
                      Originally posted by Moss View Post
                      Marc, is there a simple way to populate the list of levels of a persistent level auto-magically in run-time? I'm thinking about the 'Asset Groups' feature the guys at Yager did, using streaming levels as the base template for prefabs would be really nice but adding all levels in a persistent level could be a hell to maintain.
                      That is roughly how the World Composition feature works. It uses a folder and will pull in each of those levels as sublevels.

                      If I find some time I'll start up fortnite (though in debug that can be painfully slow) and drop a breakpoint in this function to see what the values that are being fed to the different pieces are.
                      I got curious, so I pulled it up:

                      Code:
                      StreamingLevel->SetWorldAssetByPackageName(LevelToStream);
                      This sets up a unique name to use for the streaming level. This lets the LevelStreaming object have a name that points to a unique versino of the map that you'll specify in PackageNameToLoad. It can probably be anything unique, but Fortnite uses the name of the level with some unique identifier at the end so as to make debugging easier.

                      Code:
                      StreamingLevel->PackageNameToLoad = ...
                      Set this to the full path to the map package without .umap ... so for example /Game/Maps/MySubLevel


                      Alright, I think that's as much time as I can spend digging in to it ... I hope you can use this information to get the results you want.

                      Comment


                        #12
                        So 'SetWorldAssetByPackageName' seams to be the right way to begin with.

                        Thanks Marc ^^

                        Cheers,
                        Moss
                        Sr. Engine Programmer @ www.playspace.com - moritzwundke.com
                        Remember: be polite and respect other peoples opinions - Join the Unofficial Unreal Discord Channel - Found a bug? Then use the Bug Report Form to get it fixed ^^

                        Comment


                          #13
                          Thanks Marc for your time! Now i have hope to make this work.
                          I find it really exciting to see implementation-ideas which are used in big productions. I want to learn more and more about this.
                          Last edited by Nhorth; 02-27-2015, 06:03 AM.

                          Comment


                            #14
                            I got a question if i put my houses and my houses are individual levels where are setup like old school prefabs and i place that in the main world all from the editor always loaded and for example i got around 1.000 houses in the main world this will give me problems of performance or something ?
                            Hevedy - Instance Tools: https://hevedy.itch.io/hevedyinstances
                            Hevedy - Image Tools: https://hevedy.itch.io/imagetools

                            Comment


                              #15
                              Originally posted by Hevedy View Post
                              I got a question if i put my houses and my houses are individual levels where are setup like old school prefabs and i place that in the main world all from the editor always loaded and for example i got around 1.000 houses in the main world this will give me problems of performance or something ?
                              In this case; Why not just use ISMs?
                              KITATUS
                              "Information shouldn't be behind a paywall, It should be free for all!"

                              Comment

                              Working...
                              X