Wait until world partitioned level is loaded or opened

Is there a way to wait until WP level is opened or fully loaded?

I am trying to implement a simple save / load. I have a load logic to “open level → set player transform position”. But I can’t find solution to how I can wait until level is loaded

Currently what is happening: player is teleported to the desired location (set actor transform) and then level is loaded with player at default player position (load level)


up

There is a node called IsLevelLoaded, this can probably be used in your case. Check this: Is Level Loaded | Unreal Engine Documentation

Hi! This node is not relevant for World Partition cause its target is Level Streaming. When I am trying to use GetStreamingLevel node and pass my world name as an input - the returned value is always null

I have tried to do following in the event to check if the level was loaded:


What is actually happening that after OpenLevel node execution flow jumps to the other graph (I assume its due to timer), and the intended flow is broken (cause there is logic afterward the level is loaded) and highlighted delay node spins infinitely due to the failure in validate node

So now I basically have 2 problems to solve:

  1. How to execute logic after delay and do NOT interrupt blueprint execution flow (as now it skips everything after OpenLevel node and returns to the parent graph and continues logic there, which is not what I want)
  2. How to wait for World Partition level to be loaded and only than execute any logic

I still can’t find a way to load a game in a particular state, spawn player in a particular spot using World Partition

Please help :pleading_face:

Hi have you solved this issue?

No, this is sadly an open question. I guess the only way for now is to NOT to use world partition :frowning:

I looked into the source code for some delegates regarding world partition streaming and after some searching I found this blueprint callable function in the subsystem

I’m guessing it tests based on set coordinates and radius. With a small enough radius it probably targets the cell at that location.

Shame there is not delegate that you could hook into that would broadcast it’s change. Perhaps it would be an idea for a pull to the source?

From a c++ side you could override void UWorldPartitionSubsystem::UpdateStreamingState() as it’s virtual
This could be used to call on updated state. I would have to read up on extending sub systems with custom properties.

8 Likes

Hi
Quite late response, but I can show my solution that may benefit someone. Also probably not the best soultion but work well in my case.
I call OpenLevel() with some arguments that will be sent to that level, and then nothing more due to current level will end.


Then little bit of c++ that handle character connect in GameMode for that level.

Here the delegate blueprint event called from c++ to save some data.


In GameMode When OnPostLogin are called I can create and position the character.
image

The start position will be based on found portal actors location and if not found try look for PlayerStart actors and if that fails as well have at least a fallback location.
The Portal actor should be an invisible rectangle with collision in case streaming is not ready so character dont fall through.
Hide the process with loading screen.

if (TObjectPtr WorldPartitionSubsystem = GetWorld()->GetSubsystem())
{
bIsMapReady = WorldPartitionSubsystem->IsAllStreamingCompleted();
}

I doubt even that flag is reliable.

The only way to prevent issues is to hold the game until you get traces that return values you expect.
Something as simple as sweeping the player capsule up and down to make sure something like the landscape exists is generally enough to prevent major gamplay issues you would otherwise get when relying on streamed results.

I created a solution based on @MostHost_LA 's suggestion. I created a World Partition Level Load component you add to your player, which fires a load level event once all the given actors are found/hit.

You define N number of lines to do line traces. And define the actor names you need to hit before the level is loaded. If you run it with just the line(s) defined, then you can see all the hit actors’ names in the UE log, which you can use to set the properties. Once you have all your lines and names defined, when you run it it will fire off the loaded event (in C++ and blueprints) once all those actors are loaded.

[ WorldPartitionLoadLevel.h ]

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "WorldPartitionLoadLevelComponent.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FLoadLevelDelegate);

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SLASH_API UWorldPartitionLoadLevelComponent : public UActorComponent
{
	GENERATED_BODY()
public:	
	UWorldPartitionLoadLevelComponent();

	UPROPERTY(BlueprintAssignable, Category = "Level Loaded")
	FLoadLevelDelegate OnLevelLoadedDelegate;

	UFUNCTION()
	void LevelLoaded();
		
protected:
	virtual void BeginPlay() override;
	
	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Line Trace")
	TArray<FVector> Lines;
	
	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Line Trace")
	bool bLineTraceOn = true;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Line Trace")
	TArray<FString> CheckNames;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Debug")
	bool bShowLineDebug = true;

	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Debug")
	bool bShowDebugMessage = true;
	
private:
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
	void RunLineTrace();
	bool AllWereHit();
	void LineTrace(TArray<FHitResult>& LineHits, FVector EndLine);

	bool bStopTrace = false;

	UPROPERTY()
	TArray<AActor*> HitActors;

	UPROPERTY()
	TArray<FString> HitNames;
};

[ WorldPartitionLoadLevel.cpp ]

#include "Components/WorldPartitionLoadLevelComponent.h"
#include "Kismet/KismetSystemLibrary.h"

UWorldPartitionLoadLevelComponent::UWorldPartitionLoadLevelComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
}

void UWorldPartitionLoadLevelComponent::LevelLoaded()
{
	if (bShowDebugMessage) { UE_LOG(LogTemp, Warning, TEXT("World Partition Level Loaded :)")); }
}

void UWorldPartitionLoadLevelComponent::BeginPlay()
{
	Super::BeginPlay();
	OnLevelLoadedDelegate.AddDynamic(this, &UWorldPartitionLoadLevelComponent::LevelLoaded);
}

void UWorldPartitionLoadLevelComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
	RunLineTrace();
}

void UWorldPartitionLoadLevelComponent::RunLineTrace()
{
	if (!bStopTrace)
	{
		TArray<FHitResult> LineHits;
		for(FVector Line: Lines)
		{
			LineTrace(LineHits, Line);
		}
		if (AllWereHit())
		{
			bStopTrace = true;
			OnLevelLoadedDelegate.Broadcast();
		}
	}
}

bool UWorldPartitionLoadLevelComponent::AllWereHit()
{
	bool allHit = true;
	for(FString Name: CheckNames)
	{
		if (HitNames.Contains(Name))
		{
			if (bShowDebugMessage) { UE_LOG(LogTemp, Warning, TEXT("Matched Name: %s"), *Name); }
		}
		else
		{
			allHit = false;
			break;
		}
	}
	return allHit;
}

void UWorldPartitionLoadLevelComponent::LineTrace(TArray<FHitResult>& LineHits, FVector EndLine)
{
	AActor* Owner = GetOwner();
	TArray<AActor*> ActorsToIgnore;
	ActorsToIgnore.Add(Owner);
	FVector Start = Owner->GetActorLocation();
	FVector End = Start + EndLine;

	for(AActor* Actor: HitActors)
	{
		ActorsToIgnore.AddUnique(Actor);
	}
	
	UKismetSystemLibrary::LineTraceMulti(
		this,
		Start,
		End,
		TraceTypeQuery1,
		false,
		ActorsToIgnore,
		bShowLineDebug ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::None,
		LineHits,
		true
	);

	if (LineHits.Num() > 0)
	{
		for(FHitResult LineHit : LineHits)
		{
			AActor* LineHitActor = LineHit.GetActor();
			if (!HitActors.Contains(LineHitActor))
			{
				HitActors.AddUnique(LineHitActor);
				HitNames.AddUnique(*LineHit.GetActor()->GetName());
				if (bShowDebugMessage) { UE_LOG(LogTemp, Warning, TEXT("Hit Actor: %s"), *LineHit.GetActor()->GetName()); }
			}
		}
	}
}

Player Character Blueprint

1 Like