I have a main menu that let access different levels (“maps”) by using OpenLevel:
UGameplayStatics::OpenLevel(this, levels[currentSelection], true);
The level loads without any issue, however when it starts it stutters noticeably. The slower the device the game is loaded from (ie. hdd vs sdd) the longer and noticeable the stutter. Which brings me to the conclusion you start playing while assets are still been loaded in.
Is there a way to detect when everything is loaded, any callback/delegate I can rely on?
I found an old post from 2015 simply saying “you can’t use openlevel for that, you have to switch to level streaming”. At the point I’m currently at though that would be a major undertaking and I’d love to keep my current approach… if there’s a way to find a solution to this issue, ofc.
Any help is GREATLY appreciated.
Have you tried replacing
UGameplayStatics::OpenLevel(this, levels[currentSelection], true); with
APlayerController::ClientTravel(levels[currentSelection], TRAVEL_Absolute, true);?
I think seamless probably is your best choice. The only issue with this approach is that the GameMode, PlayerState and PlayerController will move onto the next level. Not sure if this behaviour can be disabled (maybe by disabling bSeamlesTravel in the Main Menu’s GameMode).
If not, you could make another level that only contains the GameMode and stuff (very light level) and transition to this using the
OpenLevel method. This level then uses
ClientTravel to transition to the proper level.
Look into async loading.
The theory is, you load with async, get a message back when all is ready.
So you can have a Screensaver/loading screen visible and close it based on the callback.
If you delay after the message by about 20s you also allow the texture Mips to load…
The set up is much more complex to do it right, but this should get you stuff to look into…
Thanks @Dynamiquel and thanks @MostHost_LA,
I’m trying to minimize changes to the architecture of my loading system. Anything that doesn’t reset PlayerState etc. (especially async loading – which is really a last resort as I’m using AsyncLoadingScreen from Truong.Bui and that would imply rewriting everything) is really a last resort, as it would imply resetting a ton of things (position of AIs, inventory, etc) “manually”.
This said, I’ve tried ClientTravel and the hitch is still there (but it might make me gain a couple of seconds on the overall loading time).
After more research, looks like I might do better with how my assets are loaded: working with the AssetManager, defining proper Primary Data Assets, and using soft references where appropriate.
If a pass on that front won’t work, well then yes, I’ll have to refactor to a different (async) architecture.
Thanks again nonetheless!
Unless you’re explicitly using Asynchronous loading in your game, the engine will be using static loading by default for everything. This means that as your level gets larger, and your asset base gets larger, it will be loading absolutely everything as and when it is needed.
You typically want to run all of your static loading for things that you have a high likelihood of needing in the world in your loading screen. I believe you can store as soft references, and load them synchronously with a blueprint node, but could be wrong.
This will make sure that you are loading things into memory (which is causing your hitches) before you are putting the player in the level and allowing them to have a bad experience.
You’ll then want to asynchronously load things on demand while running the level. This means they’ll run on a separate thread and have a minimal impact on gameplay.
Beware!: One single static load called, either indirectly through a previously unloaded direct reference, or directly through a synchronous load call will flush every async load, causing a massive hitch.
You can use
UGameplayStatics::LoadStreamLevel, it will automatically async load the level.
But, when it is going to be shown, it could cause stutter due to constructing the actors. Because after the level is loaded, the engine will run for to all actors of the level and construct them.
To solve this, you need to fight the engine level streaming engine or modify the engine or put the construction to
BeginPlay and add some delay.
and again, thanks everybody for the answers. They convinced me to keep analyzing my issue, since, from what you wrote, it was just non-sense for OpenLevel to be the cause of my stutter: by the book, OpenLevel should indeed LOAD EVERYTHING before putting the player in the world. And indeed, that’s the case; on the opposite, I’m finding that using LoadStreamLevel, as @Hexavx was mentioning, it’s always causing stutters (wondering how much data can you actually stream in real time without stuttering… I even tried just showing a looping animation while I “stream” the level, and that too, it just stutters… heavily!).
So the stuttering wasn’t caused by Open Level. The stuttering was caused by this particle effect:
Having one of these in the level, no big deal, but once I started having more than a couple, et voila’ stutter stutter. The longer the more instances of the effect I had in my level. After fiddling with Niagara, I found this solution: enabling Warm Up, by setting to 5 seconds the “Warmup Time” in the Niagara System Properties:
So something completely unrelated to the loading. However, silver lining: this development hiccup brought me to study the loading system quite in depth, and I’ve discovered so much about stuff I was doing wrong. After “cleaning” my system, I reduced my initial loading times from more than one minute (1’13" to be exact) to 17 seconds overall. Win!
Ah yes - The wonders of death-by-particle-system