TSoftObjectPtr points to actor within wrong UWorld when running PIE

Hello,

I’m trying to implement registry for subset of actors within my whole game (placed in different persistent levels, different streaming levels etc.). Hence I have TMap<FName, TSoftObjectPtr<AActor>> to manage these actors. There as a key I use custom unique identifier and TSoftObjectPtr<AActor>::Get() should return valid pointer to actor if it’s presented (loaded) or nullptr otherwise.

The problem is that when I’m running PIE mode this pointer sometimes returns reference to the actor within EWorldType::Editor world instead of running EWorldType::Game or EWorldType::PIE world. When running the game in standalone mode it’s working fine, just that I need it in PIE for level designers.

So the question is whether it’s possible to implement such behavior? Force TSoftObjectPtr to return actor within specified world or something like that? Or I’m just getting it all wrong? Is that intended behavior or there’s a bug?

More details about initialization process:

  1. When in editor I run custom build step to generate UDataTable that contains TSoftObjectPtr variables. To get reference value I’m doing something like that:

    AActor* Actor = GetActor(); // get valid actor reference
    TSoftObjectPtr ActorRef = Actor;

  2. When launching the game I’m building TMap<FName, TSoftObjectPtr<AActor>> from the generated data table.

Thanks!

If someone faced the same problem in the PIE - in the end I’ve just avoided the issue by the cost of iterating through all actors:

TMap<FName, TSoftObjectPtr<AActor>> ActorsContainer; // persistent container
AActor* ResultActor = ActorsContainer[ActorID].Get(); // reference must be presented but actor can be unloaded

#if WITH_EDITOR
	// Check world type of result actor
	if (ResultActor && EWorldType::Editor == ResultActor->GetWorld()->WorldType)
	{
		// Get actual world reference (from UGameInstance for example)
		UWorld* World = ...;

		for (TActorIterator<AActor> ActorItr(World); ActorItr; ++ActorItr)
		{
			AActor* Actor = *ActorItr;

			// Check if it's the same actor (in my case it has custom component and corresponding unique ID)
			if (...)
			{
				// Fix the reference
				ResultActor = Actor;
				// Fix container content for future requests
				ActorsContainer.Add(ActorID, Actor);
				break;
			}
		}
	}
#endif // WITH_EDITOR

I’m running into this problem as well, and am similarly confused as to whether this is by design or not.

My use case is checking the health state of an actor placed in a specific map from my game mode blueprint. In PIE, it returns the incorrect version of that actor resulting in wrong health values.

It seems like there should be a way to have an asset pointer return the version of that asset for the current world, but I can’t seem to find it.

Using an Actor Iterator is not a great solution, btw :stuck_out_tongue:

After some experimentation, I have found an acceptable solution. Because Asset/Soft Pointers are pretty much just strings, you can manipulate them to take the PIE world into account.

keep in mind that I’m on 4.16.1 so you may need to adjust the language for your engine version.

	// Attempts to resolve the supplied Asset pointer using the calling object as a world context.  
	UFUNCTION(BlueprintPure, Category = Utilities, meta = (DeterminesOutputType = "Asset", WorldContext= "WorldContext"))
	static UObject* ResolveAsset(const TAssetPtr<UObject>& Asset, UObject* WorldContext)
	{
		UObject* Obj = ((const FAssetPtr*)&Asset)->Get();
#if WITH_EDITOR
		if (Obj && 
			WorldContext)
		{
			const FString AssetClass = Obj->GetClass()->GetName();
			const FString AssetMapName = Obj->GetWorld()->GetMapName();
			const FString CurrentMapName = WorldContext->GetWorld()->GetMapName();

			// Split using the name of the map the asset is located in, not the current map. 
			FString AssetPath, RightString;
			Asset.ToStringReference().ToString().Split(AssetMapName, &AssetPath, &RightString, ESearchCase::CaseSensitive);

			// ClassName + Path + MapPackage + CurrentMapName + AssetName
			const FString FullAssetPathString = AssetClass + "'" + AssetPath + CurrentMapName + "." + Asset.GetAssetName() + "'";

			FStringAssetReference AssetString = FStringAssetReference(FullAssetPathString);
			Obj = FAssetPtr(AssetString).Get();
 		}
#endif
		return Obj;
	}

Hopefully this helps anyone else with this issue!