UE4中資源的引用


強引用(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());
}

 

使用FSoftClassPathFSoftObjectPath來軟引用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:黃色的TPersistentObjectPtrTSoftObjectPtrTSoftClassPtr為模板類

注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)通過字符串路徑來指定,通過LoadObjectLoadClass)來加載。

該方式不能被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

FindObjectFindObjectFastFindObjectCheckedFindObjectSafe

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

LoadObjectLoadClass內部會先調用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;
}

 

參考

引用資源Referencing Assets

UE4的資源管理 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM