From 3215efeef38edfc5a8cdb141d849c5fac9ab692a Mon Sep 17 00:00:00 2001 From: jyelon Date: Fri, 14 Nov 2025 02:41:44 -0500 Subject: [PATCH] Overhauling asset loading --- Content/AnimSequences/SEQ_Jump.uasset | 3 + Content/Tangibles/TAN_Character.uasset | 4 +- Content/Tangibles/TAN_StaticMesh.uasset | 4 +- Content/Widgets/WB_Root.uasset | 4 +- Source/Integration/AssetLookup.cpp | 274 +++++++++++------------- Source/Integration/AssetLookup.h | 72 ++++--- Source/Integration/Tangible.cpp | 11 +- 7 files changed, 180 insertions(+), 192 deletions(-) create mode 100644 Content/AnimSequences/SEQ_Jump.uasset diff --git a/Content/AnimSequences/SEQ_Jump.uasset b/Content/AnimSequences/SEQ_Jump.uasset new file mode 100644 index 00000000..e218cddc --- /dev/null +++ b/Content/AnimSequences/SEQ_Jump.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47e5d7aea3e782d409c967015c36d252ebf61fb47d05f08ada2abc912714734b +size 413528 diff --git a/Content/Tangibles/TAN_Character.uasset b/Content/Tangibles/TAN_Character.uasset index cf749d2f..457795ff 100644 --- a/Content/Tangibles/TAN_Character.uasset +++ b/Content/Tangibles/TAN_Character.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e4eac77be662465181a233723da17a88968fcbfca69be4dfa5682e7068180d1 -size 323402 +oid sha256:c5a84148e336b18a658fc7d174a7b9487fd7bc8a7661ac91e386ec4975387d7b +size 329642 diff --git a/Content/Tangibles/TAN_StaticMesh.uasset b/Content/Tangibles/TAN_StaticMesh.uasset index 1f6a87a2..0a684522 100644 --- a/Content/Tangibles/TAN_StaticMesh.uasset +++ b/Content/Tangibles/TAN_StaticMesh.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64f746902f07c06222553701dfa143d050a6ff193bcc80609e85f7be486aeb69 -size 182324 +oid sha256:450bef454ae7dd0b67054f8ce437e2f2c2d7473f996c520e30868ae28663352a +size 185797 diff --git a/Content/Widgets/WB_Root.uasset b/Content/Widgets/WB_Root.uasset index d7018a47..68f1a50c 100644 --- a/Content/Widgets/WB_Root.uasset +++ b/Content/Widgets/WB_Root.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b2b135100406df2e087ccaee1905ab904f12a3605c637bb2f55b911d735416a0 -size 88765 +oid sha256:039907ebcd6a9523d780799d60c628b830c6471ffc8256e2a6a508440062dac6 +size 83262 diff --git a/Source/Integration/AssetLookup.cpp b/Source/Integration/AssetLookup.cpp index ce6b39ab..1fdba948 100644 --- a/Source/Integration/AssetLookup.cpp +++ b/Source/Integration/AssetLookup.cpp @@ -7,93 +7,9 @@ #include "Components/Widget.h" #include "WidgetBlueprint.h" #include "Blueprint/UserWidget.h" +#include "Animation/AnimSequence.h" #include "Engine/StaticMesh.h" -void UlxAssetLookup::RebuildIndex() -{ - CachedTangibles.Empty(); - CachedStaticMeshes.Empty(); - IAssetRegistry::GetChecked().WaitForCompletion(); - ScanTangibles(); - ScanStaticMeshes(); - ScanWidgets(); -} - -void UlxAssetLookup::ScanTangibles() -{ - TArray FoundData; - FARFilter AssetFilter; - AssetFilter.PackagePaths.Add(FName(TEXT("/Game/Tangibles"))); - AssetFilter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName()); - AssetFilter.bIncludeOnlyOnDiskAssets = true; - AssetFilter.bRecursivePaths = true; - IAssetRegistry::GetChecked().GetAssets(AssetFilter, FoundData); - - UE_LOG(LogLuprexIntegration, Display, TEXT("Found %d assets in /Game/Tangibles"), FoundData.Num()); - for (const FAssetData &Data : FoundData) - { - FString Path = Data.GetObjectPathString() + TEXT("_C"); - CachedTangibles.Add(Data.AssetName, Path); - } -} - -void UlxAssetLookup::ScanStaticMeshes() -{ - TArray FoundData; - FARFilter AssetFilter; - AssetFilter.PackagePaths.Add(FName(TEXT("/Game/StaticMeshes"))); - AssetFilter.ClassPaths.Add(UStaticMesh::StaticClass()->GetClassPathName()); - AssetFilter.bIncludeOnlyOnDiskAssets = true; - AssetFilter.bRecursivePaths = true; - IAssetRegistry::GetChecked().GetAssets(AssetFilter, FoundData); - - UE_LOG(LogLuprexIntegration, Display, TEXT("Found %d assets in /Game/StaticMeshes"), FoundData.Num()); - for (const FAssetData &Data : FoundData) - { - FString Path = Data.GetObjectPathString(); - CachedStaticMeshes.Add(Data.AssetName, Path); - } -} - -void UlxAssetLookup::ScanWidgets() -{ - TArray FoundData; - FARFilter AssetFilter; - AssetFilter.PackagePaths.Add(FName(TEXT("/Game/Widgets"))); - AssetFilter.ClassPaths.Add(UWidgetBlueprint::StaticClass()->GetClassPathName()); - AssetFilter.bIncludeOnlyOnDiskAssets = true; - AssetFilter.bRecursivePaths = true; - IAssetRegistry::GetChecked().GetAssets(AssetFilter, FoundData); - - UE_LOG(LogLuprexIntegration, Display, TEXT("Found %d assets in /Game/Widgets"), FoundData.Num()); - for (const FAssetData &Data : FoundData) - { - FString Path = Data.GetObjectPathString() + TEXT("_C"); - CachedWidgets.Add(Data.AssetName, Path); - } -} - -FString UlxAssetLookup::TangibleLoadPath(const FName &AssetName) const -{ - const FString *Result = CachedTangibles.Find(AssetName); - if (Result == nullptr) return TEXT(""); - return *Result; -} - -FString UlxAssetLookup::StaticMeshLoadPath(const FName &AssetName) const -{ - const FString *Result = CachedStaticMeshes.Find(AssetName); - if (Result == nullptr) return TEXT(""); - return *Result; -} - -FString UlxAssetLookup::WidgetLoadPath(const FName &AssetName) const -{ - const FString *Result = CachedWidgets.Find(AssetName); - if (Result == nullptr) return TEXT(""); - return *Result; -} - void UlxAssetLookup::LogMaybeError(bool Error, const TCHAR *Message, const TCHAR *Path) { if (Error) @@ -106,94 +22,154 @@ void UlxAssetLookup::LogMaybeError(bool Error, const TCHAR *Message, const TCHAR } } -UStaticMesh *UlxAssetLookup::LoadStaticMeshAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound) -{ - ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context); - FString Path = mode->GetAssetLookup()->StaticMeshLoadPath(FName(FString("SM_") + Name)); - if (Path.IsEmpty()) - { - LogMaybeError(ErrorIfNotFound, TEXT("Static mesh not on search path"), *Name); - return nullptr; - } - UStaticMesh *Result = LoadObject(nullptr, *Path); - if (Result == nullptr) - { - LogMaybeError(ErrorIfNotFound, TEXT("Cannot load static mesh"), *Path); - return nullptr; - } - return Result; +const ElxValidOrNotValid NotValid = ElxValidOrNotValid::NotValid; +const ElxValidOrNotValid Valid = ElxValidOrNotValid::Valid; + +void UlxAssetLookup::RebuildIndex() +{ + IAssetRegistry::GetChecked().WaitForCompletion(); + AssetPaths.Empty(); + AddAssets(TEXT("/Game/StaticMeshes"), UStaticMesh::StaticClass(), TEXT("SM_")); + AddAssets(TEXT("/Game/AnimSequences"), UAnimSequence::StaticClass(), TEXT("SEQ_")); + AddAssets(TEXT("/Game/Tangibles"), UBlueprint::StaticClass(), TEXT("TAN_")); + AddAssets(TEXT("/Game/Widgets"), UWidgetBlueprint::StaticClass(), TEXT("WB_")); } -TSubclassOf UlxAssetLookup::LoadTangibleBlueprintAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound) { - ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context); - FString Path = mode->GetAssetLookup()->TangibleLoadPath(FName(FString("TAN_") + Name)); +void UlxAssetLookup::AddAssets(const TCHAR *Path, const UClass *Class, const TCHAR *NamePrefix) +{ + TArray FoundData; + TMap Result; + FARFilter AssetFilter; + AssetFilter.PackagePaths.Add(FName(Path)); + AssetFilter.ClassPaths.Add(Class->GetClassPathName()); + AssetFilter.bIncludeOnlyOnDiskAssets = true; + AssetFilter.bRecursivePaths = true; + IAssetRegistry::GetChecked().GetAssets(AssetFilter, FoundData); + FString FSNamePrefix(NamePrefix); + + for (const FAssetData &Data : FoundData) + { + FString AssetName = Data.AssetName.ToString(); + if (AssetName.StartsWith(FSNamePrefix)) + { + FName ShortName(AssetName.RightChop(FSNamePrefix.Len())); + AssetPaths.Add(MakeTuple(Class->GetName(), ShortName), Data.GetObjectPathString()); + } + } + + UE_LOG(LogLuprexIntegration, Display, TEXT("Found %d assets of type %s in %s"), + FoundData.Num(), *Class->GetName(), Path); +} + +FString UlxAssetLookup::GetAssetPath(const UObject *Context, const UClass *Class, const FString &Name) +{ + const UlxAssetLookup *Lookup = ALuprexGameModeBase::FromContext(Context)->GetAssetLookup(); + const FString *Path = Lookup->AssetPaths.Find(MakeTuple(Class->GetName(), FName(Name))); + if (Path == nullptr) + { + return FString(); + } + return *Path; +} + +ElxValidOrNotValid UlxAssetLookup::LoadStaticMeshAsset( + UStaticMesh *&Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound) +{ + Result = nullptr; + FString Path = GetAssetPath(Context, UStaticMesh::StaticClass(), Name); if (Path.IsEmpty()) { - LogMaybeError(ErrorIfNotFound, TEXT("Tangible not on search path"), *Name); - return nullptr; + LogMaybeError(ErrorIfNotFound, TEXT("Static Mesh not found"), *Name); + Result = nullptr; return NotValid; } - UClass *Result = LoadObject(nullptr, *Path); + Result = LoadObject(nullptr, *Path); if (Result == nullptr) { - LogMaybeError(ErrorIfNotFound, TEXT("Cannot load tangible class"), *Path); - return nullptr; + LogMaybeError(ErrorIfNotFound, TEXT("Cannot load Static Mesh"), *Path); + Result = nullptr; return NotValid; } - if (!Result->IsChildOf(AActor::StaticClass())) + return Valid; +} + +ElxValidOrNotValid UlxAssetLookup::LoadAnimSequenceAsset( + UAnimSequence *&Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound) +{ + Result = nullptr; + FString Path = GetAssetPath(Context, UAnimSequence::StaticClass(), Name); + if (Path.IsEmpty()) { - LogMaybeError(ErrorIfNotFound, TEXT("Tangible class is not an actor"), *Path); - return nullptr; + LogMaybeError(ErrorIfNotFound, TEXT("Anim Sequence not found"), *Name); + Result = nullptr; return NotValid; + } + Result = LoadObject(nullptr, *Path); + if (Result == nullptr) + { + LogMaybeError(ErrorIfNotFound, TEXT("Cannot load Anim Sequence"), *Path); + Result = nullptr; return NotValid; + } + return Valid; +} + +ElxValidOrNotValid UlxAssetLookup::LoadTangibleBlueprintAsset( + TSubclassOf &Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound) +{ + FString Path = GetAssetPath(Context, UBlueprint::StaticClass(), Name); + if (Path.IsEmpty()) + { + LogMaybeError(ErrorIfNotFound, TEXT("Tangible not found"), *Name); + Result = nullptr; return NotValid; + } + Result = LoadClass(nullptr, *UnderscoreC(Path)); + if (Result == nullptr) + { + LogMaybeError(ErrorIfNotFound, TEXT("Tangible load failed, not an actor blueprint"), *Path); + Result = nullptr; return NotValid; } UFunction *aqchanged = Result->FindFunctionByName(FName(TEXT("Animation Queue Changed"))); if ((aqchanged == nullptr)||(aqchanged->ParmsSize != 0)) { LogMaybeError(ErrorIfNotFound, TEXT("Tangible does not have 'Animation Queue Changed' function"), *Path); - return nullptr; + Result = nullptr; return NotValid; } - return Result; + return Valid; } -TSubclassOf UlxAssetLookup::LoadUserWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound) { - ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context); - FString Path = mode->GetAssetLookup()->WidgetLoadPath(FName(FString("WB_") + Name)); +ElxValidOrNotValid UlxAssetLookup::LoadUserWidgetAsset( + TSubclassOf &Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound) +{ + Result = nullptr; + FString Path = GetAssetPath(Context, UWidgetBlueprint::StaticClass(), Name); + if (Path.IsEmpty()) + { + LogMaybeError(ErrorIfNotFound, TEXT("Widget not Found"), *Name); + Result = nullptr; return NotValid; + } + Result = LoadClass(nullptr, *UnderscoreC(Path)); + if (Result == nullptr) + { + LogMaybeError(ErrorIfNotFound, TEXT("Cannot load widget, not a UUserWidget"), *Path); + Result = nullptr; return NotValid; + } + return Valid; +} + +ElxValidOrNotValid UlxAssetLookup::LoadLuaWidgetAsset( + TSubclassOf &Result, const UObject *Context, const FString &Name, bool ErrorIfNotFound) +{ + Result = nullptr; + FString Path = GetAssetPath(Context, UWidgetBlueprint::StaticClass(), Name); if (Path.IsEmpty()) { LogMaybeError(ErrorIfNotFound, TEXT("Widget not on search path"), *Name); - return nullptr; + Result = nullptr; return NotValid; } - UClass *Result = LoadObject(nullptr, *Path); + Result = LoadClass(nullptr, *UnderscoreC(Path)); if (Result == nullptr) { - LogMaybeError(ErrorIfNotFound, TEXT("Cannot load widget blueprint"), *Path); - return nullptr; + LogMaybeError(ErrorIfNotFound, TEXT("Cannot load widget, not a UlxLuaWidget"), *Path); + Result = nullptr; return NotValid; } - if (!Result->IsChildOf(UUserWidget::StaticClass())) - { - LogMaybeError(ErrorIfNotFound, TEXT("Blueprint does not derive from UUserWidget"), *Path); - return nullptr; - } - return Result; -} - -TSubclassOf UlxAssetLookup::LoadLuaWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound) { - ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context); - FString Path = mode->GetAssetLookup()->WidgetLoadPath(FName(FString("WB_") + Name)); - if (Path.IsEmpty()) - { - LogMaybeError(ErrorIfNotFound, TEXT("Widget not on search path"), *Name); - return nullptr; - } - UClass *Result = LoadObject(nullptr, *Path); - if (Result == nullptr) - { - LogMaybeError(ErrorIfNotFound, TEXT("Cannot load widget blueprint"), *Path); - return nullptr; - } - if (!Result->IsChildOf(UlxLuaWidget::StaticClass())) - { - LogMaybeError(ErrorIfNotFound, TEXT("Blueprint does not derive from UlxLuaWidget"), *Path); - return nullptr; - } - return Result; + return Valid; } diff --git a/Source/Integration/AssetLookup.h b/Source/Integration/AssetLookup.h index 0b387d90..3b1f9492 100644 --- a/Source/Integration/AssetLookup.h +++ b/Source/Integration/AssetLookup.h @@ -4,12 +4,14 @@ #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" #include "CommonTypes.h" +#include "Templates/Tuple.h" #include "AssetLookup.generated.h" class AActor; class UUserWidget; class UlxLookAtWidget; class UStaticMesh; +class UAnimSequence; UCLASS(MinimalAPI) class UlxAssetLookup : public UObject @@ -17,50 +19,56 @@ class UlxAssetLookup : public UObject GENERATED_BODY() private: - // Map from asset name to full loadable path. - UPROPERTY() - TMap CachedTangibles; - - // Map from asset name to to full loadable path. - UPROPERTY() - TMap CachedStaticMeshes; - - // Map from asset name to full loadable path. - UPROPERTY() - TMap CachedWidgets; + // + // At initialization time, we scan the asset directories + // to see what assets are present. These maps store the + // result of the scan. Each entry in the map contains a + // classname, a short name, and a path: + // + // -> "/Game/AnimSequences/SEQ_Jump" + // + TMap, FString> AssetPaths; private: - void ScanTangibles(); - void ScanStaticMeshes(); - void ScanWidgets(); - static void LogMaybeError(bool ErrorIfNotFound, const TCHAR *Format, const TCHAR *Data); + void AddAssets(const TCHAR *Path, const UClass *Class, const TCHAR *NamePrefix); + + static FString GetAssetPath(const UObject *Context, const UClass *Class, const FString &Name); + + static void LogMaybeError(bool Error, const TCHAR *Message, const TCHAR *Path); + + static FString UnderscoreC(const FString &Name) { return Name + FString(TEXT("_C")); } public: void RebuildIndex(); - // Get the full LoadObject path of a Tangible. - FString TangibleLoadPath(const FName &AssetName) const; - - // Get the full LoadObject path of a Static Mesh. - FString StaticMeshLoadPath(const FName &AssetName) const; - - // Get the full LoadObject path of a Widget Blueprint. - FString WidgetLoadPath(const FName &AssetName) const; - // Get a static mesh by name - UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading") - static UStaticMesh *LoadStaticMeshAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); + UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading") + static ElxValidOrNotValid LoadStaticMeshAsset( + UStaticMesh *&Result, + const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); + // Get an animation sequence by name. + UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading") + static ElxValidOrNotValid LoadAnimSequenceAsset( + UAnimSequence *&Result, + const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); + // Get a tangible class by name - UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading") - static TSubclassOf LoadTangibleBlueprintAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); + UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading") + static ElxValidOrNotValid LoadTangibleBlueprintAsset( + TSubclassOf &Result, + const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); // Get a widget blueprint by name - UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading") - static TSubclassOf LoadUserWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); + UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading") + static ElxValidOrNotValid LoadUserWidgetAsset( + TSubclassOf &Result, + const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); // Get a look-at widget blueprint by name - UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading") - static TSubclassOf LoadLuaWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); + UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context", ExpandEnumAsExecs="ReturnValue"), Category = "Luprex|Asset Loading") + static ElxValidOrNotValid LoadLuaWidgetAsset( + TSubclassOf &Result, + const UObject *Context, const FString &Name, bool ErrorIfNotFound = false); }; diff --git a/Source/Integration/Tangible.cpp b/Source/Integration/Tangible.cpp index 3d437241..e34951fe 100644 --- a/Source/Integration/Tangible.cpp +++ b/Source/Integration/Tangible.cpp @@ -59,11 +59,12 @@ void UlxTangible::SetActorBlueprint(const FString &XName) { return; } - UClass *blueprint = UlxAssetLookup::LoadTangibleBlueprintAsset(this, Name); - if (blueprint == nullptr) + TSubclassOf Blueprint; + UlxAssetLookup::LoadTangibleBlueprintAsset(Blueprint, this, Name); + if (Blueprint == nullptr) { - blueprint = UlxAssetLookup::LoadTangibleBlueprintAsset(this, DEFAULT_BLUEPRINT); - check(blueprint != nullptr); + UlxAssetLookup::LoadTangibleBlueprintAsset(Blueprint, this, DEFAULT_BLUEPRINT); + check(Blueprint != nullptr); } // Update the blueprint name @@ -109,7 +110,7 @@ void UlxTangible::SetActorBlueprint(const FString &XName) { }; UE_LOG(LogLuprexIntegration, Display, TEXT("Creating Actor: %s"), *params.Name.ToString()); - AActor* a = w->SpawnActor(blueprint, &transform, params); + AActor* a = w->SpawnActor(Blueprint, &transform, params); check(a != nullptr); CurrentActor = a; }