How can I execute code immediately after loading a non-streaming level?

I’m working on Save/Load game code, and to load the level, I use GameplayStatics::LoadLevel(), and then set actor positions around the level to where they were saved at.

The problem is, LoadLevel() seems to only set some flags, and then the level is actually loaded on the next tick. There isn’t any callback functions for when it actually is loaded.

I might be able to do this by using streaming levels, but there appears to be no way to open a level that wasn’t already put into the persistent world by the editor. Plus, I’m using world composition as well, which may complicate that idea.

How can I get some code to run immediately after loading another level, without having to put a myriad of checks and flags in each level?

1 Like

Some code, in case the above wasn’t clear enough:
NameOf() is a helper function returning an FString,
LoadedLevelNames[] and SavedPawnList[] are TArrays loaded from SaveGame:

void UMySaveGame::LoadLevels(UWorld* TheWorld)
{
	UE_LOG(GameInfo, Log, TEXT("Loading..."));

	//load level
	//UGameplayStatics::LoadGameFromSlot();
	for (int i = 0; i < LoadedLevelNames.Num(); ++i)
	{
		UE_LOG(GameInfo, Log, TEXT("Level: %s"), *LoadedLevelNames[i]);
		UGameplayStatics::OpenLevel(TheWorld, FName(*LoadedLevelNames[i]),false);
	}

	//loop through spawn things
	TArray<AActor*> actorlist = TheWorld->GetCurrentLevel()->Actors;
	for (int i = 0; i < actorlist.Num(); ++i)
	{
		APawn* ThePawn = Cast<APawn>(actorlist[i]);
		if (ThePawn != nullptr)
		{
			UE_LOG(GameInfo, Log, TEXT("Loading pawn #%i. name: %s"), i, *NameOf(ThePawn));
			UE_LOG(GameInfo, Log, TEXT("Saved pawn list has #%i entries."), SavedPawnList.Num());
			for (int j = 0; j < SavedPawnList.Num(); ++j)
			{
				FSavedPawn sp = SavedPawnList[j];
				UE_LOG(GameInfo, Log, TEXT("Checking against saved pawn with name: "), i, *NameOf(sp));
				if (NameOf(ThePawn).Equals(NameOf(sp)))
				{
					UE_LOG(GameInfo, Log, TEXT("Pawn #%i, %s was found and will be loaded."), i, *NameOf(ThePawn));
					ThePawn->SetActorLocationAndRotation(sp.Position, sp.Rotation, false);

					//quick remove of saved pawn from list, so it will not turn up in future searches. Safely reorder because we're returning afterwards.
					SavedPawnList.RemoveAtSwap(j, 1);
					return;
				}
			}
		}
	}

	//execute BP
	Load_Event();
}

Did you tried to using default UE4 save system?

If you mean SaveGames, then yes, that’s what’s in use here. The problem is, the actor positions are (trying to be) set in this load code, but then the level is loaded on the next tick, which effectively makes the load code useless.

I need to run the loading code -after- the level has loaded, and I’m not sure how to do that.

So far, I’ve been looking into UE4’s level and world delegates, but with no success. It seems the delegates are members of a UWorld, and the UWorld is destroyed on level load.
I’ve tried adding:

	TheWorld->OnLevelsChanged().AddUObject(this, &UMySaveGame::PostLevelLoad);

and:

	FCoreUObjectDelegates::PostLoadMap.AddUObject(this, &UMySaveGame::PostLevelLoad);

but PostLevelLoad() is never a part of either delegates’ invocation lists when the level changes and the delegates are called.

My solution was to set a static flag, and since my game has only one game mode, I can override its GenericPlayerInitialization(AController* C) function to run my game load code after the level changes.

This is a terrible, terrible hack and still gets called before a pawn spawns from a “player start” placed in the level. Hopefully Epic can add a delegate somewhere that gets called after the level is fully loaded.

There are even a few of these delegates in code, but none are actually called, and are pretty misleading.

Old post, I know, but I’ve been working on this exact issue the last few days.

My solution was to create a custom ALevelScriptActor for any level I want to be saveable. Override BeginPlay, then after Super::BeginPlay(), put your loading code.

Still feels very hacky, but the player controller & pawn are spawned, would work across multiple game modes etc.

Resurrecting this post with another solution since Google brought me to this topic.

All you need to do is override GameInstance::LoadComplete(const float, const FString&). This one will get called reliably since GameInstance persists over level loads.

Personally I think this is a clean solution. Tested in UE4.22

1 Like

FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UMySubsystem::OnPostLoadMap);

2 Likes

This callback save my life, thank you dawnarc!