Hello, I’ve been developing a random dungeon generator using level instances, but I’ve come across a problem. After loading a level instance in and rotating or moving it, it does not work consistently.
Below is what it is supposed to look like once the generation is complete.
In this picture, I have printed the position of where the level instance should be and where it is based on the level instance’s translation to the screen. Obviously, they should be the same.
Even though the calculations come out the same, their positions are still at (0,0,0). My question is why is this occurring and can anyone help with solving this?
These screenshots were all taken in Standalone mode as well because I read that there were issues with PIE and level translating. Levels are loaded in using ULevelStreamingDynamic::LoadLevelInstanceBySoftObjectPtr() and are moved or rotated by accessing the ULevelStreamingDynamic’s LevelTransform and calling SetLocation() and LevelTransform.SetRotation(). If anyone needs anymore information please ask! Thank you.
void ADungeonManager::GenerateRooms(int32 SelectedSeed)
{
// Reset RoomsLoaded to check later
RoomsLoaded = 0;
// Get Data Table that holds rooms information
UDataTable* RoomsTable = Cast<UMainGameInstance>(GetWorld()->GetGameInstance())->RoomsTable;
// Get the names of the Data Tables
TArray<FName> RoomsNames = RoomsTable->GetRowNames();
FRoomInformation* RoomInformation = RoomsTable->FindRow<FRoomInformation>(RoomsNames[0], "");
ULevelStreamingDynamic* RoomLevel;
bool bIsLevelLoadSucessful;
// Create a number of rooms equal to RoomCount
for (uint32 Index = 0; Index < RoomCount; Index++)
{
// Load the level instance
RoomLevel = ULevelStreamingDynamic::LoadLevelInstanceBySoftObjectPtr(GetWorld(), RoomInformation->Level, FTransform(FRotator(0,0,0), FVector(0,0,0), FVector(1,1,1)), bIsLevelLoadSucessful);
// Once LevelStreaming loads properly
if (ensure(bIsLevelLoadSucessful and RoomLevel))
{
// Notify when Level loads properly
RoomLevel->OnLevelLoaded.AddUniqueDynamic(this, &ADungeonManager::HandleRoomLoaded);
// Add to arrays to save for later
StreamedLevels.Emplace(RoomLevel);
}
}
}
void ADungeonManager::HandleRoomLoaded()
{
// A Level has loaded
RoomsLoaded++;
// Did all rooms finish loading?
if (RoomsLoaded == RoomCount)
{
// Get all jigsaws
PlaceRooms();
}
}
void ADungeonManager::PlaceRooms()
{
// Place rooms 1000 units away from each other
for (int Index = 0; Index < StreamedLevels.Num(); Index++)
{
StreamedLevels[Index]->LevelTransform = FTransform(FRotator(0.0f, 0.0f, 0.0f), FVector((Index + 1) * 1000, 0.0f, 0.0f), FVector(1, 1, 1));
}
}
Hopefully this works for everyone else having this problem. Instead of directly changing the Transform, I used the FLevelUtils::ApplyLevelTransform to apply the transform and called GetWorld()->UpdateLevelStreaming().
#include "LevelUtils.h"
void ADungeonManager::PlaceRooms()
{
// Place rooms 1000 units away from each other
for (int Index = 0; Index < StreamedLevels.Num(); Index++)
{
FLevelUtils::ApplyLevelTransform(StreamedLevels[Index]->GetLoadedLevel(), FTransform(FRotator(0.0f, 0.0f, 0.0f), FVector((Index + 1) * 1000, 0.0f, 0.0f), FVector(1, 1, 1)));
GetWorld()->UpdateLevelStreaming();
}
}
Levels do make some assumptions to make them work efficiently.
However, I think using a “level” for each room seems like abusing the system. A Level is generally supposed to be a much, much, larger thing.
For dungeon rooms and corridors, you can totally just make them all be actors or actor groups that you add to the level.
(There’s a procedural level generation plugin on the marketplace that lets you build up these groups and place them according to rules, which might be worth it if you’re not just doing this for the fun of doing it )
Yeah, that makes sense. Doing this as actors would have made this much easier, too. Maybe I’ll redo these as actors at some point. I am just doing this for fun, but thank you for the plugin recommendation anyways!