Change Material Parameters Using C++ in Unreal Engine 5
In this example we'll make a basic material with a couple parameters and update them with C++
Software Versions: Unreal Engine 5.5.0 | Rider 2024.3
Project Name: MyProject
Changing material parameters is pretty common so let's try it in C++.
Create a new actor class, I called mine MyActorChangeMatParam
. Add a static mesh component so we can update it's material, adding a scene component is optional. The DynamicMaterial
variable is our main focus in this example, it's a type UMaterialInstanceDynamic
and it'll be doing most of the heavy lifting. The two FName
variables are the parameters we'll be changing inside the material and the UpdateMaterial
will be updating the material, for simplicity we'll be randomizing the values if arguments are not provided.
MyActorChangeMatParam.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActorChangeMatParam.generated.h"
UCLASS()
class MYPROJECT_API AMyActorChangeMatParam : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActorChangeMatParam();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
UPROPERTY(EditAnywhere)
USceneComponent* Root;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* StaticMesh;
UPROPERTY(EditAnywhere)
UMaterialInstanceDynamic* DynamicMaterial;
UPROPERTY(EditAnywhere)
FName VectorParamName = FName("Color");
UPROPERTY(EditAnywhere)
FName ScalarParamName = FName("Emissive");
UFUNCTION(BlueprintCallable)
void UpdateMaterial(FVector Color = FVector(1.f, 1.f, 1.f), float Emissive = 0.f, bool Randomize = true);
};
In the .cpp
file, basic setup happens in the constructor, but the dynamic material has to be set in the BeginPlay
function. We write defensive conditionals, but throughout we get the static mesh's first material, then use UMaterialInstanceDynamic::Create
to create and set DynamicMaterial
, then finally we set StaticMesh
's material to the new dynamically created one.
The UpdateMaterial
function sets the FVector
and float
values for the M_Basic
material via DynamicMaterial
's SetVectorParameterValue
and SetScalarParameterValue
functions. We'll randomize the values in this example for quick results.
MyActorChangeMatParam..cpp
#include "MyActorChangeMatParam.h"
// Sets default values
AMyActorChangeMatParam::AMyActorChangeMatParam()
{
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
Root->bVisualizeComponent = true;
RootComponent = Root;
StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
StaticMesh->SetupAttachment(Root);
}
// Called when the game starts or when spawned
void AMyActorChangeMatParam::BeginPlay()
{
Super::BeginPlay();
if (StaticMesh != nullptr)
{
UMaterialInterface* Material = StaticMesh->GetMaterial(0);
if (Material != nullptr)
{
DynamicMaterial = UMaterialInstanceDynamic::Create(Material, StaticMesh);
if (DynamicMaterial != nullptr)
{
StaticMesh->SetMaterial(0, DynamicMaterial);
}
}
}
UpdateMaterial();
}
void AMyActorChangeMatParam::UpdateMaterial(FVector Color, float Emissive, bool Randomize)
{
if (DynamicMaterial != nullptr)
{
if (Randomize)
{
Color = FMath::VRand();
Emissive = FMath::FRand() * 10.f;
}
DynamicMaterial->SetVectorParameterValue(VectorParamName, Color);
DynamicMaterial->SetScalarParameterValue(ScalarParamName, Emissive);
}
}
The FName
properties defined in the header match the parameters in M_Basic
material. The M_Basic
material is very simple, it has a VectorParameter
called Color
for it's base color and a ScalarParameter
called Emissive
for it's emissive power.
We can now create a Blueprint actor inheriting from MyActorChangeMatParam
. Set the mesh to whatever you want and then set the material to M_Basic
. Drag the new actor into the world and on BeginPlay
the color and emissive will be randomly set.
To test out this feature I crated a simple trigger box that when the players enters or exits the box it'll loop through a TArray
of MyActorChangeMatParam
actors triggering their UpdateMaterial
function.
MyActorChangeMatParamTrigger.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActorChangeMatParamTrigger.generated.h"
class UBoxComponent;
class AMyActorChangeMatParam;
UCLASS()
class MYPROJECT_API AMyActorChangeMatParamTrigger : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AMyActorChangeMatParamTrigger();
UPROPERTY(EditAnywhere)
USceneComponent* Root;
UPROPERTY(EditAnywhere)
UBoxComponent* Box;
UPROPERTY(EditAnywhere)
TArray<AMyActorChangeMatParam*> MyMatActors;
UFUNCTION()
void OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
UFUNCTION()
void OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION()
void RandomizeParams();
};
MyActorChangeMatParamTrigger.cpp
#include "MyActorChangeMatParamTrigger.h"
#include "Components/BoxComponent.h"
#include "MyActorChangeMatParam.h"
// Sets default values
AMyActorChangeMatParamTrigger::AMyActorChangeMatParamTrigger()
{
Root = CreateDefaultSubobject<USceneComponent>(TEXT("Roort"));
Root->bVisualizeComponent = true;
RootComponent = Root;
Box = CreateDefaultSubobject<UBoxComponent>(TEXT("Box"));
Box->SetupAttachment(Root);
Box->OnComponentBeginOverlap.AddDynamic(this, &AMyActorChangeMatParamTrigger::OnBeginOverlap);
Box->OnComponentEndOverlap.AddDynamic(this, &AMyActorChangeMatParamTrigger::OnEndOverlap);
}
void AMyActorChangeMatParamTrigger::OnBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
RandomizeParams();
}
void AMyActorChangeMatParamTrigger::OnEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
RandomizeParams();
}
void AMyActorChangeMatParamTrigger::RandomizeParams()
{
for (AMyActorChangeMatParam* MatActor : MyMatActors)
{
MatActor->UpdateMaterial();
}
}
Below is the final result.
I had fun putting this together, I hope this helps.
Harrison McGuire