MaterialParameterCollection not updating in game

I am having a weird issue with MaterialParameterCollections.

I have made a Notify State that updates a MaterialParameterCollection overtime. When logged, it seems that the scalar parameters are properly being animated, but I am not seeing any visual change during the game. When I close the game and return to the editor however, the effects of the MaterialParameterCollection reflects what it should have been in game however.

It’s really hard to explain with just words, so let me show some screenshots.
Note that the Output Log on the left is showing the scalar value of the pupil; at that size, the eye should have been perfectly black, like you can see in the second screenshot.

The code for the notify state in question.

void UPA_AnimState_SetMatParameter::NotifyBegin(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float TotalDuration)
    {
    	if (ValidateParameters(MeshComp) && !bStartedState)
    	{
    		AnimDuration = TotalDuration;
    		ElapsedTime = 0.f;
    
    		World = GetWorld();
    
    #if WITH_EDITOR
    		World = GEditor->GetEditorWorldContext().World();
    #endif
    
    		if (World == NULL)
    			return;
    
    		StartTime = World->GetTimeSeconds();
    		bStartedState = true;
    
    		UE_LOG(AnyLog, Log, TEXT("StartTime %f"), StartTime);
    
    		Received_NotifyBegin(MeshComp, Animation, TotalDuration);
    	}
    }
    
    void UPA_AnimState_SetMatParameter::NotifyTick(USkeletalMeshComponent * MeshComp, class UAnimSequenceBase * Animation, float FrameDeltaTime)
    {
    	if (MeshComp 
    		&& Animation
    		&& World)
    	{
    		ElapsedTime = World->GetTimeSeconds() - StartTime;
    		float DurationPct = ElapsedTime / (AnimDuration / Animation->RateScale);
    		float BlendPct;
    
    		if (bEaseIn)
    		{
    			if (bEaseOut)
    			{
    				// EASE IN/OUT
    				BlendPct = FMath::InterpEaseInOut(0.f, 1.f, DurationPct, CurveIntensity);
    			}
    			else
    			{
    				// EASE IN
    				BlendPct = FMath::Lerp(0.f, 1.f, FMath::Pow(DurationPct, CurveIntensity));
    			}
    		}
    		else
    		{
    			if (bEaseOut)
    			{
    				// EASE OUT
    				BlendPct = FMath::Lerp(0.f, 1.f, FMath::Pow(DurationPct, 1.f / CurveIntensity));
    			}
    			else
    			{
    				// LINEAR
    				BlendPct = FMath::Lerp(0.f, 1.f, DurationPct);
    			}
    		}
    
    		if (ScalarParameters.Num() != 0)
    		{
    			for (int i = 0; i < ScalarParameters.Num(); i++)
    			{
    				float AnimFloatValue = FMath::Lerp(ScalarParameters[i].StartValue, ScalarParameters[i].TargetValue, BlendPct);
    				UKismetMaterialLibrary::SetScalarParameterValue(World, MaterialParameterCollection, ScalarParameters[i].ParameterName, AnimFloatValue);
    			}
    		}
    
    		if (VectorParameters.Num() != 0)
    		{
    			for (int i = 0; i < VectorParameters.Num(); i++)
    			{
    				FVector AnimVectorValue = UKismetMathLibrary::LinearColorLerp(VectorParameters[i].StartValue, VectorParameters[i].TargetValue, BlendPct);
    				UKismetMaterialLibrary::SetVectorParameterValue(World, MaterialParameterCollection, VectorParameters[i].ParameterName, AnimVectorValue);
    			}
    		}	
    	}
    
    	Received_NotifyTick(MeshComp, Animation, FrameDeltaTime);
    }

Nevermind, I managed to solve it by implementing an interface class, and using that interface class to process the material parameter collection variables (by putting it into the MyCharacter class and then searching for all actors with the interface in the Anim Notify State). That seems to work.

Could you explain exacly how you did that? I am having exact same problem. My material parameter collection scalar is updating but I cannot see any change in my shaders (they are not getting the updated value).

I think the reason it’s not updating is because certain classes have limited access to classes like material parameter collections (that’s at least the best reason I can think off, being a programming noob). So, the other option is essentially to get access to a class that CAN change it at runtime and have that class handle it. For me, I used the player character to do that.

However, in the case of the Notify State class, it seems that unless the animation is used by the player character itself, it’s impossible to get a reference to the player character. Any calls to “get first player controller” or “get player character” and the likes will either create fatal errors or just return null pointers.

So what I did is make an interface class. In this interface class I added a function that would be responsible for actually updating the parameter collection. Then I attached this interface to the player character and implemented the parameter collection function there. Last, I searched for any interface implementations in the notify state and if found, executed the parameter function, which worked.

Interface.h

#pragma once

#include "PA_GlobalCharacterInterface.generated.h"

/**
 * 
 */
UINTERFACE(MinimalAPI)
class UPA_GlobalCharacterInterface : public UInterface
{
	GENERATED_UINTERFACE_BODY()
};

class IPA_GlobalCharacterInterface
{
	GENERATED_IINTERFACE_BODY()

	virtual void UpdateScalarMaterialParameterSet(UMaterialParameterCollection* Collection, FName ParameterName, float Value);

};

Interface.cpp

#include "MyGame.h"

UPA_GlobalCharacterInterface::UPA_GlobalCharacterInterface(const class FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{

}

void IPA_GlobalCharacterInterface::UpdateScalarMaterialParameterSet(UMaterialParameterCollection* Collection, FName ParameterName, float Value)
{
	//empty implementation, consider adding the basic functionality here.
}

Implementation in my Character class:

void MyCharacter::UpdateScalarMaterialParameterSet(UMaterialParameterCollection* Collection, FName ParameterName, float Value)
{
	if (Collection)
	{
		UKismetMaterialLibrary::SetScalarParameterValue(GetWorld(), Collection, ParameterName, Value);
	}
}

And finally the functionality to call this function using the interface (for information on implementing interfaces, I recommend checking out Rama’s tutorials on it). This was the functionality that I put in the notify state:

	IPA_GlobalCharacterInterface* CharacterInterface = NULL;
	for (TObjectIterator<MyCharacter> It; It; ++It)
	{
		CharacterInterface = Cast<IPA_GlobalCharacterInterface>(*It);

		//Run the Event specific to the actor, if the actor has the interface
		if (CharacterInterface) CharacterInterface->UpdateScalarMaterialParameterSet(MaterialParameterCollection, ParameterName, AnimationValue);
	}

You can technically change the MyCharacter class to anything, even Actor (I just put MyCharacter since I really needed only that class, for performance reasons). I have tested it and it works. Of course, you need to be careful with calls from multiple classes, as they will all attempt to overwrite the parameter collections. Shouldn’t cause any errors, but will make animations look kinda weird. At least, in my case.