跨 Level 选择 Actor

在场景中,美术和游戏逻辑是区分开的,所以有时候需要程序关卡去操控美术关卡的对象,但是 UE 的不同关卡其实是不同的资源,属于不同的 Pacakge,是不能直接跨关卡来选择对象实例的。
选择时会有以下错误:

1
LogProperty: Warning: Illegal TEXT reference to a private object in external package (StaticMeshActor /Game/Test/Map/Level_Sub2.Level_Sub2:PersistentLevel.Cube_2) from referencer (BP_AActor_C /Game/Test/Map/Level_Sub1.Level_Sub1:PersistentLevel.BP_AActor_2).  Import failed...

这是因为在 PropertyBaseObject.cppFObjectPropertyBase::ImportText_Internal中对 Object 属性是否可以跨关卡做了检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UObject* FObjectPropertyBase::FindImportedObject(const FProperty* Property, UObject* OwnerObject, UClass* ObjectClass, UClass* RequiredMetaClass, const TCHAR* Text, uint32 PortFlags/*=0*/, FUObjectSerializeContext* InSerializeContext /*= nullptr*/, bool bAllowAnyPackage /*= true*/)
{
// ...
// if we found an object, and we have a parent, make sure we are in the same package if the found object is private, unless it's a cross level property
if (Result && !Result->HasAnyFlags(RF_Public) && OwnerObject && Result->GetOutermost() != OwnerObject->GetOutermost())
{
const FObjectPropertyBase* ObjectProperty = CastField<const FObjectPropertyBase>(Property);
if (!ObjectProperty || !ObjectProperty->AllowCrossLevel())
{
UE_LOG(LogProperty, Warning, TEXT("Illegal TEXT reference to a private object in external package (%s) from referencer (%s). Import failed..."), *Result->GetFullName(), *OwnerObject->GetFullName());
Result = nullptr;
}
}
// ...
}

其中 AllowCrossLevel 有两个继承类有覆写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Runtime/CoreUObject/Private/UObject/PropertyBaseObject.cpp
bool FObjectPropertyBase::AllowCrossLevel() const
{
return false;
}
// Runtime/CoreUObject/Private/UObject/PropertyLazyObjectPtr.cpp
bool FLazyObjectProperty::AllowCrossLevel() const
{
return true;
}
// Runtime/CoreUObject/Private/UObject/PropertySoftObjectPtr.cpp
bool FSoftObjectProperty::AllowCrossLevel() const
{
return true;
}

所以,不能够直接通过创建 FObjectPropertyBase 这种硬引用方式的属性从 SubLevel1 选择 SubLevel2 中的 Actor。
那么如何解决这么问题呢?,上面已经列出了两个可以跨平台选择的属性,分别是 FLazyObjectPropertyFSoftObjectProperty,那么以 FSoftObjectProperty 为例,可以通过 TSoftObjectPtr 来实现:

1
TSoftObjectPtr<AActor> Actor;

TSoftObjectPtr获取到的其实是 SubLevel2 中的资源的路径:

1
/Game/Test/Map/Level_Sub2.Level_Sub2:PersistentLevel.Cube_2

在运行时访问需要使用以下操作来获取:

上面蓝图中节点 Load Asset BlockingUKismetSystemLibrary中的函数:

1
2
3
4
5
// Runtime/Engine/Private/KismetSystemLibrary.cpp
UObject* UKismetSystemLibrary::LoadAsset_Blocking(TSoftObjectPtr<UObject> Asset)
{
return Asset.LoadSynchronous();
}

看来 UE 加载资源时,并没有区分真正的物理资源和场景中的实例,统一使用资源的路径来加载,这一点做的非常爽,可以把另一个关卡中的 Actor 当作资源来读取,并且获取的还就是运行时的那个实例,非常 Nice。