[UE5.4] How do levels work?

Can someone explain how levels work in Unreal Engine? I’ve looked at countless docs and tutorials and not a single one of them explains how to load a level. Sure, they explain sub levels. They explain how to replace a level. But not how to just load a new level and not unload common assets.

I want to do something EXTREMELY simple. I want common code for my UI and some other assets and I want to be able to swap levels. That’s it. But it’s apparently 100% IMPOSSIBLE to do in Unreal Engine. The lack of support here is unbelievable.

If I load a new level, my common level that sets up my UI is gone. And never mind that there’s no way to do this asynchronously. Ok, so I try streaming levels (ie. sublevels). Well, each of my levels is on a different planet with vastly different lighting. The editor complains I have multiple sky lights and conflicting directional lights. Just because I have multiple sub levels with different lighting.

Ok, I look up lighting scenarios. I put the lighting in those. No dice. Editor still complains.

Why is this so difficult? I just want to be able to switch to different levels without losing a common set of actors and code for my UI and gameplay.

How do I switch levels without having common assets disappear on me?

You know what the funniest part is? You run code in a BP to load another level. But you can’t call or do anything else because the current BP is gone. Like WTF?

From what you’re describing, it sound like you want level streaming.

With streaming, you can have one main level ( or even empty level ) permanently loaded and then ‘stream’ other levels in. It’s the persistent level.

If you load all your common code in the persistent ( also possibly your Game Instance ).

The main ways of streaming are using overlap volumes, or using blueprint.

Another thing you can do is use Level Instances

You can load an instance with ‘load level instance’.

You can only have one lighting setup loaded at once, of course. So, you can either unload the previous level before loading, or have your only setup in the persistent, and change it when you change levels.

2 Likes

Open Level will trash EVERYTHING, it just replaces the current level with the new one.

I’m extremely familiar with level streaming. I already mentioned that level streaming doesn’t work with lighting. It doesn’t work with navigation either. It doesn’t work with a few other things as well. Same for level instancing.

So I still have no solution.

edit:

You can only have one lighting setup loaded at once, of course. So, you can either unload the previous level before loading, or have your only setup in the persistent, and change it when you change levels.

You cannot do this. You cannot load another lighting setup with level streaming. You can’t even use lighting scenarios. Every doc I’ve read says it’s supposed to work with level streaming, but it doesn’t. It gives an error that multiple lights and atmospheres are in the level. Which there technically is (though in separate lighting scenarios)… but it makes light scenario levels obsolete and useless since the editor won’t let you have more than one.

But even if it did, it wouldn’t solve my issues with navigation and a few other things. I could potentially have the navigation bounds so large that it encompasses every level and set it to dynamic so that it reprocesses the navigation when a sublevel loads. But again, why is this so ■■■■ hard to do. It seems like a simple concept to have a menu and assets that doesn’t just go away and be able to load new levels. Seems odd to me that this is even a discussion.

edit2:

Here’s the log for just one lighting setup. Only ONE SkyLight is enabled, but I have multiple light scenarios as sub levels. All are turned off except for one.

MapCheck: Error: SkyLight_0 Multiple sky lights are active, only one can be enabled per world. 
MapCheck: Error: SkyAtmosphere_0 Multiple sky atmosphere are active, only one can be enabled per world. 

But If I turn off all light scenarios, no errors. I turn ONE of them on and I get the error above. There is only ONE sky light per lighting scenario. Same for SkyAtmosphere.

I don’t even think light scenarios are the way to go. That’s to bake static lighting into all other sub level and I don’t want to do that. But I get the same error even if lighting is in a normal sub-level.

Also, navigation will NEVER go into a sub level. Even if you can get it into a sub-level, it will be ignored.

I think the reason lighting scenarios don’t work is because I’m not using static lighting.

I’m now thinking that if the lighting assets can be serialized, I could just create an editor tool that would create a binary file with each level’s lighting properties and then load them at runtime. I’ll just have to figure out how to package binary files and open them in a packaged build.

I really shouldn’t have to do this though. I’m baffled as to how simple level handling is still a black art in 2024.

About " multiple skylights ". This error appear when several levels opened in editor simultaneously. On top menu bar find " levels ’ in drop down menu. Open levels list and hide other levels. So Unnecessary light sources will disappear.

