Download

FObjectFinder.object is null on Tick

I have the following code:



APracticeProjectCharacter::APracticeProjectCharacter(const FObjectInitializer& ObjectInitializer)
         : Super(ObjectInitializer)
     {
         static ConstructorHelpers::FObjectFinder<UMaterialInstance> materialAsset(TEXT("Material'/Game/ThirdPerson/Materials/playerMaterial_Inst.playerMaterial_Inst'"));
         if (materialAsset.Succeeded())
             material = (UMaterialInstanceDynamic*)materialAsset.Object;
     }
     
     void APracticeProjectCharacter::Tick(float deltaTime)
     {
         Super::Tick(deltaTime);
             material->SetScalarParameterValue(TEXT("power"), maxPowerLevel / powerLevel);
     }


I am trying to load material from the assets and change some parameters.
On the constructor, material is not null and if I ser paramater value there it works. but on Tick function material is null and it leads to run time error.
I understand why this is happaning. material is a UMaterialInstanceDynamic pointer and when FObjectFinder.object dies, materials dies too. but why does it goes null? it is static.

I also have tried to change the contructor for the following:



static ConstructorHelpers::FObjectFinder<UMaterialInstance> materialAsset(TEXT("Material'/Game/ThirdPerson/Materials/playerMaterial_Inst.playerMaterial_Inst'"));
         if (materialAsset.Succeeded())
             material = UMaterialInstanceDynamic::Create(materialAsset.Object, this);


But chainging the paramater is not working. Even if I change it from the constructor. How can I get this work?


         if (materialAsset.Succeeded())
             material = (UMaterialInstanceDynamic*)materialAsset.Object;

This is an invalid cast. There’s no such thing as a MaterialInstanceDynamic asset, what you have is a Material, plain and simple. You end up reinterpreting a UMaterial pointer to an unrelated type which will cause a crash when UMaterialInstance::SetScalarParameterValue tries to access members that don’t exist on a UMaterial.


         if (materialAsset.Succeeded())
             material = UMaterialInstanceDynamic::Create(materialAsset.Object, this);

Internally, this calls NewObject, which is not supported in UObject constructors. The constructed object does not have the proper flags for it to be serialized at runtime, so when properties are serialized in, the material is zeroed out or otherwise invalid.

If you really, really want to do this in the constructor, in theory, it would be possible to modify the engine create a variant of UMaterialInstanceDynamic::Create that goes through FObjectInitializer, resulting in a MID that is properly flagged as a default subobject.

But the proper solution as it is would be to save the parent material as a UMaterial in the constructor:


         if (materialAsset.Succeeded())
             material = (UMaterial*)materialAsset.Object;

And move creation of the MID to PostInitializeComponents():


     void APracticeProjectCharacter::PostInitializeComponents()
     {
	Super::PostInitializeComponents();
	
	materialDynamic = UMaterialInstanceDynamic::Create(material, this);
     }

Note that this will only create the MID, you still have to assign it to whatever mesh or surface you want to control. If it’s a PrimitiveComponent, there’s some CreateAndSetMaterialInstanceDynamic shortcuts you can use to directly create and replace a material with a MID.

-Camille