While our game may be running without any issues in the editor or even in…
Handling level streaming through C++
In this post we’re going to implement a project that uses level streaming. Then, using a custom console command, we will be able to load and change the position of the desired sub levels.
For this tutorial I’m using the 4.12 version of UE 4 Editor.
Creating the project
Create a blank C++ project and right away, save the default map with a name of BaseMap. Then, add two new maps in the same folder as the BaseMap, named MapOne and MapTwo and place some objects in each one. The reason we need to save all the maps we want to stream, in the same folder, is due to the implementation of the engine. Maps which are located in different folders cannot be streamed.
Here is a screenshot of each map in my case:
Save and close your maps. Then, open up the BaseMap and in the World Settings Menu Activate the Enable World Composition Option. The following message will pop up:
Click the OK button. Then, from your editor open up the Levels panel (Window -> Levels). Select both of your maps and by right clicking load then in your base map. This is my result:
Then, we need to create a Layer in which our maps will reside. We need a new layer so our sub levels won’t be loaded by default. To add a new layer, click on the marked icon to summon the World Composition and:
- Click on the “+” Icon to create a new layer
- Name your layer (in my case I named it “MyLayer”)
- Disable the streaming distance
- Click the create button
When you’re done with that, select both of your maps from the Levels panel, right click on them and assign them to MyLayer:
As I mentioned in the start of the post, we’re going to use a custom command through the console to move our maps during game time. To achieve that, we’re going to modify the GameMode C++ class that comes with the blank c++ template. More information of how to create custom console commands can be found here.
Moreover, in order to move the sub levels, we’re going to need a location. Instead of typing in locations, I will create a new c++ actor class and place some instances on the BaseMap level. Then, I will store all the placed actors of the BaseMap level in an array. As a result, I will simply type one number (which will be the index of actor in the array) and my sub level will get spawned in the location of that Actor.
If that sounds confusing, don’t worry! Everything will get clear once we type in our code. However, before we do that, add a new C++ Actor class named LevelMovePoint.
Editing our Game Mode C++ class
In the header file of your game mode, type in the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
private: /** Contains all the available transforms of LevelMovePointActors. */ TArray<FTransform> Transforms; /** Map name to spawn */ FString MapName; /** The desired transform of our map */ FTransform MapTransform; /** Spawns the desired map based on the MapName and the MapTransform */ UFUNCTION() void SpawnMap(); public: /** Moves or spawns a map in the BaseMap. * @param MapName - the name of the map we want to move * @param Position - the index of the Transforms array we want to use */ UFUNCTION(Exec, Category = LevelStreaming) void MoveMap(FString MapName, int32 Position); virtual void BeginPlay() override; |
In the source code, include the LevelMovePoint header file and then type in the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
void ACppStreamingLevelsGameMode::SpawnMap() { FLatentActionInfo info; //We load the stream level but we make sure not to make it visible before we assign a transform to it //The reason I've typed *MapName is the parameters is because the second parameters needs an FName so //essentially I'm type casting the FString to FName UGameplayStatics::LoadStreamLevel(GetWorld(), *MapName, false, false, info); ULevelStreaming* level = UGameplayStatics::GetStreamingLevel(GetWorld(), *MapName); //Assign a new transform to our level level->LevelTransform = MapTransform; //Make the level visible level->bShouldBeVisible = true; } void ACppStreamingLevelsGameMode::MoveMap(FString MapName, int32 Position) { if (Transforms.IsValidIndex(Position)) { ULevelStreaming* level = UGameplayStatics::GetStreamingLevel(GetWorld(), *MapName); //store the new map name and the new transform this->MapName = MapName; MapTransform = Transforms[Position]; if (level->IsLevelVisible()) { //If the level is visible we need to unload it first //and then spawn it in a new location //The unload stream level is essentially an async operation //Using the Latent Action Info we're able to create a callback //which is fired when the UnloadStreamLevel finishes its execution FLatentActionInfo info; info.CallbackTarget = this; info.ExecutionFunction = FName("SpawnMap"); info.UUID = 0; info.Linkage = 0; UGameplayStatics::UnloadStreamLevel(GetWorld(), *MapName, info); } //If the level is not visible just spawn the map else SpawnMap(); } } void ACppStreamingLevelsGameMode::BeginPlay() { Super::BeginPlay(); TArray<AActor*> OutActors; UGameplayStatics::GetAllActorsOfClass(GetWorld(), ALevelMovePoint::StaticClass(), OutActors); for (auto It : OutActors) { //Get the transform of each actor Transforms.Add(It->GetTransform()); } } |
Save and compile your code.
Before moving any further, open up the BaseMap and make sure that its your persistent map and the other maps are unloaded. To make sure that your map is the persistent one check the lower right corner of your viewport:
To make a map Persistent, from the Levels Panel right click on it and select the option “Make Current”.
When you’re done with that, create a Blueprint based on your LevelMovePoint actor and place some instances on your BaseMap. Moreover, override the game mode of the BaseMap to our C++ GameMode class:
To test your code, click on the play button and open up the console window (using the ~ key). Then, use the following command:
movemap “mapone” 2
The parameter in the “” is the string name of the map and the second one is the index of the transforms array. Note that if the given string doesn’t match any of yourr maps, the game will crash.
Hey Orfeas,
this is a great tutorial. And I could reproduce the code easily with some minor tweaks. However I have a question: Is it possible to load the same Map 2 times (with 2 different MoveActors)?
When I call the command MoveMap map1 0
and then use the command MoveMap map1 1 then the engine seems to reload map1 on the position of the actor who is stored at index 0. Any Ideas how to solf this problem?
Kind regards,
Lennart