언리얼엔진

[C++] 레퍼런스 / StaticMesh, Material 값 할당

찬이2 2023. 9. 4. 21:29

저번 시간에 Cube클래스에서 Mesh 변수를 만들고 생성까지 했다.

이번에는 생성한 Mesh 변수 내부에 StaticMesh를 할당해줄 것이다.

 

Tip!!

* Ctrl + Shift + Space : VS에서 이 단축키를 통해 어떠한 값이 들어갈지 예시를 볼 수 있다.

 

* 파일 4개(Config, Content, Source, UE4 파일)를 압축했던 프로젝트를 다시 사용하려면, UE4 파일을 우클릭하여 솔루션    을 미리 만들어준 뒤, VS or 언리얼 에디터에서 꼭 한번은 빌드를 해주어야 한다.

 

* 클래스 내에서 ctor 치고 tab키를 누르면 자동으로 해당 클래스 struct가 완성됨

* 클래스 내에서 ~ 치고 tab키를 누르면 자동으로 해당 클래스의 소멸자가 완성됨


StaticMesh 할당하기

StaticMesh를 할당해주기 위해 먼저 언리얼 에디터에 만들어져 있는 StaticMesh 도형을 꺼내올 것이다.

그리고 그 StaticMesh 에셋을 참조하는 방식으로 해볼 것이다.

엔진 콘텐츠 - BasicShapes - Cube / BasicShape Material

엔진 콘텐츠 폴더 내의 BasicShapes 폴더에 있는 Cube(StaticMesh)와 BasicShape Material(Material)를 콘텐츠 폴더로

복사해준다.

 

그리고 복사해온 Cube 스태틱 메시를 우클릭 - 레퍼런스 복사를 하면 해당 스태틱 메시의 레퍼런스(주소)가 복사된다.

레퍼런스는 Content/ 의 경로를 기반으로 되어있다.

StaticMesh'/Game/Cube.Cube'

그래서 /Game/의 부분이 바로 Content/와 같다고 보면 된다. (ProjectDir/Content/ == /Game/)

앞에 StaticMesh는 해당 에셋의 유형을 나타내고 있다.

그리고 앞의 Cube는 PackageName을 뜻하고, 뒤의 Cube는 AssetName을 뜻한다고 보면 된다.


이제 복사한 레퍼런스를 통해 VS에서 StaticMesh를 할당해볼 것이다.

Cube클래스 ACube()생성자에서 Mesh 생성과 동시에 할당하게 된다.

ACube::ACube()
{
	PrimaryActorTick.bCanEverTick = true;

	Mesh = CreateDefaultSubobject<UStaticMeshComponent>("Cube");
	Mesh->SetStaticMesh(); //빈 ()부분에서 받아온 Mesh를 할당해줄 것이다.
}

 

생성자(Constructor)에서만 사용가능한 ConstructorHelpers를 사용하여 복사한 레퍼런스를 타고 StaticMesh를 받아온다.

ConstructorHelpers::FObjectFinder<UStaticMesh>();

() 안에 들어가는 값은 총 3가지가 있다.
1. & 복사생성자
2. && 이동생성자
3. TCHAR

그리고 여기선 복사나 이동 생성자를 따로 만들어둔 것이 없기 때문에 TCHAR를 대입해볼 것이다.


TCHAR에 대하여...

먼저 char는 문자를 말한다. TCHAR에 알아보기 전에 먼저 Character Set(문자 집합)은 두가지를 봐야한다.

- MBCS(Multi-Byte Character Set)

이는 우리가 일반적으로 사용하는 "문자열"을 말한다. char

 

- UNICODE

L"문자열"을 말하며 wchar_t를 사용한다.

 

만약 MBCS형식으로 사용하다가 UNICODE로 바꿨다면 "문자열" 앞에 전부 L을 붙여주어야 할까?

이런 문제가 발생하기 때문에 아래와 같이 따로 정의를 해준다.

//UNICODE로 정의되어 있다면 실행
#if defined _UNICODE
typedef wchar_t PCHAR;
#define PEXT(string) L ## String

//UNICODE 정의 조건이 아니라면(MBCS) 실행
#else 
typedef char PCHAR;
#define PEXT(string) String
#endif

* ##란? : #은 Characterize 매크로 인자를 문자열화 시켜주고, ##은 Concatenate 매크로 인자와 다른 내용을 연결해준다.

              그래서 L##String은 들어온 매크로 인자 앞에 L을 연결시켜주는 것이다. (L"문자열")


아무튼 그래서 앞서 ConstructorHelpers를 통해 StaticMesh를 받아오는데, TCHAR의 값 복사한 레퍼런스를 넣어준다.

