UE4 C++ SetUp 액터 메쉬와 머티리얼


Tags :


SetUp

확장도구 설치

  • UnrealMacroGenerator
    Unreal 리플렉션 생성 툴 - 나열된 리플렉션들을 선택하면서 자동으로 키워드를 생성해주는 편리한 기능! 다운로드 후 Alt + W 를 통해 사용
    01

  • ReSharper
    UHT, 리플렉션 등 intelliSence 구문분석이 너무 오래 걸리는 상활을 도와주는 유료 툴
    코드 입력 시 키워드, 리플렉션 등을 추천해주는 똑똑지니어스한 기능을 가지고 있다
    02
    (선생님께서 추천해 주셨지만 유료상품이라… 좀 더 복잡한 함수와 키워드를 사용하게 될 때 구매를 고민해 보자…)

모듈 디렉토리 포함

Source폴더의 파일에 추가하는 #include 헤더를 상대경로로 입력해야(예. ../Source/header.h) 인식되기 때문에 모듈경로를 절대경로로 설정하는게 좋다.
Source\프로젝트명폴더\프로젝트명.Build.cs 파일 public class U03_Basic : ModuleRules에 아래 내용 추가

    // 헤더 포함 시 상대경로가 아닌 모듈 기준의 경로로 입력가능
    PublicIncludePaths.Add(ModuleDirectory);

03


C++클래스 액터 생성

C++클래스로 생성된 액터에 각 다른 리플렉션으로 선언하는 변수들이 언리얼 에디터에 어떻게 보여지는 지 확인해보자
04

헤더

C01_Property.h에 아래 변수들 선언하기

private:
	// 블프 & 레벨(뷰포트) 어디서든 수정편집가능
	UPROPERTY(EditAnywhere)
		int32 A = 10;

	// 인스턴스화되었을 때만(뷰포트에 스폰되었을 때만) 수정편집가능 (초기화하지 않으면 기본값 = 0)
	UPROPERTY(EditInstanceOnly)
		int32 B;

	// 블프에서만 수정편집 가능
	UPROPERTY(EditDefaultsOnly)
		int32 C;

	// 블프 & 뷰포트에서 보임 (수정불가)
	UPROPERTY(VisibleAnywhere, Category = "Property")
		float D = 100.0f;

	// 상속받는 클래스에서 수정편집가능
protected:
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Property")
		int Variable = 10;

소스

C01_Property.cpp 생성자에 변수 B,C 정의

AC01_Property::AC01_Property()
	:B(20)
{
 	// 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;

	C = 30;
}

c++클래스 액터 C01_Property기반 블루프린트 클래스 생성(파생) 후 뷰포트 레벨에 올려졌을 때,
변수 BC는 각각 뷰포트, 블루프린트에디터에서만 보여지고 수정편집이 가능하다
05

BeginPlay & Tick

아래와 같이 C01_Property.cppBeginPlay()에 Log출력할 Fstring str 선언 정의하기

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

	FString str;
	str.Append(" / A : ");
	str.Append(FString::FromInt(A));

	str.Append(" / B : ");
	str.Append(FString::FromInt(B));

	str.Append(" / C : ");
	str.Append(FString::FromInt(C));

	// string str --> str.c_str() = *str
	GLog->Log(*str);
}

Tick에 변수D를 매 프레임의 DT씩 증가시켜주기

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

	D += DeltaTime;
}

컴파일 후 실행 시 Log가 출력되고 월드아웃라이너의 C01_Property의 디테일에 변수D가 증가하고 있는 모습을 확인할 수 있다.
06


메쉬와 머티리얼 추가

액터에 메쉬와 머티리얼 추가하기 위해 C02_Mesh 클래스 생성, Tick이벤트는 사용하지 않을 예정이라 Tick함수 삭제

Mesh

C02_Mesh.h에 Mesh정보를 담을 컴포넌트 선언

protected:
	// 컴포넌트의 경우 속성을 VisibleAnywhere로 한다
	UPROPERTY(VisibleAnywhere)
		class UStaticMeshComponent* Mesh;

C02_Mesh.cpp에 StaticMesh관련 함수를 사용하기 위해 헤더 추가

#include "Components/StaticMeshComponent.h"

생성자에서 SetRootComponent & SetStaticMesh

	// 블프에서는 자동생성되는 DefaultRootSceen이 C++로 만들 땐 Null로 생성되기때문에 RootComponent를 정의해줘야 함
	Mesh = CreateDefaultSubobject<UStaticMeshComponent>("Mesh");
	RootComponent = Mesh;

    // SetStaticMesh
    ConstructorHelpers::FObjectFinder<UStaticMesh> mesh(L"StaticMesh'/Game/Meshes/Cube.Cube'");
	if (mesh.Succeeded())
		Mesh->SetStaticMesh(mesh.Object);

빌드 후 언리얼 에디터에서 Cube Mesh가 적용된 C02_Mesh를 확인 할 수 있다
(C02_Mesh기반 블루프린트클래스 생성을 통해 디테일 확인 가능)
07

Material

C02_Mesh.h에 Material Instance Class를 담을 변수 선언

private:
	// Material Instance는 Dynamic일 때 수정이 가능하다
	class UMaterialInstanceDynamic* Material;

C02_Mesh.cpp에 Material관련 헤더 추가

#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialInstanceDynamic.h"

