Change Material Mesh

January 27, 2018

UE4 Version: 4.18.3

Github Link: https://github.com/Harrison1/unrealcpp/tree/master/ChangeMaterialMesh

For this tutorial we are using the standard first person C++ template with starter content.

In this tutorial we will change a static mesh's material on overlap. Create a new actor class and call it whatever you want, in this tutorial I will call it ChangeMaterialMesh.

First, in the .h file we will create a UStaticMeshComponent, two UMaterial classes, and a UBoxComponent. Add the elements to the public section of the header file.

setup header file.

...
UPROPERTY(VisibleAnywhere)
class UStaticMeshComponent* MyMesh;

UPROPERTY(EditAnywhere)
class UMaterial* OnMaterial;

UPROPERTY(EditAnywhere)
class UMaterial* OffMaterial;

UPROPERTY()
class UBoxComponent* MyBoxComponent;

UFUNCTION()
void OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

Now we'll move into the .cpp file. First, include DrawDebugHelpers.h and Components/BoxComponent.h files at the top so we can visualize and use our collison box.

include debug helpers

// include draw debug helpers header file
#include "DrawDebugHelpers.h"
#include "Components/BoxComponent.h"

Next, we'll set up our constructor function and set our default values. Create the static mesh using CreateDefaultSubobject<UStaticMeshComponent> and set it as the RootComponent. Then, create the box component by using CreateDefaultSubobject<UBoxComponent> and we'll set it's extent to FVector(100,100,100) by using InitBoxExtent. The box component will init with a collision profile name of Trigger and will be attached to the RootComponent. Next, create two materials for the mesh to switch between, establish a default value for the bool, and finally connect the overlap function. Below is the constructor code.

Constructor

AChangeMaterialMesh::AChangeMaterialMesh()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
	RootComponent = MyMesh;

	MyBoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("MyBoxComponent"));
	MyBoxComponent->InitBoxExtent(FVector(100,100,100));
	MyBoxComponent->SetCollisionProfileName("Trigger");
	MyBoxComponent->SetupAttachment(RootComponent);

	OnMaterial = CreateDefaultSubobject<UMaterial>(TEXT("OnMaterial"));
	OffMaterial = CreateDefaultSubobject<UMaterial>(TEXT("OffMaterial"));

	MyBoxComponent->OnComponentBeginOverlap.AddDynamic(this, &AChangeMaterialMesh::OnOverlapBegin);

}

Next, in the BeginPlay() method we will draw our debug box with DrawDebugBox and set the first material for our mesh with SetMaterial. Below is the BeginPlay() code.

BeginPlay()

void AChangeMaterialMesh::BeginPlay()
{
	Super::BeginPlay();

	DrawDebugBox(GetWorld(), GetActorLocation(), FVector(100,100,100), FColor::White, true, -1, 0, 10);

	MyMesh->SetMaterial(0, OffMaterial);
	
}

Now, we'll create the overlap function that will change the mesh's material. We will check if the OtherActor is not null, and if the OtherActor is not the same actor, and if the OtherComp is not null. If everything passes, we will call SetMaterial and pass in the new material and set it as the first material for the mesh. Below is the overlap function.

overlap function

void AChangeMaterialMesh::OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) 
{
	if ( (OtherActor != nullptr ) && (OtherActor != this) && ( OtherComp != nullptr ) ) 
	{
		MyMesh->SetMaterial(0, OnMaterial);
	}
}

Compile the code. Drag and drop the actor into the game world. Add a mesh in the details panel and add two materials to the Actor (the materials are set in the partent's (Instance) details panel. Now when you push play the mesh will change materials when overlapped. Below is the final code.

ChangeMaterialMesh.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ChangeMaterialMesh.generated.h"

UCLASS()
class UNREALCPP_API AChangeMaterialMesh : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AChangeMaterialMesh();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	class UStaticMeshComponent* MyMesh;

	UPROPERTY(EditAnywhere)
	class UMaterial* OnMaterial;

	UPROPERTY(EditAnywhere)
	class UMaterial* OffMaterial;

	UPROPERTY()
	class UBoxComponent* MyBoxComponent;

	UFUNCTION()
	void OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);
	
};

ChangeMaterialMesh.cpp

#include "ChangeMaterialMesh.h"
// include draw debug helpers header file
#include "DrawDebugHelpers.h"
#include "Components/BoxComponent.h"



// Sets default values
AChangeMaterialMesh::AChangeMaterialMesh()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	MyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
	RootComponent = MyMesh;

	MyBoxComponent = CreateDefaultSubobject<UBoxComponent>(TEXT("MyBoxComponent"));
	MyBoxComponent->InitBoxExtent(FVector(100,100,100));
	MyBoxComponent->SetCollisionProfileName("Trigger");
	MyBoxComponent->SetupAttachment(RootComponent);

	OnMaterial = CreateDefaultSubobject<UMaterial>(TEXT("OnMaterial"));
	OffMaterial = CreateDefaultSubobject<UMaterial>(TEXT("OffMaterial"));

	MyBoxComponent->OnComponentBeginOverlap.AddDynamic(this, &AChangeMaterialMesh::OnOverlapBegin);

}

// Called when the game starts or when spawned
void AChangeMaterialMesh::BeginPlay()
{
	Super::BeginPlay();

	DrawDebugBox(GetWorld(), GetActorLocation(), FVector(100,100,100), FColor::White, true, -1, 0, 10);

	MyMesh->SetMaterial(0, OffMaterial);
	
}

// Called every frame
void AChangeMaterialMesh::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AChangeMaterialMesh::OnOverlapBegin(class UPrimitiveComponent* OverlappedComp, class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) 
{
	if ( (OtherActor != nullptr ) && (OtherActor != this) && ( OtherComp != nullptr ) ) 
	{
		MyMesh->SetMaterial(0, OnMaterial);
	}
}

Author

Harrison McGuire

Harrison McGuire