Reset level without reloading it?

Hi. I was wondering if there is an easy way to reset a level without reloading it. I know there is the AGameModeBase::ResetLevel method, but it seems I would then have to implement Reset() on all actors and track their initial states. I would like to avoid that at all costs. I also know I can do something like APlayerController::RestartLevel or this:

UGameplayStatics::OpenLevel(GetWorld(), FName(UGameplayStatics::GetCurrentLevelName(GetWorld())));

However, the problem with that method is in the packaged game, the textures seem to get unloaded when reloading the level and take a second to reload (for a second they are blurry). So, what I’m really asking is: “Is there a way to get the loaded assets to remain in memory when I reload the level”

I use Linux, and I can use C++ or Blueprints, whichever you like.

As far as I know, your only options are

  1. Use ResetLevel

  2. Record the changes in status of all actors during gameplay in the game instance, and re-load from that.

1 Like

After digging around in the source code for a while, I found a solution.

Here’s what I found:

  • The textures are being unloaded because they are being deleted by the garbage collector
  • The garbage collector is deleting the textures from memory because they are temporarily unreferenced.

So my solution is this (C++):

  • Put an array of UObjects in my Game Instance.
  • Before reloading the level, find all texture objects in memory and add their pointers to the array in the Game Instance.
  • After the map has been reloaded remove the references from the array.

**Keeping a reference to the textures in the GameInstance keeps them from being unloaded, so they do not have to be reloaded when the level is reloaded **

I found all textures in memory using this:

TArray<UObject*> Objects;
GetObjectsOfClass(UTexture2D::StaticClass(), Objects, true);

I hope this helps anyone who has this problem.

Update to this post after a few years for those that are looking into the same issue. I’ve added the following functions to our GameInstance to shorten loading times from 20 sec to 7 secs on the Switch.

Can also be used to keep a separate level loaded in memory if you know it’s going to be reused soon without using async level loading.

// Static member variable defined in header.
TMap<FName, TArray<TStrongObjectPtr<UObject>>> UMyGameInstance::_keptLevels;

void UMyGameInstance::KeepReferenceToLevel(const FName levelName)
{
	const auto* arr = _keptLevels.Find(levelName);
	if (arr && arr->Num() > 0)
	{
		DropReferenceToLevel(levelName);
	}
	else
	{
		auto& levelArray = _keptLevels.FindOrAdd(levelName);

		TArray<UObject*> outTextures;
		GetObjectsOfClass(UTexture2D::StaticClass(), outTextures);
		TArray<UObject*> outMeshes;
		GetObjectsOfClass(UStaticMesh::StaticClass(), outMeshes);
		TArray<UObject*> outSkeletalMeshes;
		GetObjectsOfClass(USkeletalMesh::StaticClass(), outSkeletalMeshes);
		TArray<UObject*> outAnimSequences;
		GetObjectsOfClass(UAnimSequence::StaticClass(), outAnimSequences);
		TArray<UObject*> outAnimMontages;
		GetObjectsOfClass(UAnimMontage::StaticClass(), outAnimMontages);
		TArray<UObject*> outAnimBp;
		GetObjectsOfClass(UAnimBlueprint::StaticClass(), outAnimBp);
		TArray<UObject*> outMats;
		GetObjectsOfClass(UMaterial::StaticClass(), outMats);
		TArray<UObject*> outBps;
		GetObjectsOfClass(UMaterial::StaticClass(), outBps);

		levelArray.Reserve(outTextures.Num() + outMeshes.Num() + outSkeletalMeshes.Num() + outAnimSequences.Num() + outAnimMontages.Num() + outAnimBp.Num() + outMats.Num() + outBps.Num());

		for (size_t i = 0; i < outTextures.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outTextures[i]));
		}
		for (size_t i = 0; i < outMeshes.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outMeshes[i]));
		}
		for (size_t i = 0; i < outSkeletalMeshes.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outSkeletalMeshes[i]));
		}
		for (size_t i = 0; i < outAnimSequences.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outAnimSequences[i]));
		}
		for (size_t i = 0; i < outAnimMontages.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outAnimMontages[i]));
		}
		for (size_t i = 0; i < outAnimBp.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outAnimBp[i]));
		}
		for (size_t i = 0; i < outMats.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outMats[i]));
		}
		for (size_t i = 0; i < outBps.Num(); i++)
		{
			levelArray.Add(TStrongObjectPtr<UObject>(outBps[i]));
		}
	}
}

void UMyGameInstance::DropReferenceToLevel(const FName levelName)
{
	const auto* arr = _keptLevels.Find(levelName);
	if (arr)
	{
		_keptLevels.Remove(levelName);
	}
}

Note that this approach has caused my editor to crash at exit though so do with it what you will.