Download

UWorld tick not call in PIE game

I have a code that works in a running Standalon game. But when the game is launched in PIE mode the tick created by UWorld is not called. It looks like for it to work in PIE, you need to create an FWorldContext with an EWorldType :: PIE argument for GEngine to start calling it. It seems in EditorEngine.cpp at line 1648 a check is being made for this.
But the problem is that I cannot create FWorldContext with EWorldType :: PIE type. This leads to editor crashes.
I need help to solve this.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyBlueprintFunctionLibrary.generated.h"



/**
 * 
 */
UCLASS()
class NAVIGATIONTEST_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
	
public:

	UFUNCTION(BlueprintCallable, meta = (WorldContextObject = "WorldContextObject"))
		static void CreateNewWorld(const UObject *WorldContextObject, const TSoftObjectPtr<UWorld> Level, FString LevelName, UWorld *&World);



	UFUNCTION(BlueprintCallable, meta = (WorldContextObject = "WorldContextObject"))
		static void CreateNewWorld2(const UObject *WorldContextObject, const TSoftObjectPtr<UWorld> Level, UWorld *&World);

	static FDelegateHandle id;
	static TArray<UWorld*> CreatedWorlds;

	UFUNCTION(BlueprintPure)
		static UWorld *GetMainWorld();
};


#include "MyBlueprintFunctionLibrary.h"

#include "Serialization/ObjectAndNameAsStringProxyArchive.h"

#include "GameFramework/GameModeBase.h"
#include "AI/AISystemBase.h"
#include "EngineUtils.h"
#include "Kismet/GameplayStatics.h"
#include "NavigationSystem.h"


UWorld * UMyBlueprintFunctionLibrary::GetMainWorld()
{
	return GEngine->GetCurrentPlayWorld();
}




FDelegateHandle UMyBlueprintFunctionLibrary::id;
TArray<UWorld*> UMyBlueprintFunctionLibrary::CreatedWorlds;