ConstructorHelpers::FObjectFinder<UStaticMesh>(TEXT("StaticMesh'/Game/Cube.Cube'"));

여기서 TEXT()는 앞서 _UNICODE라면 앞에 L을 붙이고, MBCS라면 그대로 넘기는 방식대로 처리가 된다.

그래서 TEXT()를 이용하여 TCAHR를 받을 수 있는 것이다.

* TEXT()는 언리얼에서 미리 만들어진 매크로


이제 ConstructorHelpers에서 레퍼런스 주소로 받아온 값을 Finder에 넣어준다.

// Sets default values
ACube::ACube()
{
	PrimaryActorTick.bCanEverTick = true;
	
	//StaticMesh'/Game/Cube.Cube'

	ConstructorHelpers::FObjectFinder<UStaticMesh> Finder(TEXT("StaticMesh'/Game/Cube.Cube'"));

	if (Finder.Succeeded() == true)
	{
		Mesh = CreateDefaultSubobject<UStaticMeshComponent>("Cube");
		Mesh->SetStaticMesh(Finder.Object);
	}
}

그리고 if(Finder.Succeded() == true) 조건을 통해 Finder에 정상적으로 값이 할당됬는지를 판단할 수 있다.

true일 때에만 실행되서 Mesh->SetStaticMesh(); 안에 Finder.Object를 넣어줌으로써 할당이 된다.

 

이후 빌드하면 언리얼 에디터에서 Cube 클래스를 레벨에 올렸을 때 Cube의 StaticMesh 모양이 보이는 것을 볼 수 있다.


Material 할당해주기

이번엔 Cube 클래스에 Material을 할당해볼 것이다.

언리얼 에디터에서 아까 엔진 콘텐츠에서 복사했던 BasicShape Material(머티리얼)을 우클릭하여 레퍼런스 복사를 한다.

 

그리고 Cube클래스 생성자에서 아까와 비슷하게 Mesh->SetMaterial();을 통해 할당해준다.

Mesh->SetMaterial( , );

MaterialInterface를 받는다. Material의 최상위이다.
첫번째 인자는 Material이 몇번 슬롯에 들어갈지 정하는 것이다.
두번째 인자는 MaterialInterface를 받는다.

 

이번에도 ConstructorHelpers를 통해 복사한 레퍼런스를 받아온다.

이번에는 UMaterialInterface를 받아오며, TEXT에 아까 복사한 머티리얼의 레퍼런스를 넣었다.

ConstructorHelpers::FObjectFinder<UMaterialInterface> Finder2(TEXT("Material'/Game/BasicShapeMaterial.BasicShapeMaterial'"));
if (Finder2.Succeeded() == true)
{
	Mesh->SetMaterial(0, Finder2.Object);
}

Finder2가 성공적으로 할당됬다면, Mesh->SetMaterial();에 0번째 슬롯에 Finder2.Object를 할당했다.


그런데 이렇게 문자열에 의존하는 코드는 별로 좋지 않다고 배웠다. 따라서 위의 방식들을 권장하지는 않는다.

만약 레퍼런스의 경로가 바뀌게 된다면 바로 오류가 발생하기 때문이다.

따라서 다른 방법도 알아보자.

 

언리얼 에디터에서 만들었던 Cube 클래스를 우클릭 - Cube 기반 블루프린트 클래스 생성을 선택하여 BP_Cube를 만든다.

그러면 우리가 자주 봤던 BP_Cube 블루프린트가 만들어진다.

 

BP_Cube 블루프린트에서 Static Mesh Component를 본다면 디테일에 아무것도 없는 것을 볼 수 있다.


일단 먼저 Cube클래스에서 생성자말고 BeginPlay() 런타임 중에 로드하는 방법을 알아보자.

LoadObject를 사용하여 StaticMesh를 가져오는 것이다. 레퍼런스 주소는 아까 Cube 레퍼런스 주소와 동일하다.

// Called when the game starts or when spawned
void ACube::BeginPlay()
{
	Super::BeginPlay();
	
	//첫번째 방식
    LoadObject<UStaticMesh>(this, TEXT("StaticMesh'/Game/Cube.Cube'"));
    //두번째 방식
    StaticLoadObject();
}

StaticLoadObject();의 첫번째 인자 UClass는 블루프린트 변수의 클래스 레퍼런스 타입를 의미한다.

 

위 방식처럼 LoadObject 사용에는 두가지 방식이 있다.

LoadObject()는 받아올 형식 자체를 지정할 수 있는 경우에 사용한다.

StaticLoadObject()는 해당 형식에 대한 클래스 레퍼런스가 있거나, 형식을 직접 지정할 수 없는 경우에 사용한다.

* 직접 지정할 수 없는 형식이란, 블루프린트 클래스를 말한다.