Keep in mind that all levels exist in same start point. They do not disturb each other, but if you try build, when they not hidden, it will crazy. So you can move them around, but it will hard to render scene for PC ( possible freeze ).

What about UI loses ( like Health, Experience and etc options ). When you start level all variables of your Character cleared. To keep them, use Game Instance. Game Instance wide theme. Also use Event Dispatchers.

Yes, Unreal Engine is crazy Unreal machine :laughing: . Not logical, insidious, unpredictable. That’s all about this thing.

If you want to use it, you have to come to terms with it and be ready for anything. Save copies of files frequently.

Quoting myself:

“I turn ONE of them on and I get the error above.”

So only ONE sub level is active. Only one Sky Light is active. But I still get the error. All other sublevels are hidden and there’s absolutely nothing in the persistent level.

The problem with GameInstance is that even if you use that, once a level is unloaded, all the UI blueprints disappear with it. Well, it’s hard to debug because you can’t set a breakpoint in a BP that’s in a different level. But it crashes. So I’m assuming the BP’s are gone.

I have two possible alternatives. The first is the one I mentioned above where I serialize each level’s lighting info and load it manually.

The second alternative is to see if I can manually load a BP as a primary asset and have it outlive a OpenLevel call. I could put my UI and gameplay stuff in there. I think I can use the asset manager to asynchronously load levels as well, but not sure on this.

I’m currently trying out this strategy.

  1. Have all levels be full levels (no sub levels. That means no level streaming).
  2. Each level can now have its own lighting setup.
  3. I converted a main actor that had a lot of game logic into a UObject and made it a primary asset.
  4. This primary asset is loaded and maintained by the game instance.
  5. I have preview platforms and cameras for the UI. I’m going to try to put these into level instances and manually load them in. I’m scared that the cameras will cause problems, but we’ll see.
  6. Manually update all references to the assets in each level every time a level is loaded (like the spawner actor, the preview assets, the lighting, etc.)

The irony of all this is how many nodes and methods need a UWorld pointer, yet you can’t have more than one world. Even with streaming levels, they still use the persistent UWorld.

I really wish Unreal Engine had better support for levels. It’s 2024. It shouldn’t be this difficult for something that all classic games need and use.

1 Like

Something like:

  • when designing, for each of your planet levels, separate the lighting and game objects into different sub-levels
  • when running the game, stream in only the object sub-level for the new planet, without the lighting
  • put some type of “lighting info” in your object levels, which saves the lighting settings for the planet, so the existing skylights and stuff can be changed appropriately

This might enable you to interpolate the lighting from one level to another? Kinda like how World of Warcraft or other games do when you seamlessly walk between map areas, and you get a smooth lighting transition.

But yeah, the default situation for the engine is entirely based on all players being in the same level with no streaming. If you do level streaming extra work will be involved. Is your game multiplayer? Same thing applies. There is no support for players being in different physical levels in the same multiplayer game/server.

Yeah, that was one of my options. FYI, my levels don’t need to transition. They’re literally old school levels. When you’re done one level, you go back to the main screen and then you pick what level to play next. There are no areas or anything. Think of Super Mario 1 but you go back to the main screen after each level where you can pick the next level to play. It’s single player as well.

My main problem right now is that the original level doesn’t fully unload. Also, if you have level instances set to be loaded always, they remain loaded even if you use OpenLevel. I’m really confused as to what stays in memory and what doesn’t. Also, my player character is stuck in the old level and the viewport is still using the old level. I have no idea how to move either one to the newly loaded level. But it could just be that I have to have a delay for everything to update. Dunno.

There is world->DestroyLevel(bool, NewLevel) but it doesn’t seem to do anything. Docs say it’s used to destroy the old world once the new one is loaded. So it seems you can have multiple levels loaded at once. I just can’t seem to figure out how to do it correctly.

I’m getting lost now… :slight_smile:

If it’s one level at a time, why all this hassle? You can just close the last one, and open the next one, no problems with navigation or lighting.

2 Likes

In that case just: open main menu >> open game level >> open main menu when done with game, repeat.

Everything but your GameInstance is destroyed when you open a new level. Keeps things pretty simple. Highly advise making a entirely different GameMode, PlayerController, HUD, etc. for Main Menu mode.

