Why is my C++ UMaterialInstanceDynamic not accepting parameter changes?

Hey everyone!

EDIT: Further investigation reveals that the material is updating fine - variables edited on the instance of the component attached to the actor just aren’t updating.

I’m working on a character customization system component in C++, and I’m having trouble getting my MIDs to update. I have materials declared in my header like this:

	UPROPERTY(BlueprintReadOnly, Category = "Owning Actor")
		USkeletalMeshComponent* Mesh;

	UPROPERTY(EditDefaultsOnly, Category = "Materials")
		UMaterial* HairMaterial;

	UPROPERTY(EditDefaultsOnly, Category = "Materials")
		UMaterial* SkinMaterial;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Materials")
		UMaterial* AlphaMaterial;

	UPROPERTY(BlueprintReadOnly, Category = "Materials")
		UMaterialInstanceDynamic* Hair;

	UPROPERTY(BlueprintReadOnly, Category = "Materials")
		UMaterialInstanceDynamic* Skin;

and I’m attempting to set vector and texture parameter values from both C++ and Blueprint. However, any change I try to make to the instance is silently rejected, and the actor I have this component attached to doesn’t update. I’ve tried changing my MID variables from BlueprintReadOnly to BlueprintReadWrite, but that didn’t help.

My source file looks like this. The last two functions are a product of desperation, and they aren’t applying any changes either. The last function contains some debug that should print the updated colour, but only prints out the default value.

// Sets default values for this component's properties
UCharacterCustomizationComponent::UCharacterCustomizationComponent()
{
	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these features
	// off to improve performance if you don't need them.
	bWantsInitializeComponent = true;
	PrimaryComponentTick.bCanEverTick = true;

	PrimaryComponentTick.bStartWithTickEnabled = false;
	bReplicates = true;
	bAutoActivate = true;

	// ...	
}


// Called when the game starts
void UCharacterCustomizationComponent::InitializeComponent()
{
	Super::InitializeComponent();

	// ...
	
}


// Called every frame. Is not enabled by default.
void UCharacterCustomizationComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction )
{
	Super::TickComponent( DeltaTime, TickType, ThisTickFunction );

	// ...
}

void UCharacterCustomizationComponent::PostInitProperties()
{
	Super::PostInitProperties();

	ACharacter* Character = Cast<ACharacter>(GetOwner());
	if (Character)
	{
		Mesh = Character->GetMesh();

		//Apply the skin to all skin elements		
		Skin = UMaterialInstanceDynamic::Create(SkinMaterial, this);		
		if (Skin)
		{
			for (auto Iter(SkinElements.CreateIterator()); Iter; Iter++)
			{
				Mesh->SetMaterial(*Iter, Skin);
			}
		}
		
		//Apply the hair to all hair elements
		Hair = UMaterialInstanceDynamic::Create(HairMaterial, this);
		if (Hair)
		{
			for (auto Iter(HairElements.CreateIterator()); Iter; Iter++)
			{
				Mesh->SetMaterial(*Iter, Hair);
			}
		}
	}

	//make sure the overriding blueprint sets all of its values proper in this event.
	OnSettingsChanged();
}

void UCharacterCustomizationComponent::SetSkinVectorParameter(FName Key, FLinearColor Colour)
{
	if (Skin)
	{
		Skin->SetVectorParameterValue(Key, Colour);
	}	
}

void UCharacterCustomizationComponent::SetHairVectorParameter(FName Key, FLinearColor Colour)
{
	if (Skin)
	{
		Hair->SetVectorParameterValue(Key, Colour);
		if (GEngine)
		{
			GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Red, TEXT("stuff"));
			FLinearColor Colour2;
			Hair->GetVectorParameterValue(Key, Colour2);
			GEngine->AddOnScreenDebugMessage(-1, 5, FColor::Yellow, Colour2.ToString());
		}
	}	
}

Could it be that the loop is not setting the instance to the actual mesh?

Not sure if this will help, but I set my params slightly differently, e.g.

UStaticMeshComponent* meshComp = mesh->GetStaticMeshComponent();

if (meshComp)
{
	UMaterialInstanceDynamic* meshCompMat = meshComp->CreateAndSetMaterialInstanceDynamic(0);

	if (meshCompMat)
	{
		meshCompMat->SetScalarParameterValue(YourStaticMeshMaterialParamFName, newValue);
	}
}

i.e. I create a MaterialInstanceDynamic from the existing material (in slot 0) and then set param values from that reference.

No, the mesh is definitely getting the material set - it shows up, and if I change any settings in the component (and not the instance attached to my actor) the change is applied.

Thanks for the suggestion! This would have me creating a new instance for every element, though, and I’d like to avoid that.

Sure, makes sense.

However, I recall vaguely from UE3 that this was actually the only way to do it, as there is a lot of black magic going on in the background. I honestly don’t know the details, but maybe it’s worth trying out to see if it works?

have tried creating the material in the construction script? Try it first in BP and then in C++ within the OnContruct method, just to check that the base logic is ok.

Where is the Value “Key” set?

Make sure you use the text menu and that the names match the parameter in the material perfectly. It IS case-sensitive in some cases.

You could just replace “Key” with : TEXT(“ParamName”)

This was originally all in the character’s blueprint (where I did create the instances in the constructor) and it worked fine there. If I move the Skin and Hair assignments to the constructor in C++, the asset references haven’t been properly loaded yet and they’re set to null.

The last two functions aren’t used anywhere but my attempts to get this working - normally, the parameter logic is all in BP where it’s a normal SetVectorParameter node from either the Skin or Hair variable.

Originally, yes, but that was before I made this functionality a component instead. It’s all in C++ now, save for the parameter changes which are in the component’s BP. Additionally, I can’t seem to assign any materials to the mesh from BP either.

You create them in the constructor in BP? You mean the construction script I guess.

Okay, after some additional testing it seems like the values aren’t even getting updated on the instance when I fiddle with them on the actor.

I changed my debug function to this:

void UCharacterCustomizationComponent::SetHairVectorParameter(FName Key, FLinearColor Colour)
{
	if (Skin)
	{
		Hair->SetVectorParameterValue(Key, Colour);
		if (GEngine)
		{
			GEngine->AddOnScreenDebugMessage(-1, 5, Colour, TEXT("InValue"));
			FLinearColor Colour2;
			Hair->GetVectorParameterValue(Key, Colour2);
			GEngine->AddOnScreenDebugMessage(-1, 5, Colour2, TEXT("ReadValue"));
		}
	}	
}

and hooking it up like this in BP produces an output where InValue and Result are the same, no matter what value I set it to.

Attempting to connect a print string to some of my Enums produces a hard crash, and the colour value never changes from its default. what the fuuu

Okay, I managed to fix it!

The problem was a classic PLBCAC (problem lies betwen chair and computer), and I had misunderstood how InitializeComponent was fired. I was under the assumption that it only fired on BeginPlay and was as such not useable for me. This is not the case, and it fires as a last-step construction event.

Moving all of my logic to InitializeComponent allows all values to be loaded and initialized correctly before I attempt to access them, and my mesh gets everything assigned properly. Having the logic in PostEditChangeProperty sort of worked, but it produced a quick flash of my MID before all materials reset.

So, in short - PLBCAC, move logic to InitializeComponent to fix most anything.

A big thanks to everyone who helped out on the way!

Can you please check my post? I have exact same iissue and not sure how to fix…

link text