Download

[SOLVED] FObjectFinder vs StaticLoad, best way to provide common assets, globally, at runtime?

My game involves many actors, with the same skeletal mesh, who from time to time will change materials depending on their allegiance.

i.e., an actor’s ChangeAllegiance(…) method will do a USkeletalMesh->SetMaterial() call.

However, I’m trying to figure out the most approprate place to summon these assets from. My goal is to hold the least amount of information on this per-actor and maybe get what they need from some static BlueprintUtility resource?

As far as I’m aware you can’t load resources in BlueprintUtility libraries because they have no Constructor and hence can’t use ConstructorHelpers::FFindObject.

However, I do appear to be able to load resources anywhere, at any time, using **StaticLoadObject **?

Q: Why would I ever use FFindObject when I can use StaticLoadObject where there seems to be no caveat or downside?


The following is just some extra information on how I’m currently doing this with FFindObject in the constructor

At the moment, each Actor has:
TMap<EAllegiance, UMaterialInstance*> Uniforms;
and this is loaded statically in the constructor:


    static ConstructorHelpers::FObjectFinder<UMaterialInterface> WhiteUniform(TEXT("MaterialInstanceConstant'/Game/Materials/Knight_White.Knight_White'"));
    static ConstructorHelpers::FObjectFinder<UMaterialInterface> RedUniform(TEXT("MaterialInstanceConstant'/Game/Materials/Knight_Red.Knight_Red'"));
    static ConstructorHelpers::FObjectFinder<UMaterialInterface> YellowUniform(TEXT("MaterialInstanceConstant'/Game/Materials/Knight_Yellow.Knight_Yellow'"));
    static ConstructorHelpers::FObjectFinder<UMaterialInterface> BlueUniform(TEXT("MaterialInstanceConstant'/Game/Materials/Knight_Blue.Knight_Blue'"));
    if (WhiteUniform.Succeeded() && RedUniform.Succeeded() && YellowUniform.Succeeded() && BlueUniform.Succeeded()) {
        Uniform.Add(EAllegiance::White, WhiteUniform.Object);
        Uniform.Add(EAllegiance::Red, RedUniform.Object);
        Uniform.Add(EAllegiance::Yellow, YellowUniform.Object);
        Uniform.Add(EAllegiance::Blue, BlueUniform.Object);
    }
    else {
        UE_LOG(LogTemp, Error, TEXT("UNABLE TO FIND MATERIAL"));
    }

This means if I have thousands of Actors, each has a TMap of possible UMaterialInstance*. Not too system intensive, but not a great design decision?

Is there a way I could get these assets loaded in C++ at runtime into a BlueprintUtility and be able to call something like:



void ChangeAllegiance(EAllegiance a) {
   UMaterialInstance* mat = UGameUtility::GetUniformMaterial(a);
   SkeletalMesh->SetMaterial(0, mat);
}


It’s mostly about getting these resources initialised at the beginning and having them globally available to any actor.

Feels… “hacky”? Lazy initialisation via static functions… but seems to work okay?

UtilityLibrary.h (HEADER)



UCLASS()
class TERRAINTEST_API UUtilityLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()

public:
    static UMaterialInterface* GetPlebUniform(EAllegiance a);
    static const bool IsMaterialIndexInitialised() {
        return bRuntimeMaterialsInitialised;
    }

private:
    static TMap<EAllegiance, UMaterialInterface*> PlebUniforms;
    static void InitialiseMaterials();
    static bool bRuntimeMaterialsInitialised;
};


UtilityLibrary.cpp (SOURCE)



// Implementation of static variables
TMap<EAllegiance, UMaterialInterface*> UUtilityLibrary::PlebUniforms;
bool UUtilityLibrary::bRuntimeMaterialsInitialised = false;

UMaterialInterface * UUtilityLibrary::GetPlebUniform(EAllegiance a)
{
    if (!bRuntimeMaterialsInitialised) {
        // This should happen one time only. Lazy initialisation.
        InitialiseMaterials();
    }

    if (bRuntimeMaterialsInitialised) {
        if (PlebUniforms.Contains(a)) { // Possibly remove for optimisation.
            return PlebUniforms[a];
        }
    }

    return nullptr;
}

void UUtilityLibrary::InitialiseMaterials() {
    if (bRuntimeMaterialsInitialised) return;

    UMaterialInterface* whitePlebUniform = LoadObject<UMaterialInterface>(nullptr, TEXT("MaterialInstanceConstant'/Game/Materials/Knight_White.Knight_White'"));
    UMaterialInterface* redPlebUniform = LoadObject<UMaterialInterface>(nullptr, TEXT("MaterialInstanceConstant'/Game/Materials/Knight_Red.Knight_red'"));
    UMaterialInterface* yellowPlebUniform = LoadObject<UMaterialInterface>(nullptr, TEXT("MaterialInstanceConstant'/Game/Materials/Knight_Yellow.Knight_Yellow'"));
    UMaterialInterface* bluePlebUniform = LoadObject<UMaterialInterface>(nullptr, TEXT("MaterialInstanceConstant'/Game/Materials/Knight_Blue.Knight_Blue'"));

    if (whitePlebUniform && redPlebUniform && yellowPlebUniform && bluePlebUniform) {
        UE_LOG(LogTemp, Warning, TEXT("Successfully loaded Pleb Uniform assets..."));
        PlebUniforms.Add(EAllegiance::White, whitePlebUniform);
        PlebUniforms.Add(EAllegiance::Red, redPlebUniform);
        PlebUniforms.Add(EAllegiance::Yellow, yellowPlebUniform);
        PlebUniforms.Add(EAllegiance::Blue, bluePlebUniform);
        bRuntimeMaterialsInitialised = true;
    }
    else {
        UE_LOG(LogTemp, Error, TEXT("ERROR - Unable to load Pleb Uniform assets"));
    }
}



TSoftObjectPtr OBJPtr (object);
OBJPtr.LoadSynchronous();

Thanks! I’m new to the UnrealEngine and still getting my head around how things are expected to be done.

Searching for TSoftObjectPtr I also came across the following documentation which will also be helpful.

https://docs.unrealengine.com/en-us/Programming/Assets/ReferencingAssets