컨텐츠 검색
[UE5 C++] 언리얼 엔진 c++ 로 액터 생성하기

2026. 1. 9. 20:58Unreal Engine/개념

지난 시간에 c++로 이동과 회전을 10번하는 액터를 만들었다.
그때 랜덤 이벤트로 머티리얼 자체를 변경하고 싶었는데 머티리얼 색상을 변경하는 선에서 그치고 말았다.
이게 맞는건가 긴가민가 하면서 썼는데 드디어 머티리얼을 어떻게 적용하는지에 대해 알게 되었다.

 

순서는 다음과 같이 진행했다.
C++ 클래스에 먼저 씬 컴포넌트를 생성하고 루트 컴포넌트로 지정했다.
그리고 스태틱 메시 컴포넌트를 생성해 씬 컴포넌트에 붙였다.
다음으로 스태틱 메시 에셋과 머티리얼 에셋을 불러와 스태틱 메시 컴포넌트에 설정했다.

// Item.h
#pragma once

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


UCLASS()
class PRACTICEUNREALCPP_API AItem : public AActor
{
    GENERATED_BODY()

public:    
    AItem(); // 생성자 선언

protected:
    USceneComponent* SceneRoot;
    UStaticMeshComponent* StaticMeshComp;


};
// Item.cpp
#include "Item.h"
#include "Engine/Engine.h"

AItem::AItem()
{
    // 씬 컴포넌트를 생성해 포인터에 연결한다.
    SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));

    // 씬 컴포넌트를 루트 컴포넌트로 한다.
    SetRootComponent(SceneRoot);

    // 스태틱 메시 컴포넌트를 생성해 포인터에 연결한다.
    StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));

    // 씬 컴포넌트에 스태틱 메시 컴포넌트를 붙인다.
    StaticMeshComp->SetupAttachment(SceneRoot);

    // 스태틱 메시 에셋을 불러와 스태틱 메시 컴포넌트에 설정한다.
    static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Chair.SM_Chair"));

    // 스태틱 메시 에셋을 성공적으로 불러왔으면 스태틱 메시 컴포넌트에 설정한다.
    if (MeshAsset.Succeeded())
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 메시를 불러왔습니다"));
        }

        StaticMeshComp->SetStaticMesh(MeshAsset.Object);
    }
    else
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("메시를 불러오지 못했습니다"));
        }
    }

    // 머티리얼 에셋을 불러와 스태틱 메시 컴포넌트에 설정한다.
    static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Potion_Antidote.M_Potion_Antidote"));

    // 머티리얼 에셋을 성공적으로 불러왔으면 스태틱 메시 컴포넌트에 설정한다.
    if (MaterialAsset.Succeeded())
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 에셋을 불러왔습니다"));
        }
        StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
    }
    else
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("에셋을 불러오지 못했습니다"));
        }
    }

}

내 머티리얼 어디갔냐...

머티리얼을 3번 바꿔 끼워봤는데 전부다 적용되지 않아서 디버그 메시지를 띄워봤다.

왜 불러오지 못한거냐 네녀석!!

혹시 C++에서 빌드를 하지 않고 라이브 코딩에서 컴파일해서 안되는건가 싶어서 언리얼 엔진을 종료하고 Visual Studio에서 부분 빌드 후에 다시 실행해봤다.

아니 라이브 코딩 너 만능 아니였냐고...
이제 일 잘하는군...

그리고 혹시나 싶어서 하나 더 테스트해봤는데 머티리얼 인스턴스의 경우에도 불러오지 못했다.

그래서 찾아보니 템플릿 타입을 UMaterial 대신 UMaterialInterface로 적용해야 한대서 코드를 일부 수정했더니 됐다.

static ConstructorHelpers::FObjectFinder<UMaterialInterface> MaterialAsset(TEXT("/Game/Resources/Materials/M_Key_B_Inst.M_Key_B_Inst"));

잘 되는군...

UMaterialInterface는 머티리얼 인스턴스만 되는건가 싶어서 머티리얼로 갈아끼워봤다.

static ConstructorHelpers::FObjectFinder<UMaterialInterface> MaterialAsset(TEXT("/Game/Resources/Materials/M_Ground_Moss.M_Ground_Moss"));

?? 머티리얼도 잘 들어가는데?

 

언리얼 엔진의 머티리얼 시스템이 궁금해져 알아보았다.

 

언리얼 엔진의 머티리얼 시스템은 다음과 같은 구조를 가진다.

  • UMaterialInterface를 통한 다형성 제공
  • UMaterial을 통한 셰이딩 로직 정의
  • Usage Flag를 통한 셰이더 순열 관리

UMaterialInterface는 언리얼 엔진의 머티리얼 시스템에서 추상화된 기본 클래스로, 렌더링 파이프라인이 구체적인 머티리얼 자산(UMaterial)이나 인스턴스(UMaterialInstance)를 구분하지 않고 공통적으로 접근할 수 있게 해주는 핵심 인터페이스 역할을 한다.

 

클래스 계층 구조

UObject
└── UMaterialInterface (추상 클래스)
     ├── UMaterial (셰이더 그래프 원본)
     └── UMaterialInstance (파라미터 오버라이드 데이터)
          ├── UMaterialInstanceConstant (에디터용, 정적)
          └── UMaterialInstanceDynamic (런타임용, 동적)

이말은 즉, UMaterialInterface에 UMaterial, UMaterialInstance가 각각 있어서

