How would I get the closest world composition level from a given actor in the world?

Hey everyone!

I’m currently fiddling with a respawn system that requires me to place Graveyard actors at arbitrary locations around an open world utilizing the world composition. Now, this raises a couple of issues when it comes to level loading, but the primary thing is that sometimes the level doesn’t load fast enough for the player, and the player falls through the world. To solve this, I would like to get the closest streaming WC level from a given actor (the Graveyard actor, in this case) and preload it before teleporting the player.

Is this possible? I would prefer to do it in C++.

Hi,

When you do a teleport you should call UWorld::FlushLevelStreaming after that, it will block game thread until all streaming levels around current player location will be loaded. So player will not fall down.

Thank you for the answer! Unfortunately, I can’t get this to work. My code looks like this:

void ARPGGameMode::TeleportSafely(APlayerStart* PlayerStart, ACharacter* Character)
{
	if (PlayerStart && Character)
	{
		FVector teleportLocation = PlayerStart->GetActorLocation();
		FRotator teleportRotation = PlayerStart->GetActorRotation();

		//teleport the player
		Character->SetActorLocationAndRotation(teleportLocation, teleportRotation, false, NULL);

		//block game thread until all levels have loaded in
		GetWorld()->FlushLevelStreaming();
	}	
}

Hmm, it could be that player viewpoint which is used as streaming location is not immediately updated when you call Character->SetActorLocationAndRotation. Could you set a breakpoint inside UWorldComposition::UpdateStreamingState before you enter into FlushLevelStreaming() call. Inside UpdateStreamingState it calculates streaming location, it should be close to teleportLocation value.

Oh, ignore that you should be calling GEngine->BlockTillLevelStreamingCompleted instead of FlushLevelStreaming, like it’s done in AGameMode::HandleMatchHasStarted.

I’ve changed the code to this now:

void ARPGGameMode::TeleportSafely(APlayerStart* PlayerStart, ACharacter* Character)
{
	if (PlayerStart && Character)
	{
		FVector teleportLocation = PlayerStart->GetActorLocation();
		FRotator teleportRotation = PlayerStart->GetActorRotation();

		//teleport the player
		Character->SetActorLocationAndRotation(teleportLocation, teleportRotation, false, NULL);
		GEngine->BlockTillLevelStreamingCompleted(GetWorld());
	}	
}

I’m afraid that it still doesn’t work. Could it be that GetWorld() isn’t returning a proper pointer to the World Composition world?

GetWorld is correct here, problem could be with a player viewpoint as I said earlier. So you will need to debug it in UWorldComposition::UpdateStreamingState which will be called by GEngine->BlockTillLevelStreamingCompleted

Okay, I’ve debugged it. These are the values I got from it:

In TeleportSafely()
teleportLocation = {X=811502.625 Y=337410.969 Z=66414.0625 }

In UpdateStreamingState()
First Hit (Similar to the original location of the character): InLocation = {X=47325.2109 Y=44431.3047 Z=1379.52795 }
Second Hit (Value appears correct): InLocation = {X=811470.938 Y=337458.375 Z=66477.0625 }
Third Hit (Screen is beginning to update): InLocation = {X=0.963172913 Y=0.365150452 Z=66398.6641 }
Fourth Hit(Screen has updated, character has "arrived": InLocation = {X=0.963172913 Y=0.365150452 Z=66163.4609 }

InLocation stays virtually the same from this point on, save for Z which is decreasing as we fall.

It does get the correct value once on the second hit, but then it shifts to a near-0,0,0 value. I would assume that this has to do with a world origin rebasing which happens right after the character arrives at the targetLocation - I threw a print on the origin changed event, and it fires right as I arrive.

yeah, looks like pawn camera view is not updated immediately on Character->SetActorLocationAndRotation call, so GEngine->BlockTillLevelStreamingCompleted uses old location for streaming.

You could use pawn location instead of camera view for world composition streaming, so you will need to change code in UWorldComposition::UpdateStreamingState that call GetPlayerViewPoint to GetActorLocation.

Or even better to override APlayerController::GetPlayerViewPoint function in your game specific controller class to detect teleports and return actor location in this case instead of camera location, so you will not have to change engine code.

That sounds like it could work! I’m not too comfortable with engine edits :stuck_out_tongue:

I’ve overridden GetPlayerViewPoint like this in my controller:

void ARPGPlayerController::GetPlayerViewPoint(FVector& out_Location, FRotator& out_Rotation) const
{
	//since UpdateStreamingState doesn't immediately update, we have to manually tell the controller that we are indeed teleporting
	if (bIsTeleporting)
	{
		
		out_Location = GetPawn()->GetActorLocation();
		out_Rotation = GetPawn()->GetActorRotation();
	}
	else
	{
		Super::GetPlayerViewPoint(out_Location, out_Rotation);
	}
}

And the teleport function now looks like this:

void ARPGGameMode::TeleportSafely(APlayerStart* PlayerStart, ACharacter* Character)
{
	if (PlayerStart && Character)
	{
		FVector teleportLocation = PlayerStart->GetActorLocation();
		FRotator teleportRotation = PlayerStart->GetActorRotation();

		//teleport the player
		AWarcraftPlayerController* Controller = Cast<ARPGPlayerController>(Character->GetController());
		if (Controller)
		{
			Controller->bIsTeleporting = true;

			Character->SetActorLocationAndRotation(teleportLocation, teleportRotation, false, NULL);
			GEngine->BlockTillLevelStreamingCompleted(GetWorld());

			Controller->bIsTeleporting = false;
		}				
	}	
}

I am, however, crashing the entire editor now when I try to teleport :stuck_out_tongue: I’ll get back to you once I have some more usable information.

The crash is stemming from an access violation in FLatentActionManager::ProcessLatentActions, where ObjectsToRemove just says “Unable to read memory”.

I’m still having trouble with this. Do you have any additional thoughts on how I could solve either this crash, or block the thread another way?

Hard to say why it’s crashing there, you will need to build engine in “Debug” configuration and go through that code in the debugger.

Eech. Okay, I’ll give it a shot.

Has the BlockTillLevelStreamingCompleted’s inability to work properly with world origin shifting been entered as a bug report, by the way?

I know this is more than a year old, but I was facing a similar problem and managed to make this work by forcing the player controller to update the viewport by calling

PlayerController->PlayerCameraManager->UpdateCamera(DeltaTime);

after SetActorLocationAndRotation, and before GEngine->BlockTillLevelStreamingCompleted.

Also, for my problem I only needed to force maps to begin loading so all I needed was GetWorld()->WorldComposition->UpdateStreamingState() as opposed to the blocking version.

Is it possible to do this in blueprints?