void UMyBlueprintFunctionLibrary::CreateNewWorld2(const UObject *WorldContextObject, const TSoftObjectPtr<UWorld> Level, UWorld *&World)
{

	UWorld *NewWorld = nullptr;

	const FString LevelName2 = FPackageName::ObjectPathToPackageName(Level.ToString());
	GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("level name = %s"), *LevelName2));

	UPackage *WorldPackage = FindPackage(nullptr, *LevelName2);
	if (WorldPackage == nullptr)
	{
		WorldPackage = LoadPackage(nullptr, *LevelName2, ELoadFlags::LOAD_None);
		if (!WorldPackage)
		{
			GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("Package not loaded")));
			return;
		}

	}
	

	UWorld *PackageWorld = UWorld::FindWorldInPackage(WorldPackage);



	UPackage* NewWorldPackage = CreatePackage(nullptr);
	//NewWorldPackage->SetPackageFlags(PKG_PlayInEditor);
	//NewWorldPackage->PIEInstanceID = PIEInstanceID;
	//NewWorldPackage->FileName = PackageFName;
	//NewWorldPackage->SetGuid(EditorLevelPackage->GetGuid());
	//NewWorldPackage->MarkAsFullyLoaded();


	FObjectDuplicationParameters Parameters(PackageWorld, NewWorldPackage);
	Parameters.DestName = PackageWorld->GetFName();
	Parameters.DestClass = PackageWorld->GetClass();
	Parameters.DuplicateMode = EDuplicateMode::PIE;
	Parameters.PortFlags = EPropertyPortFlags::PPF_Duplicate;

	NewWorld = CastChecked<UWorld>(StaticDuplicateObjectEx(Parameters));

	//NewWorld = UWorld::DuplicateWorldForPIE(LevelName2, PackageWorld);
	//if (!NewWorld)
	//{
	//	return;
	//}


	//const FString WorldNameString = "CreatedMap" + FString::FromInt(FMath::RandRange(0, 100));
	//NewWorld = UWorld::CreateWorld(EWorldType::PIE, true, FName(*WorldNameString));
	//NewWorld = UWorld::CreateWorld(EWorldType::Game, true, FName("CreatedMap"), WorldPackage);
	


	

	FWorldContext &WorldContext = GEngine->CreateNewWorldContext(EWorldType::Game);
	WorldContext.OwningGameInstance = UGameplayStatics::GetGameInstance(WorldContextObject);
	//WorldContext.PIEInstance = -1;
	NewWorld->SetGameInstance(UGameplayStatics::GetGameInstance(WorldContextObject));
	////NewWorld->WorldType = WorldContext.WorldType;
	NewWorld->WorldType = EWorldType::PIE;
	WorldContext.SetCurrentWorld(NewWorld);



	GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("CreateWorld: %s"), *NewWorld->GetFName().ToString()));

	//// In the PIE case the world will already have been initialized as part of CreatePIEWorldByDuplication
	if (!WorldContext.World()->bIsWorldInitialized)
	{
		WorldContext.World()->InitWorld();
	}



	//FURL URL;
	FURL URL(&WorldContext.LastURL, *NewWorld->GetFName().ToString(), ETravelType::TRAVEL_Absolute);
	WorldContext.World()->SetGameMode(URL);


	//WorldContext.World()->CreateAISystem();



	WorldContext.World()->InitializeActorsForPlay(URL);

	UNavigationSystemV1 *NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(NewWorld);
	if (NavSys)
	{
		ANavigationData *UseNavData = NavSys->GetDefaultNavDataInstance(FNavigationSystem::Create);
		NavSys->Build();
	}


	WorldContext.World()->BeginPlay();
	////WorldContext.World()->bWorldWasLoadedThisTick = true;

	// Tick call in PIE
	//FWorldDelegates::OnWorldTickStart.AddLambda([](UWorld* World, ELevelTick TickType, float DeltaSeconds) {
	//	if (World == GetMainWorld() && GetMainWorld()->WorldType == EWorldType::PIE) {
	//		for (auto WorldRef : CreatedWorlds) {
	//			WorldRef->Tick(TickType, DeltaSeconds);
	//			GEngine->AddOnScreenDebugMessage(-1, 0.0f, FColor::Yellow, FString::Printf(TEXT("TickInWorld: %s"), *WorldRef->GetFName().ToString()));
	//			UE_LOG(LogTemp, Warning, TEXT("TickInWorld: %s"), *WorldRef->GetFName().ToString());
	//		}
	//	}

	//});
	

	if (!id.IsValid()) {
	id = 	FWorldDelegates::OnPostWorldCleanup.AddLambda([](UWorld *World, bool bSessionEnded, bool bCleanupResources) {
			if (!CreatedWorlds.Contains(World)) {
				FWorldDelegates::OnPostWorldCleanup.Remove(id);
				id.Reset();

				for (auto WorldRef : CreatedWorlds) {
					auto Name = WorldRef->GetMapName();
					for (FActorIterator ActorIt(WorldRef); ActorIt; ++ActorIt)
					{
						ActorIt->RouteEndPlay(EEndPlayReason::LevelTransition);
					}

					// Do this after destroying pawns/playercontrollers, in case that spawns new things (e.g. dropped weapons)
					//WorldRef->CleanupWorld();

					//GEngine->WorldDestroyed(WorldRef);

				

					GEngine->DestroyWorldContext(WorldRef);
					WorldRef->DestroyWorld(true);

					GEngine->TrimMemory();
					//GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("Created World destroyed: %s"), *WorldRef->GetFName().ToString()));
					UE_LOG(LogTemp, Warning, TEXT("Created World destroyed: %s"), *Name);
				}
				CreatedWorlds.Empty();

				//GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("DestroyedWorld: %s"), *World->GetFName().ToString()));
				UE_LOG(LogTemp, Warning, TEXT("DestroyedWorld: %s"), *World->GetFName().ToString());
			}
		});
	}


	CreatedWorlds.Add(NewWorld);

	World = NewWorld;





}