UMaterialInterface로 머티리얼 에셋과 머티리얼 인스턴스 에셋 모두 불러올 수 있다는 것이다.

 

정말 그런지 테스트 해보자.

하나는 UMaterial

다른 하나는 UMaterialInstance

마지막 하나는 UMaterialInterface 로 에셋을 불러왔다.

// Item.cpp
#include "Item.h"
#include "Engine/Engine.h"

AItem::AItem()
{
    // 씬 컴포넌트를 생성해 포인터에 연결한다.
    SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));

    // 씬 컴포넌트를 루트 컴포넌트로 한다.
    SetRootComponent(SceneRoot);

    // 스태틱 메시 컴포넌트를 생성해 포인터에 연결한다.
    StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));

    // 씬 컴포넌트에 스태틱 메시 컴포넌트를 붙인다.
    StaticMeshComp->SetupAttachment(SceneRoot);

    // 스태틱 메시 에셋을 불러와 스태틱 메시 컴포넌트에 설정한다.
    static ConstructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("/Game/Resources/Props/SM_Chair.SM_Chair"));

    // 스태틱 메시 에셋을 성공적으로 불러왔으면 스태틱 메시 컴포넌트에 설정한다.
    if (MeshAsset.Succeeded())
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 메시를 불러왔습니다"));
        }

        StaticMeshComp->SetStaticMesh(MeshAsset.Object);
    }
    else
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("메시를 불러오지 못했습니다"));
        }
    }

    // 머티리얼 에셋을 불러와 스태틱 메시 컴포넌트에 설정한다.
    static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Ground_Moss.M_Ground_Moss"));

    // 머티리얼 인스턴스 에셋을 불러와 스태틱 메시 컴포넌트에 설정한다.
    static ConstructorHelpers::FObjectFinder<UMaterialInstance> MaterialInstanceAsset(TEXT("/Game/Resources/Materials/M_Power_Sphere_Inst.M_Power_Sphere_Inst"));

    // 머티리얼 에셋 또는 머티리얼 인스턴스 에셋을 불러와 스태틱 메시 컴포넌트에 설정한다.
    static ConstructorHelpers::FObjectFinder<UMaterialInterface> MaterialInterfaceAsset(TEXT("/Game/Resources/Materials/M_Tech_Hex_Tile_Pulse.M_Tech_Hex_Tile_Pulse"));

    // 머티리얼 에셋을 성공적으로 불러왔으면 스태틱 메시 컴포넌트에 설정한다.
    if (MaterialAsset.Succeeded())
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 머티리얼 에셋을 불러왔습니다"));
        }
        StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
    }
    else
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("머티리얼 에셋을 불러오지 못했습니다"));
        }
    }

    // 머티리얼 인스턴스 에셋을 성공적으로 불러왔으면 스태틱 메시 컴포넌트에 설정한다.
    if (MaterialInstanceAsset.Succeeded())
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 머티리얼 인스턴스 에셋을 불러왔습니다"));
        }
        StaticMeshComp->SetMaterial(1, MaterialInstanceAsset.Object);
    }
    else
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("머티리얼 인스턴스 에셋을 불러오지 못했습니다"));
        }
    }

    // 머티리얼 에셋 또는 머티리얼 인스턴스 에셋을 성공적으로 불러왔으면 스태틱 메시 컴포넌트에 설정한다.
    if (MaterialInterfaceAsset.Succeeded())
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 머티리얼 에셋 또는 머티리얼 인스턴스 에셋을 불러왔습니다"));
        }
        StaticMeshComp->SetMaterial(2, MaterialInterfaceAsset.Object);
    }
    else
    {
        if (GEngine)
        {
            GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("머티리얼 에셋 또는 머티리얼 인스턴스 에셋을 불러오지 못했습니다"));
        }
    }

}

 

 

이후 사운드 큐, 사운드 웨이브도 적용해봤다.

#include "Components/AudioComponent.h"

// 오디오 컴포넌트를 생성해 포인터에 연결한다.
AudioComp = CreateDefaultSubobject<UAudioComponent>(TEXT("AudioComp"));

// 스태틱 메시 컴포넌트에 오디오 컴포넌트를 붙인다.
AudioComp->SetupAttachment(StaticMeshComp);
// 사운드 큐 할당
#include "Sound/SoundCue.h"

static ConstructorHelpers::FObjectFinder<USoundCue> AudioCueAsset(TEXT("/Game/Resources/Audio/Starter_Music_Cue.Starter_Music_Cue"));

if (SoundCueAsset.Succeeded())
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 사운드 큐 에셋을 불러왔습니다"));
    }
    AudioComp->SetSound(SoundCueAsset.Object);
}
else
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("오디오 사운드 에셋을 불러오지 못했습니다"));
    }
}
// 사운드 웨이브 에셋을 불러와 오디오 컴포넌트에 설정한다.
#include "Sound/SoundWave.h"

static ConstructorHelpers::FObjectFinder<USoundWave> SoundWaveAsset(TEXT("/Game/Resources/Audio/Starter_Birds01.Starter_Birds01"));

if (SoundWaveAsset.Succeeded())
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Blue, TEXT("성공적으로 사운드 웨이브 에셋을 불러왔습니다"));
    }
    AudioComp->SetSound(SoundWaveAsset.Object);
}
else
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 3.0f, FColor::Red, TEXT("사운드 웨이브 에셋을 불러오지 못했습니다"));
    }
}

 

소리는 마지막에 적용된 사운드 웨이브 에셋의 소리가 들렸다.