[UE 5.1.1] TSoftObjectPtr Always Null

I’m trying to set up a way to travel between levels without using hard-coded string references to the maps. I’m using a custom GameInstance to control travel:

.h

    // ...
	UPROPERTY(EditAnywhere, Category = Levels, meta = (AllowPrivateAccess = "true"))
	TSoftObjectPtr<UWorld> MainMenuMap;
	UPROPERTY(EditAnywhere, Category = Levels, meta = (AllowPrivateAccess = "true"))
	TSoftObjectPtr<UWorld> GameplayMap;

    //...

	// Start Menu Interface Implementation
	UFUNCTION(exec)
	virtual void StartGame() override;
	UFUNCTION(exec)
	virtual void QuitToMainMenu() override;

    //...

.cpp

void UProjectCalmGameInstance::StartGame()
{
    CHECK_SOFTPTR_RET(GameplayMap, LogLevel, "PCGameInstance:: GameplayMap is NULL!");

    APlayerController* PlayerController = GetFirstLocalPlayerController();
    CHECK_NULLPTR_RET(PlayerController, LogPlayerController, "PCGameInstance:: PlayerController is NULL!");

    PlayerController->ClientTravel(GameplayMap->URL.ToString(), ETravelType::TRAVEL_Absolute);
}

void UProjectCalmGameInstance::QuitToMainMenu()
{
    CHECK_SOFTPTR_RET(MainMenuMap, LogLevel, "PCGameInstance:: MainMenuMap is NULL!");

    APlayerController* PlayerController = GetFirstLocalPlayerController();
    CHECK_NULLPTR_RET(PlayerController, LogPlayerController, "PCGameInstance:: PlayerController is NULL!");

    PlayerController->ClientTravel(MainMenuMap->URL.ToString(), ETravelType::TRAVEL_Absolute);
}

I’ve created a blueprint child of my GameInstance and assigned the appropriate levels to the TSoftObjectPointer properties. I then set the blueprint as the game instance class in the project settings. However, whenever StartGame or QuitToMainMenu are called, I always get an error saying the map pointer is null. I’ve tried checking both SoftPtr == nullptr and SoftPtr.Get() == nullptr and they are both always true. Am I using the TSoftObjectPtr incorrectly?

SoftObjectPtr reference objects that might or might not be loaded. So in your case they are probably not loaded at that point. You can load them async e.g. with a streamableManager or syncronous by calling myPtr.LoadSynchronous()

Okay, that stopped the pointers from being null, but it seems to have created some really weird issues. Whenever it called GameplayMap.LoadSynchronous, I would see a warning in the log that it failed to load Game/FirstPerson/Maps/FirstPersonMap, which is not the map I had assigned to the pointer. That seems to have been a bug with the engine. The map I was trying to load had originally been the FirstPersonMap, but I had renamed and moved it. I created a new map called GameplayMap and just copied the contents over. However, it still isn’t loading the correct maps. I added a log to both StartGame and QuitToMainMenu to print the URL they are trying to travel to. Here is the new code:

void UProjectCalmGameInstance::StartGame()
{
    GameplayMap.LoadSynchronous();
    CHECK_SOFTPTR_RET(GameplayMap, LogLevel, "PCGameInstance:: GameplayMap is NULL!");

    APlayerController* PlayerController = GetFirstLocalPlayerController();
    CHECK_NULLPTR_RET(PlayerController, LogPlayerController, "PCGameInstance:: PlayerController is NULL!");

    UE_LOG(LogTemp, Warning, TEXT("PCGameInstance::StartGame::Traveling to %s"), *(GameplayMap->URL.ToString()));

    PlayerController->ClientTravel(GameplayMap->URL.ToString(), ETravelType::TRAVEL_Absolute);
}

void UProjectCalmGameInstance::QuitToMainMenu()
{
    MainMenuMap.LoadSynchronous();
    CHECK_SOFTPTR_RET(MainMenuMap, LogLevel, "PCGameInstance:: MainMenuMap is NULL!");

    APlayerController* PlayerController = GetFirstLocalPlayerController();
    CHECK_NULLPTR_RET(PlayerController, LogPlayerController, "PCGameInstance:: PlayerController is NULL!");

    UE_LOG(LogTemp, Warning, TEXT("PCGameInstance::QuitToMainMenu::Traveling to %s"), *(MainMenuMap->URL.ToString()));

    PlayerController->ClientTravel(MainMenuMap->URL.ToString(), ETravelType::TRAVEL_Absolute);
}

When I call QuitToMainMenu, it attempts to travel to “/Game/ProjectCalm/Maps/UEDPIE_0_MainMenuMap”. I don’t know where the UEDPIE_0_ prefix is coming from. Even more strange, when I call StartGame (which should be traveling to the new map I just created), it tries to travel to “/Game/ProjectCalm/Maps/MainMenuMap”! You can see here I have the pointers assigned correctly in the game instance blueprint:

I’m completely baffled as to what is happening here.

The map it tries to travel to also changes depending on what map I start PIE from. If I start playing from MainMenuMap, I get the results above, but if I start playing from GameplayMap, I get “QuitToMainMenu::Traveling to /Game/ProjectCalm/Maps/DevEnvironment” and “StartGame::Traveling to /Game/ProjectCalm/Maps/UEDPIE_0_GameplayMap” instead!

When moving/ renaming assets unreal can create redirectors. Basically empty assets that point to the actual now moved asset. When right clicking “Fix up Redirectors” will delete the old assets and also fix the other assets that had the original asset referenced. You can also see them by enabling a filter Miscellaneous > Redirector.

I always used UGameplayStatics::OpenLevelBySoftObjectPtr to change the level. Im not 100% shure what the ClientTravel method does but it claims “Travel to a different map or IP address” … sounds a bit wierd to be using that to travel into the main menu.

That works! (At least when playing as a standalone game. In PIE, that “UED_PIE_0_” prefix gets tacked on to the map names and messes it up, so I can travel to a given map once, but I can’t travel back to one I’ve already been to.) Thanks so much!

As for ClientTravel, under the hood, the actual travel is performed the same way for Absolute travel as it is in UGameplayStatics::OpenLevelBySoftObjectPtr. They both use GEngine->SetClientTravel. The ClientTravel method on the PlayerController just has some extra checks/steps because it can be used in multiplayer games by the client to disconnect from a server and travel to a local map (such as the main menu) or by the server to tell the client to travel to a different map on the server.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.