Preventing player from falling through newly streamed levels

Our entire game world is stitched together using world composition, which works great, but fast travel is presenting a bit of a problem: when you instantly move from one location to another, there’s a split second where nothing is streamed in around you. It’s easy to visually hide it with a loading screen or quick fade out/in, but until the floor loads there is nothing preventing the player from falling through it and ending up out of bounds. The only way I’ve thought of to prevent this would be to spawn a collision volume at the destination, spawn the player on top of it, then delete the volume after a 0.1 second (or whatever) delay. However, that’s an incredibly clunky way to solve a minor problem.

Is there a cleaner solution I’m unaware of, or is the volume thing really the simplest option out there?

Disable gravity; stream; enable gravity.

Durr, I feel dumb for never even considering disabling gravity… there’s no delegate I can subscribe to in order to make world streaming manually notify me when a chunk loads in though, right? I should just literally do nograv-teleport-yesgrav and trust streaming to load in a floor between the last two calls?

You have two conditions to check to know if a level is loaded “isLoaded” and “isVisible” (from the “getStreamingLevel” node).

What I did for my game was to branch a AND with these two booleans aforementionned plugged in and if the branch returns false, I wait 0.x seconds and run the branch again. That returns true when the level is loaded and I plug the logic that is supposed to launch when the level is ready at that moment.

You also have the “blockOnLoad” bool that you can turn true or false to have a freeze when you level stream in order to wait for the loading. I needed to add my branch system to that to properly have my systems working though.

Oh, the delay is a very smart idea, thank you for clarifying! :slight_smile:

I suggest using set active and disable/enable input instead, because by disabling gravity if it takes a while to load the character will float in the air:

2 Likes

I was hitting simplar problem of the player always falling through the terrain for a streamed level.
In the level bluprint for the persistant map, I was spawnwning my player character. The problem is, even in play in editor, the player is falling through the terrain when it is spawned.

Its taken a little while for me to work out a solution to this, but now I think I have one that seems to work.

First, my default pawn class is SpectatorPawn
My map has Enable World Composition checked, along with Enable World Origin Rebasing.
Now in my GameInstance blueprint, I have added 1 custom event called PlayerEnterWorld, and a function called CheckLevelVisibility. The PlayerEnterWorld is called by the level blueprint of the persistent level.
In the PlayerEnterWorld event, I teleport the spectator pawn (already possessed by the player controller) to the location where I want to spawn my player character.

I then use SetTimerByFunctionName to create a repeating timer for 0.5 time and the function i’m using is CheckLevelVisibility, so this function can be called repeatedly on a 0.5 second interval.
In the CheckLevelVisibility, I am checking to see if the name of the sub level where my character is going to be is loaded, and if it is, Clear my timer, and then spawn my player character.


Now when ever I click play, with this approach, I can be sure that the sub-level has been streamed in before spawning.

I have the following solution: I created a custom c++ blueprint function library, where I added the following function:


void UMyFunctionLibrary::FlushWorldComposition(const UObject * WorldContextObject)
{
    UWorld* World = WorldContextObject->GetWorld();

    // Force a camera update manually just in case the player has been moved and the camera did not yet update it's position
    APlayerCameraManager* CameraManager = UGameplayStatics::GetPlayerCameraManager(WorldContextObject, 0);
    CameraManager->UpdateCamera(0.1f);

    // And then block the game thread until all subsequent levels are loaded. This will also bring up a loading screen by default
    // if not overridden by GEngine->BeginStreamingPauseDelegate and GEngine->EndStreamingPauseDelegate.
    GEngine->BlockTillLevelStreamingCompleted(World);
}

You can call this after loading your primary world, or, immediatly after teleporting the player character while already being in that persistent level. There is, however, one point to note: You should NOT call this function from your level blueprint immediatly in the BeginPlay node, because this will load the subsequent levels while your persistent level is still not flagged as “bBegunPlay”. This will lead to all actors in your sublevels not getting BeginPlay called and not working properly (also they don’t tick etc.). To work around this, I added a custom game instance class and have overridden the LoadComplete method, which seems to only be called for persistent levels:


void UMyGameInstance::LoadComplete(const float LoadTime, const FString & MapName)
{
    Super::LoadComplete(LoadTime, MapName);

    // Flush world composition on level loading to prevent the player from falling though the ground, and also to prevent
    // sublevels from popping up around the player.
    UMyFunctionLibrary::FlushWorldComposition(this);
}

And there you go: Hope it helps.

Works great! and +1 for a C++ solution!

Ok, I forgot to include the headers but I can’t find the header for “FlushWorldComposition”