void UMyBlueprintFunctionLibrary::CreateNewWorld(const UObject *WorldContextObject, const TSoftObjectPtr<UWorld> Level, FString LevelName, UWorld *&World)
{

	UWorld *NewWorld = nullptr;

	const FString LevelName2 = FPackageName::ObjectPathToPackageName(Level.ToString());
	GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("level name = %s"), *LevelName2));

	UPackage *WorldPackage = FindPackage(nullptr, *LevelName2);
	if (WorldPackage == nullptr)
	{
		WorldPackage = LoadPackage(nullptr, *LevelName2, ELoadFlags::LOAD_None);
		if (!WorldPackage)
		{
			GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("Package not loaded")));
			return;
		}

	}

	NewWorld = UWorld::FindWorldInPackage(WorldPackage);
	// Create new UWorld, ULevel and UModel.
	const FString WorldNameString = (!LevelName2.IsEmpty()) ? LevelName2 : TEXT("Untitled");
	//NewWorld = NewObject<UWorld>(WorldPackage, *WorldNameString);
	//NewWorld->SetFlags(RF_Transactional);
	//NewWorld->FeatureLevel = ERHIFeatureLevel::Num;


	//NewWorld = UWorld::CreateWorld(EWorldType::Game, true, FName("CreatedMap"), GetTransientPackage());
	//NewWorld = UWorld::CreateWorld(EWorldType::Game, true, FName("CreatedMap"), WorldPackage);
	GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("World %s loaded"), *NewWorld->GetFName().ToString()));




	FWorldContext &WorldContext = GEngine->CreateNewWorldContext(EWorldType::Game);
	NewWorld->SetGameInstance(UGameplayStatics::GetGameInstance(WorldContextObject));
	//NewWorld->SetGameInstance(WorldContext.OwningGameInstance);
	WorldContext.SetCurrentWorld(NewWorld);
	NewWorld->WorldType = WorldContext.WorldType;



	//// In the PIE case the world will already have been initialized as part of CreatePIEWorldByDuplication
	if (!WorldContext.World()->bIsWorldInitialized)
	{
		WorldContext.World()->InitWorld();
	}



	//FURL URL;
	FURL URL(&WorldContext.LastURL, *NewWorld->GetFName().ToString(), ETravelType::TRAVEL_Absolute);
	WorldContext.World()->SetGameMode(URL);



	WorldContext.World()->CreateAISystem();


	WorldContext.World()->InitializeActorsForPlay(URL);
	WorldContext.World()->BeginPlay();
	//WorldContext.World()->bWorldWasLoadedThisTick = true;








	FTimerDelegate TimerCallback;
	TimerCallback.BindLambda([WorldContext, NewWorld]
	{
		for (FActorIterator ActorIt(WorldContext.World()); ActorIt; ++ActorIt)
		{
			ActorIt->RouteEndPlay(EEndPlayReason::LevelTransition);
		}

		// Do this after destroying pawns/playercontrollers, in case that spawns new things (e.g. dropped weapons)
		WorldContext.World()->CleanupWorld();

		GEngine->WorldDestroyed(WorldContext.World());

		GEngine->DestroyWorldContext(NewWorld);
		NewWorld->DestroyWorld(true);

		GEngine->TrimMemory();
		GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("World unloaded")));
	});

	FTimerHandle Handle;
	WorldContextObject->GetWorld()->GetTimerManager().SetTimer(Handle, TimerCallback, 5.0f, false);

	World = NewWorld;





}

You might want to move the logic you currently have in UWorld into some other class, such as a game state. As far as I can tell, the editor can’t use a custom world, unless you re-build the entire editor to use your world class instead of the default one.

In the editor, this code calls the actors’ construction script and the logic on their BeginPlay functions. Timers and Delay also work. The only thing not to call tick on the generated UWorld is to call tick on the actors.

Yes, when I looked into something like this, the UWorld is special from the point of view of the editor.