BeginPlay에 Material 생성

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

    // DynamicInstance는 Ingame에서만 생성할 수 있다
	UObject* obj = StaticLoadObject(UMaterialInstanceConstant::StaticClass(), nullptr, L"MaterialInstanceConstant'/Game/Materials/M_White_Inst.M_White_Inst'");
	UMaterialInstanceConstant * material = Cast<UMaterialInstanceConstant>(obj);
	Material = UMaterialInstanceDynamic::Create(material, this);
	Mesh->SetMaterial(0, Material);
}

블프에서 캐릭터에 Dynamic Material Instance 생성할 때 와 비교해보면 같은 내용인 걸 확인할 수 있다
08
여기서 BeginPlay가 수정되었기 때문에 빌드&컴파일을 하면 언리얼에디터가 오류나면서 강제종료 될 수도 있다
C02_Mesh기반 블루프린트클래스 생성된 상태에서 BeginPlay의 내용이 변동됐기 때문에 지속해서 오류가 발생하는 경우인듯 하다
당황하지 말고 침착하게 uproject를 다시 실행하고 BP_C02_Mesh 삭제 후 다시 생성한 뒤에,
뷰포트에 올려보면 다음과 같이 Dynamic Material Instance가 실행 중일 때만 적용된 걸 확인할 수 있다
09

CHelpers.h & Global.h

반복적으로 사용할 함수는 Template으로 활용하면 편리하다
Source 폴더에 Global.h를 생성하고, 새 폴더 UtilitiesCHelpers.h를 생성한다

C02_Mesh.hCHelpers.h 헤더 포함하기

#include "Utilities/CHelpers.h"

C02_Mesh.cppGlobal.h 헤더 포함하기

#include "Global.h"
  • CHelpers.h
    • SetRootComponent
        #pragma once
        #include "CoreMinimal.h"
      	
        class U03_BASIC_API CHelpers
        {
        public:
            template<typename T>
            static void CreateComponent(AActor* InActor, T** OutComponent, FName InName, USceneComponent* InParent = nullptr)
            {
                *OutComponent = InActor->CreateDefaultSubobject<T>(InName);
                if (!!InParent)
                {
                    (*OutComponent)->SetupAttachment(InParent);
                    return;
                }
                InActor->SetRootComponent(*OutComponent);
            }
        };
      
        // 블프에서 자동생성되는 DefaultRootSceen이 C++로 만들 땐 Null로 생성되기때문에 RootComponent를 정의해줘야 함
        //Mesh = CreateDefaultSubobject<UStaticMeshComponent>("Mesh");
        //RootComponent = Mesh;
      	
        CHelpers::CreateComponent<UStaticMeshComponent>(this, &Mesh, "Mesh");
      
    • SetStaticMesh
        template<typename T>
        static void GetAsset(T** OutObject, FString InPath)
        {
            ConstructorHelpers::FObjectFinder<T> asset(*InPath);	//FString이라서 *
            *OutObject = asset.Object;
        }
      
        //ConstructorHelpers::FObjectFinder<UStaticMesh> mesh(L"StaticMesh'/Game/Meshes/Cube.Cube'");
        //if (mesh.Succeeded())
        //	Mesh->SetStaticMesh(mesh.Object);
      		
        UStaticMesh* mesh = nullptr;
        CHelpers::GetAsset(&mesh, "StaticMesh'/Game/Meshes/Cube.Cube'");
        Mesh->SetStaticMesh(mesh);
      
    • SetDynamicMaterialInstance
        template<typename T>
        static void GetAssetDynamic(T** OutObject, FString InPath)
        {
            *OutObject = Cast<T>(StaticLoadObject(T::StaticClass(), nullptr, *InPath));
        }
      
        // DynamicInstance는 Ingame에서만 생성할 수 있다
        // 예) BP_Enemy BeginPlay -> LoadMaterial() 함수 참조
        //UObject* obj = StaticLoadObject(UMaterialInstanceConstant::StaticClass(), nullptr, L"MaterialInstanceConstant'/Game/Materials/M_White_Inst.M_White_Inst'");
        //UMaterialInstanceConstant * material = Cast<UMaterialInstanceConstant>(obj);
        //Material = UMaterialInstanceDynamic::Create(material, this);
        //Mesh->SetMaterial(0, Material);
      		
        UMaterialInstanceConstant* material = nullptr;
        CHelpers::GetAssetDynamic<UMaterialInstanceConstant>(&material, "MaterialInstanceConstant'/Game/Materials/M_White_Inst.M_White_Inst'");
        Material = UMaterialInstanceDynamic::Create(material, this);
        Mesh->SetMaterial(0, Material);
      
  • Global.h
      #include "kismet/GameplayStatics.h"
      #include "kismet/KismetSystemLibrary.h"
      #include "kismet/KismetMathLibrary.h"
    
      #include "Utilities/CHelpers.h"
    

메쉬 & 머티리얼 활용

랜덤 머티리얼 색변환

C02_MeshSetRandomColor()함수 선언 & 정의

private:
	UFUNCTION()
		void SetRandomColor();
    ...BeginPlay...
	//Deligate함수선언 -> 1초마다 SetRandomColor
	UKismetSystemLibrary::K2_SetTimer(this, "SetRandomColor", 1.0f, true);

void AC02_Mesh::SetRandomColor()
{
	Material->SetVectorParameterValue("Color", FLinearColor::MakeRandomColor());
}

09_2

다양한 메쉬 적용

C02_Mesh 파생 C++클래스 생성을 통해 자식클래스들을 만들어 다른 Mesh를 적용시켜 보자
10

UE4_CPP_001