A level like in Super Mario. You play one level and when you’re done, it’s completely gone. Why is it Unreal Engine cannot handle classic levels?

How? OpenLevel literally doesn’t work. The old level is still partly there. My player character is still in the old level. DestroyWorld does absolutely nothing. OnActorsInitialized and OnWorldBeginPlay never trigger. So I have to wait to continue setup after a level is loaded, but I have no idea how long. And with the player character stuck in the old level, all user input is ignored.

Why is this so difficult to do?

Without using BeginPlay (which is broken in level BP since forever), how do you know when it’s done loading? I have scores, dynamic enemies, custom pathfinding, timers, etc. I have setup to do. When do I do that? Why is the player character still in the old level?

And just for clarity, I have a LOT of assets that are common to each level. It’s kind of a pain to duplicate them every time. I had all of this in a common set of actors.

For example, you can preview assets in game in a UI widget. I have a capture camera that grabs the image realtime. It lets me animate the image. This is the same setup in all levels. So level streaming really seemed like the way to go at first. It worked really well. Until I tried to set up navigation and lighting. Then all hell broke loose.

So why isn’t it just load level, load UI, load level… because of all the common stuff. I’ve now started to separate them. All the UI logic, I’ve taken out of the actors and put it into a UObject. I didn’t want to clutter the GameInstance until I knew it was working. The GameInstance does maintain it and allows access to it. That part is actually working well.

As for the preview stuff, I’ve put those into a level instance. That seems to work well, but when you call OpenLevel on a new level, the old level instances still remain and I get duplicate copies of the level instance. I thought OpenLevel got rid of the old level, but apparently not.

OpenLevel is what I’m struggling with. It’s doing nothing of what the documentation says. It doesn’t fully unload the old level. It doesn’t respawn my player character. It doesn’t get rid of old level instances. And it never finishes initializing the actors in the level. Probably why it never triggers OnWorldBeginPlay or OnActorsInitialized. The level remains uninitialized and unusable. The level is stripped down and is fine. It works in the level streaming version of the project (without lighting).

So I have no idea what is going on or how any of this is supposed to work.

If you’re using ‘open level’ there is no streaming, or level subtlety, you just get the level you asked for.

How can you possibly have bits of the previous level hanging around?!

I really can’t figure that out… :joy:

None of this is needed with ‘open level’.

Is it possible you’ve jumbled up more than one level technology here? Although, I don’t see how, ‘open level’ just nukes everything else…

I can guarantee that OpenLevel doesn’t nuke LevelInstances that were there before. They remain. I’m getting duplicates and the outer is clearly the old level (along with the new one where the outer is the new level).

Right, so you’re mixing methods.

If you want to use open level, then no instances or streaming, just use open level.

( Even then, I don’t get it. I think you can have a very elaborate streaming and instancing setup, and ‘open level’ will put a stop to that ).

Is this blueprint or CPP? Maybe open level is different in CPP, but I find that a bit strange… :melting_face:

Actually, maybe it’s the level instance in the main UI level. I’ll convert it to regular actors and see if that makes a difference.

I’m in C++.

UGameplayStatics::OpenLevelBySoftObjectPtr(world, level);

Not sure why OpenLevel needs a world argument. That seems strange to me. The BP node has a world context input as well, but it’s hidden most of the time and automatically uses the world from the object calling it. So this should be identical.

My problem is how to know when it done. You can’t get a handle to the world to bind events to until after the level is loaded (unlike streaming where it lets you provide a delegate).

I tried using the AssetManager and it has no problem providing the level before calling OpenLevel. I bind my delegates, but nothing gets called. And I know for a fact that the level is never initialized (the actors are still uninitialized). I’ll have to double check the one in the AssetManager is the same as was opened and not a copy (CDO type deal). Or my UObject with the callback got GCd, but fairly certain that didn’t happen.

GEngine->GetWorldContextFromGameViewport(GEngine->GameViewport)

This always returns the main level even after OpenLevel is called.

1 Like

I found this in the GameInstance base class.

virtual void LoadComplete(const float LoadTime, const FString& MapName) override;
virtual void OnWorldChanged(UWorld* OldWorld, UWorld* NewWorld) override;

Gonna refactor some stuff and try this out. LoadComplete does get called. So I’ve made a bit of progress.

1 Like