your .h file should already have the needed headers if you created the custom cpp class from within the engine. Simply append the code above rather then replace the whole file and it should do.

Ok, I successfully built the two classes and now have a custom Game Instance called “MyGameInstance” and set it as my game instance in project settings.
Then I also have a node called “Flush World Composition” with a “world context object” pin, and… I’m lost.
Shoud I use this node somewhere while you said “You should NOT call this function from your level blueprint immediatly in the BeginPlay node”?
Shoud the function be fired by the “UMyFunctionLibrary::FlushWorldComposition(this);” of the game instance class?
What is supposed to be connected to the pin “world context object”? the level file of the persistant level?

With the node connected to the beginplay event of the persistent level blueprint and the level file of the persistent level connected to the pin, it does nothing.
Same a “self” node connected to the pin.
Same without using the node.

The code above literally just gets the “world” from the world context pin object, the only not commented portion.

UWorld* World = WorldContextObject->GetWorld();

// Force a camera update manually just in case the player has been moved and the camera did not yet update it’s position
APlayerCameraManager* CameraManager = UGameplayStatics::GetPlayerCameraManager(WorldContextObject, 0);
CameraManager->UpdateCamera(0.1f);

// And then block the game thread until all subsequent levels are loaded. This will also bring up a loading screen by default

// if not overridden by GEngine>BeginStreamingPauseDelegate and GEngine>EndStreamingPauseDelegate.

GEngine->BlockTillLevelStreamingCompleted(World);

So the function it is using is literally just this
https://docs.unrealengine.com/en-US/…ted/index.html

And you are effectively exposing it to blueprint by implementing the code above - with the addition of that camera lock.

Given that, you may need to have

#include “Engine/Engine.h”
And
#include “Engine/World.h”

In your header file for it to use the existing unreal engine function. Maybe that’s all that was missing though I would expect it not to have compiled unless you already added that in.

Oh, as far as the pin and what to connect. Any instance of Actor or variation thereof used to have an associated “getworld()” function. So you could technically call this on the begin okay of the character maybe? Its worth a test anyway…

I had #include “Engine/Engine.h” but not #include “Engine/World.h”
Anyway, adding it doesn’t help.
I’m using the created “flush world composition” node connected to the BeginPlay event of level blueprint of the persistent level, with the persistent level file selected as “world context object” and from what I understand, the game thread should freeze until all level streaming is done but it does nothing. The game start in an empty world and sublevels are loaded after that, as usual.
Don’t know what I’m doing wrong as **RoLYroLLs**said it works

PS: The only place I can use this node is the persistent level blueprint. If I try to use it in other blueprints and set the “world context object” to any level file, I can’t save it because “graph is linked to object in external map”

I was having issues with character going through terrain, streaming just 4 tiles (atm), now I fixed by going back to auto streaming, although I’m on terrain on play , two different sectins of terrain about equa-distant, yet on left to unique terrain area , I can see all in distanc, to right again diffrent terrain area,terrain is I guess, not streame in, yet ojbects floating in mid-air,what a bummer!!

All are set to Uncategorized , so how do I resolve this…hate streaming;)

Hello there , i’m trying to implement your solution in my project but i’m trying to figure how you got the sub level name in the first place?

1 Like

Is there a way to remove the loading screen?
I actualy have my own loading screen with animated pictures, game tips, etc. but this “black screen + 3 animated dots” loading screen is drawn on top of my own

Hi UltimateLegend
You can do a MultiLineTraceByChannel from your character’s feet down to get a list of actors beneath the player. Then Cast to LandscapeStreamingProxy and promote the result of the cast to a variable call it something like Landscape, then on the Landscape variable call GetPathName. This returns the path of the landscape. If you examine the path, you want the last part of the path after the ‘:’ as that would be your sublevel.

1 Like

I fixed it by just putting a plane in the persistent level below the Player Start. I set the plane to no gravity, but enabled physics and I set the texture to transparent.

Hi there, this is an old question but since someone can still need the answer, here it is:
you will first need to implement a slate widget for your loading screen in c++, I recommend you to either implement or take a look into this plugin: GitHub - ue4plugins/LoadingScreen: A plugin for Unreal Engine 4 to expose simple controls for managing load screens.
After that you will need to bind to GEngine->BeginStreamingPauseDelegate and GEngine->EndStreamingPauseDelegate, you may do that by calling GEngine->RegisterBeginStreamingPauseRenderingDelegate and GEngine->RegisterEndStreamingPauseRenderingDelegate.

I’m afraid this is the only way to swap that annoying primitive loading screen.