強引用(hard reference)
如果A強引用B,那么在加載A時會把B也加載進內存
c++強引用資源
在AMyTest1GameMode的構造函數中加載Blueprint UClass,並賦值給DefaultPawnClass成員變量
那么AMyTest1GameMode類型就強引用着/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter的Blueprint UClass
AMyTest1GameMode::AMyTest1GameMode() { // set default pawn class to our Blueprinted character static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter")); if (PlayerPawnBPClass.Class != NULL) { DefaultPawnClass = PlayerPawnBPClass.Class; } }
注1:由於使用的是字符串,因此在構建版本時,並不會cook並將/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter的Blueprint UClass打進安裝包
注2:為了讓該資源打進安裝包,需要讓被cook的地圖直接或間接引用該Blueprint UClass;或者在DefaultGame.ini文件的/Script/UnrealEd.ProjectPackagingSettings標簽的DirectoriesToAlwaysCook數組中配置包含該資源的目錄
[/Script/UnrealEd.ProjectPackagingSettings] +DirectoriesToAlwaysCook=(Path="/Game/ThirdPersonCPP/Blueprints")
資源強引用資源
下圖中從MyTest1Character派生的Blueprint中的USkeletalMeshComponent* Mesh組件的USkeletalMesh* SkeletalMesh變量就強引用着SK_Mannequin的Skeletal Mesh資源
UCLASS(hidecategories=Object, config=Engine, editinlinenew, abstract) class ENGINE_API USkinnedMeshComponent : public UMeshComponent { GENERATED_UCLASS_BODY() // ... ... /** The skeletal mesh used by this component. */ UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Mesh") class USkeletalMesh* SkeletalMesh; // ... ... };


注1:由於USkeletalMesh* SkeletalMesh被標為EditAnywhere,因此ThirdPersonCharacter的Blueprint放置到游戲關卡中后,還可以對該Blueprint實例的USkeletalMeshComponent* Mesh組件的USkeletalMesh* SkeletalMesh指向的資源進行再次修改
注2:若被修飾為UPROPERTY(EditDefaultsOnly),就只能在Blueprint中進行修改

軟引用(soft reference)
為了控制何時加載資源,可使用TSoftObjectPtr(或TSoftClassPtr)模板類型來軟引用資源(或UClass類型)。
軟引用的工作方式與硬引用一樣,可直接關聯對應的資源(或UClass類型),在編輯器中界面表現是一樣,可通過拖拽、下拉框或箭頭選擇要關聯的資源。
在編輯器中,僅僅可通過彈出的Tips來區分
另外,被軟引用到的資源會被編輯器中的Reference Viewer(引用查看器)工具識別,也會自動cook進安裝包
與指定字符串路徑相比,當在編輯器中對被軟引用到的資源進行移動、改名等操作時,ue4會進行自動調整。
UCLASS(config=Game) class AMyTest1Character : public ACharacter { GENERATED_BODY() // ... ... public: UPROPERTY(Category = MyTest1, EditAnywhere) TSoftObjectPtr<UTexture2D> SourceTexture1; // 軟引用 UPROPERTY(Category = MyTest1, EditAnywhere) UTexture2D* SourceTexture2; // 硬引用 };

在Referece Viewer中,粉色的線為弱引用,白色的線為強引用

UTexture2D* AMyTest1Character::GetLazyTexture2D() { if (!SourceTexture1.IsPending()) // 檢查資源是否已准備好 { return SourceTexture1.Get(); } return SourceTexture1.LoadSynchronous(); // 手動來同步加載貼圖資源 注:同步加載可能會導致游戲線程卡頓,開發者需要評估該行為是否合理 } UTexture2D* AMyTest1Character::GetLazyTexture2D_2() { if (!SourceTexture1.IsPending()) // 檢查資源是否已准備好 { return SourceTexture1.Get(); } const FSoftObjectPath& SourceTextureRef = SourceTexture1.ToSoftObjectPath(); return Cast<UTexture2D>(UAssetManager::GetStreamableManager().LoadSynchronous(SourceTextureRef)); // 手動來同步加載貼圖資源 注:同步加載可能會導致游戲線程卡頓,開發者需要評估該行為是否合理 } UTexture2D* AMyTest1Character::GetLazyTexture2D_3() { if (!SourceTexture1.IsPending()) // 檢查資源是否已准備好 { return SourceTexture1.Get(); } const FSoftObjectPath& SourceTextureRef = SourceTexture1.ToSoftObjectPath(); UAssetManager::GetStreamableManager().RequestSyncLoad(SourceTextureRef); // 手動來同步加載貼圖資源 注:同步加載可能會導致游戲線程卡頓,開發者需要評估該行為是否合理 return FindObject<UTexture2D>(nullptr, *SourceTexture1.ToString()); }
如果希望延遲加載UClass,可使用TSoftClassPtr模版類來引用對應的UClass
UCLASS(config=Game) class AMyTest1Character : public ACharacter { GENERATED_BODY() // ... ... public: UPROPERTY(Category = MyTest1, EditAnywhere) TSoftClassPtr<UMyBPObject> MyObjectClass; // 軟引用 };

UClass* AMyTest1Character::GetLazyBPObjectClass() { if (MyObjectClass.IsValid()) // 檢查類型是否已准備好 { return MyObjectClass.Get(); } return MyObjectClass.LoadSynchronous(); // 手動來同步加載藍圖Class 注:同步加載可能會導致游戲線程卡頓,開發者需要評估該行為是否合理 } UClass* AMyTest1Character::GetLazyBPObjectClass_2() { if (MyObjectClass.IsValid()) // 檢查類型是否已准備好 { return MyObjectClass.Get(); } const FSoftObjectPath& AssetRef = MyObjectClass.ToSoftObjectPath(); return Cast<UClass>(UAssetManager::GetStreamableManager().LoadSynchronous(AssetRef)); // 手動來同步加載藍圖Class 注:同步加載可能會導致游戲線程卡頓,開發者需要評估該行為是否合理 } UClass* AMyTest1Character::GetLazyBPObjectClass_3() { if (MyObjectClass.IsValid()) // 檢查類型是否已准備好 { return MyObjectClass.Get(); } const FSoftObjectPath& AssetRef = MyObjectClass.ToSoftObjectPath(); UAssetManager::GetStreamableManager().RequestSyncLoad(AssetRef); // 手動來同步加載藍圖Class 注:同步加載可能會導致游戲線程卡頓,開發者需要評估該行為是否合理 return FindObject<UClass>(nullptr, *MyObjectClass.ToString()); }
使用FSoftClassPath、FSoftObjectPath來軟引用UClass和資源
UCLASS(config=Game) class AMyTest1Character : public ACharacter { GENERATED_BODY() // ... ... public: UPROPERTY(Category = MyTest1, EditAnywhere) FSoftClassPath SourceClass1; UPROPERTY(Category = MyTest1, EditAnywhere) FSoftObjectPath SourceObject1; };

注1:FSoftClassPath只能軟引用Class、DynamicClass、BlueprintGeneratedClass、WidgetBlueprintGeneratedClass、AnimBluprintGeneratedClass和LinkerPlaceholderClass六大類。這些類的繼承關系如下:
Class (632)
DynamicClass (760)
LinkerPlaceholderClass (1072)
BlueprintGeneratedClass (1512)
WidgetBlueprintGeneratedClass (1640)
AnimBlueprintGeneratedClass (2632)
注2:FSoftObjectPath可以軟引用任何資源和UClass

注1:黃色的TPersistentObjectPtr、TSoftObjectPtr、TSoftClassPtr為模板類
注2:FSoftObjectPath類中FThreadSafeCounter CurrentTag、TSet<FName> PIEPackageNames(黃色)是static的
它們之間相互轉換關系如下:
// === 定義FSoftObjectPath對象 === FSoftObjectPath ObjectPath1(TEXT("Material'/Engine/EngineDebugMaterials/VertexColorMaterial.VertexColorMaterial'")); FSoftObjectPath ObjectPath2; ObjectPath2.SetPath(TEXT("/Engine/EngineMaterials/DefaultDiffuse_TC_Masks.DefaultDiffuse_TC_Masks")); FSoftObjectPath ObjectPath3(UMyObject::StaticClass()->GetDefaultObject()); // ObjectPath3為:/Script/MyTest1.Default__MyObject // FSoftObjectPtr對象與FSoftObjectPath對象相互轉換 FSoftObjectPtr FObjectPtr1(ObjectPath2); const FSoftObjectPath& ObjectPath4 = FObjectPtr1.ToSoftObjectPath(); FSoftObjectPtr FObjectPtr2(UMyObject::StaticClass()->GetDefaultObject()); // FObjectPtr2為:/Script/MyTest1.Default__MyObject const FSoftObjectPath& ObjectPath5 = FObjectPtr2.ToSoftObjectPath(); // === 定義TSoftObjectPtr<T>對象 === TSoftObjectPtr<UMyObject> TObjectPtr1(UMyObject::StaticClass()->GetDefaultObject()); // TObjectPtr1為:/Script/MyTest1.Default__MyObject // TSoftObjectPtr<T>對象與FSoftObjectPath對象相互轉換 FSoftObjectPath ObjectPath6 = FSoftObjectPath("/Game/ThirdPerson/Meshes/Bump_StaticMesh.Bump_StaticMesh"); TSoftObjectPtr<UStaticMesh> TObjectPtr2(ObjectPath6); const FSoftObjectPath& ObjectPath7 = TObjectPtr2.ToSoftObjectPath(); // === 定義FSoftClassPath對象 === FSoftClassPath ClassPath1 = FSoftClassPath(TEXT("/Script/MyTest1.MyObject")); FSoftClassPath ClassPath2 = FSoftClassPath(UMyObject::StaticClass()); // ClassPath2為:/Script/MyTest1.MyObject FSoftClassPath ClassPath3; ClassPath3.SetPath(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter.ThirdPersonCharacter_C")); // === 定義TSoftClassPtr<T>對象 === TSoftClassPtr<UMyObject> TClassPtr1(UMyObject::StaticClass()); // TClassPtr1為:/Script/MyTest1.MyObject // TSoftClassPtr<T>對象與FSoftObjectPath對象相互轉換 FSoftObjectPath ObjectPath8(TEXT("/Game/ThirdPersonCPP/Blueprints/MyBPObject.MyBPObject_C")); TSoftClassPtr<UMyBPObject> TClassPtr2(ObjectPath8); const FSoftObjectPath& ObjectPath9 = TClassPtr2.ToSoftObjectPath();
字符串路徑
資源(或UClass)通過字符串路徑來指定,通過LoadObject(LoadClass)來加載。
該方式不能被Reference Viewer工具識別,資源進行移動、改名等操作時,不會自動調整。也不能自動cook進安裝包。
加載資源
// 加載UI藍圖資源 注:不是UI藍圖Class FString MyWidgetBPPath = TEXT("/Game/ThirdPerson/UMG/MyWidgetBlueprint"); UObject* MyWidgetBPObject = LoadObject<UObject>(nullptr, *MyWidgetBPPath); // MyWidgetBPObject的類型為UWidgetBlueprint* // 加載Texture2D資源 FString TexturePath1 = TEXT("/Engine/EngineMaterials/DefaultDiffuse_TC_Masks"); UTexture2D* TextureObj1 = FindObject<UTexture2D>(nullptr, *TexturePath1); // 若之前沒有加載,TextureObj1則返回為空 TextureObj1 = LoadObject<UTexture2D>(nullptr, *TexturePath1); // 在LoadObject內部在加載前也會調用FindObject先進行查找,如果不存在,才會從硬盤加載它 // 加載材質資源 FString MaterialPath1 = TEXT("/AnimationSharing/AnimSharingRed.AnimSharingRed"); UMaterialInterface* MaterialObj1 = LoadObject<UMaterialInterface>(nullptr, *MaterialPath1); // 加載StaticMesh資源 FString StaticMeshPath1 = TEXT("/Game/ThirdPerson/Meshes/Bump_StaticMesh.Bump_StaticMesh"); UStaticMesh* MyStaticMesh = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *StaticMeshPath1));
如果在UObject類型的構造函數中,可以使用ConstructorHelpers::FObjectFinder來加載資源
static ConstructorHelpers::FObjectFinder<UStaticMesh> MyStaticMeshClass(TEXT("/Game/ThirdPerson/Meshes/Bump_StaticMesh")); UStaticMesh* MyStaticMesh = MyStaticMeshClass.Object;
加載UClass(類型)
// Native UClass FString MyObjectPath = TEXT("/Script/MyTest1.MyObject"); UClass* MyObjectHit = FindObject<UClass>(ANY_PACKAGE, *MyObjectPath); // 在游戲啟動初始化時就已創建,因此可以直接Find到 UClass* MyObjectClass = LoadClass<UMyObject>(nullptr, *MyObjectPath); // MyObjectClass為UClass*類型 與MyObjectHit相等 //UClass* MyObjectClass = StaticLoadClass(UMyObject::StaticClass(), nullptr, *MyObjectClassPath); // 與上面語句等價 // UObjcet Blueprint UClass FString MyBPObjectPath = TEXT("/Game/ThirdPersonCPP/Blueprints/MyBlueprintObject.MyBlueprintObject_C"); UClass* MyBPObjectHit = FindObject<UClass>(ANY_PACKAGE, *MyBPObjectPath); UClass* MyBPObjectClass = LoadClass<UObject>(nullptr, *MyBPObjectPath); // MyBPObjectClass為UBlueprintGeneratedClass*類型 //UClass* MyBPObjectClass = StaticLoadClass(UObject::StaticClass(), nullptr, *MyBPObjectPath); // 與上面語句等價 // AActor Blueprint UClass FString PlayerPawnBPPath = TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter.ThirdPersonCharacter_C"); UClass* PlayerPawnBPClass = StaticLoadClass(APawn::StaticClass(), nullptr, *PlayerPawnBPPath); // PlayerPawnBPClass為UBlueprintGeneratedClass*類型 // 也可使用LoadClass來加載 如:UClass* PlayerPawnBPClass = LoadClass<APawn>(nullptr, *PlayerPawnBPPath); // 也可使用LoadObject來加載 如:UClass* PlayerPawnBPClass = LoadObject<UClass>(nullptr, *PlayerPawnBPPath); // 可以通過FindObject<UClass>函數來查找PlayerPawnBPPath對應的藍圖Class是否已經加載 // 另外,StaticLoadClass、LoadClass、LoadObject函數在加載前也會調用FindObject<UClass>來查找,如果不存在,才會從硬盤加載它 // UClass* PlayerPawnBPClass = FindObject<UClass>(nullptr, *PlayerPawnBPPath); // UUserWidget Blueprint UClass FString MyWidgetBPPath = TEXT("/Game/ThirdPerson/UMG/MyWidgetBlueprint.MyWidgetBlueprint_C"); UClass* MyWidgetBPClass = StaticLoadClass(UObject::StaticClass(), nullptr, *MyWidgetBPPath); // MyWidgetBPClass為UWidgetBlueprintGeneratedClass*類型 // 也可使用LoadClass來加載 如:UClass* MyWidgetBPClass = LoadClass<UObject>(nullptr, *MyWidgetBPPath); // 也可使用LoadObject來加載 如:UClass* MyWidgetBPClass = LoadObject<UClass>(nullptr, *MyWidgetBPPath); UUserWidget* DefaultMyWidgetBPObject = MyWidgetBPClass->GetDefaultObject<UUserWidget>(); UUserWidget* MyWidget = UWidgetBlueprintLibrary::Create(this, MyWidgetBPClass, nullptr); // 基於MyWidgetBPClass類型創建一個UserWidget實例
注1:Native UClass的字符串路徑為/Script/【模塊名】.【類型名】] // 類型名要去掉U、A等前綴
注2:Blueprint UClass的字符串路徑為/【Engine | Game | 插件名】/<XXX>/[名稱].[名稱] _C
如果在UObject類型的構造函數中,可以使用ConstructorHelpers::FClassFinder來加載UClass(類型)
static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPersonCPP/Blueprints/ThirdPersonCharacter")); UClass* PawnClass = PlayerPawnBPClass.Class; static ConstructorHelpers::FClassFinder<UObject> MyBPObjectClass(TEXT("/Game/ThirdPersonCPP/Blueprints/MyBlueprintObject")); UClass* MyClass = MyBPObjectClass.Class; static ConstructorHelpers::FClassFinder<UObject> MyWidgetBPClass(TEXT("/Game/ThirdPerson/UMG/MyWidgetBlueprint.MyWidgetBlueprint_C")); UClass* MyWidgetClass = MyWidgetBPClass.Class;
注:傳給ConstructorHelpers::FClassFinder的Blueprint UClass的字符串路徑不為[名稱].[名稱] _C時,其內部會進行補全
查找資源API
FindObject,FindObjectFast,FindObjectChecked,FindObjectSafe
FindObject會在內存中查找對象,找到就會返回,找不到會返回nullptr,不會觸發加載。
如果傳入了Outer,就會在Outer所在的Package下面找對應的資源對象,如果沒有Outer就會在全局找這個資源對象。
Fast版本功能和FindObject相同,但是不會檢查路徑,明確知道完整路徑時用這個可以避免檢查路徑開銷,速度會快一些。
Check版本功能也和FindObject相同,但是不會返回nullptr,找不到就會報Fatal,直接停止程序(Shipping和Test版不會)。
Safe版本的函數功能和FindObject相同,但是在正在GC時或者正在保存包時直接返回nullptr。
這些函數最終都會調用到StaticFindObjectFastInternal函數,StaticFindObjectFastInternal內部又會調用StaticFindObjectFastInternalThreadSafe函數
傳入的ObjectName(資源路徑)會被GetObjectOuterHash轉為Hash值,通過Hash值在ThreadHash上取到一個對象列表,之后再根據條件取得要查找的對象
UObject* StaticFindObjectFastInternalThreadSafe(FUObjectHashTables& ThreadHash, const UClass* ObjectClass, const UObject* ObjectPackage, FName ObjectName, bool bExactClass, bool bAnyPackage, EObjectFlags ExcludeFlags, EInternalObjectFlags ExclusiveInternalFlags) { ExclusiveInternalFlags |= EInternalObjectFlags::Unreachable; // If they specified an outer use that during the hashing UObject* Result = nullptr; if (ObjectPackage != nullptr) { int32 Hash = GetObjectOuterHash(ObjectName, (PTRINT)ObjectPackage); FHashTableLock HashLock(ThreadHash); for (TMultiMap<int32, class UObjectBase*>::TConstKeyIterator HashIt(ThreadHash.HashOuter, Hash); HashIt; ++HashIt) { UObject *Object = (UObject *)HashIt.Value(); if /* check that the name matches the name we're searching for */ ((Object->GetFName() == ObjectName) /* Don't return objects that have any of the exclusive flags set */ && !Object->HasAnyFlags(ExcludeFlags) /* check that the object has the correct Outer */ && Object->GetOuter() == ObjectPackage /** If a class was specified, check that the object is of the correct class */ && (ObjectClass == nullptr || (bExactClass ? Object->GetClass() == ObjectClass : Object->IsA(ObjectClass))) /** Include (or not) pending kill objects */ && !Object->HasAnyInternalFlags(ExclusiveInternalFlags)) { checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName()); if (Result) { UE_LOG(LogUObjectHash, Warning, TEXT("Ambiguous search, could be %s or %s"), *GetFullNameSafe(Result), *GetFullNameSafe(Object)); } else { Result = Object; } #if (UE_BUILD_SHIPPING || UE_BUILD_TEST) break; #endif } } // ... ... } else { // ... ... } // Not found. return Result; }
FUObjectHashTables ThreadHash類型如下:
class FUObjectHashTables { /** Critical section that guards against concurrent adds from multiple threads */ FCriticalSection CriticalSection; public: /** Hash sets */ TMap<int32, FHashBucket> Hash; TMultiMap<int32, class UObjectBase*> HashOuter; /** Map of object to their outers, used to avoid an object iterator to find such things. **/ TMap<UObjectBase*, FHashBucket> ObjectOuterMap; TMap<UClass*, FHashBucket> ClassToObjectListMap; TMap<UClass*, TSet<UClass*>> ClassToChildListMap; TAtomic<uint64> ClassToChildListMapVersion; /** Map of package to the object their contain. */ TMap<UPackage*, FHashBucket> PackageToObjectListMap; /** Map of object to their external package. */ TMap<UObjectBase*, UPackage*> ObjectToPackageMap; // ... ... };
引擎會把每個對象會根據Hash存到全局的HashOuter里,這是一個TMultiMap,也就是一個hash會對應多個Value
Hash是用GetObjectOuterHash這個函數計算出來的,內部實際是路徑FName加Package名字取hash
這個計算結果本身就會沖突(FHashBucket用於處理沖突),多個路徑有概率映射到同一個hash值,所以用TMultiMap就可以將多個值存到同一個hash上
這樣只要把加載好的對象存到這里,就可以保證即使多次查找也能找到同一個對象
同步加載資源API
LoadObject,LoadClass,LoadPackage
LoadObject、LoadClass內部會先調用FindObject在內存中找,找到了直接返回,沒找到就會把路徑轉化為Package進行LoadPackage同步加載
一個包里如果有多個資源,他們在硬盤上對應的是同一個文件,那么只需要加載這個文件就好了
再深入底層可以看到,會調用到LoadPackageInternal函數,並在該函數中最終調用LoadPackageAsync函數,這就是異步加載的入口,並且最后FlushAsyncLoading,內部阻塞等待,將異步加載轉為同步
UPackage* LoadPackageInternal(UPackage* InOuter, const TCHAR* InLongPackageNameOrFilename, uint32 LoadFlags, FLinkerLoad* ImportLinker, FArchive* InReaderOverride, const FLinkerInstancingContext* InstancingContext) { DECLARE_SCOPE_CYCLE_COUNTER(TEXT("LoadPackageInternal"), STAT_LoadPackageInternal, STATGROUP_ObjectVerbose); SCOPED_CUSTOM_LOADTIMER(LoadPackageInternal) ADD_CUSTOM_LOADTIMER_META(LoadPackageInternal, PackageName, InLongPackageNameOrFilename); checkf(IsInGameThread(), TEXT("Unable to load %s. Objects and Packages can only be loaded from the game thread."), InLongPackageNameOrFilename); UPackage* Result = nullptr; if (FPlatformProperties::RequiresCookedData() && GEventDrivenLoaderEnabled && EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME ) { FString InName; FString InPackageName; if (FPackageName::IsPackageFilename(InLongPackageNameOrFilename)) { FPackageName::TryConvertFilenameToLongPackageName(InLongPackageNameOrFilename, InPackageName); } else { InPackageName = InLongPackageNameOrFilename; } if (InOuter) { InName = InOuter->GetPathName(); } else { InName = InPackageName; } FName PackageFName(*InPackageName); { if (FCoreDelegates::OnSyncLoadPackage.IsBound()) { FCoreDelegates::OnSyncLoadPackage.Broadcast(InName); } int32 RequestID = LoadPackageAsync(InName, nullptr, *InPackageName); if (RequestID != INDEX_NONE) { FlushAsyncLoading(RequestID); } } Result = (InOuter ? InOuter : FindObjectFast<UPackage>(nullptr, PackageFName)); return Result; } // ... ... return Result; }
參考
