언리얼 C++ 25.01.21

HyeonjungYun·2025년 1월 21일

언리얼 기초

목록 보기
8/23

Migrate

특정 프로젝트의 있는 에셋들을 다른 프로젝트로 옮기는 방법을 Migrate라고 한다.


옮기고 싶은 에셋이 있는 프로젝트로 들어가서 에셋이 들어있는 폴더를 우클릭하여 Migrate를 선택한다.


그 다음 옮기고 싶은 에셋들을 선택해준 뒤에 OK를 눌러준다.


그러면 기존에 에셋이 들어있는 폴더들이 전부 비워진 것을 확인할 수 있는데 이는 Migrate가 복사 붙여넣기가 아닌 파일을 이동시키는 것이기 때문이다.

언리얼 프로젝트 기본 맵 설정

언리얼 프로젝트를 열었을 때 열리는 기본 맵을 바꾸는 방법이다.


위에 Edit항목에서 Project Settings를 선택한다.


그 다음 왼쪽에서 Maps & Modes를 선택한 뒤 Default Maps에서 빨간 테두리가 쳐진 곳에 기본으로 설정할 맵을 골라주면 된다.

C++ 클래스 생성하는 방법


좌측 상단에서 Tools를 선택한 후 New C++ Class를 선택한다.


그 다음 생성하고 싶은 클래스를 선택한 후에 Next를 누른다.


생성할 클래스를 Public으로 만들지 Private로 만들지 선택가능한데
Public으로 생성하면 헤더 파일을 비주얼 스튜디오 내의 Public에서 cpp파일을 Private에서 생성시킨다.
Private로 생성하면 헤더파일과 cpp파일 모두 Private에서 생성시킨다.

생성하고 나면 언리얼 에디터의 콘텐츠 브라우저에 C++ Classes 폴더가 생성된다.


그리고 비주얼 스튜디오가 켜지는데 여기서 파일의 변화가 생겨 로드 유무를 묻는 팝업창이 뜰텐데 "모두 다시 로드"를 선택해주면 된다.


그러면 클래스가 새로 생성된 것이 보인다.
Public안에 있는 헤더파일은 다른 모듈에서 참조가 가능하지만 Private에 있는 헤더파일은 참조가 불가능해진다.


언리얼 에디터의 C++ Classes 폴더에도 새로 만든 클래스가 생성된 것이 보인다.


새로 만든 클래스를 맵에 끌어다 놓으면 맵에 액터 인스턴스가 새로 생긴 것을 볼 수 있다.


오른쪽에 아웃라이너를 보면 맵에 존재하는 모든 오브젝트를 표시해주는데, 여기서도 새로 만든 인스턴스가 추가된 것을 볼 수 있다.

클래스 삭제하는 법

이제 새로 생성한 클래스를 삭제하는 법을 알아보자.


먼저 언리얼 에디터에서 새로 만든 클래스를 우클릭하여 삭제해보려고 해도 삭제할 수 있는 항목이 보이지 않는다.

우리가 생성한 C++클래스를 삭제하기 위해선 비주얼 스튜디오 내에서 한 번 실제 폴더 내에서 한 번, 총 두 번 지워줘야 한다.


먼저 비주얼 스튜디오 내의 가상 구조에서 우리가 생성한 헤더 파일과 cpp파일들을 전부 제거해준다.



그리고 실제 프로젝트 폴더에 들어가보면 우리가 생성한 헤더파일과 cpp파일이 그대로 남아있는 것을 볼 수 있다. 이 파일들도 전부 제거해주어야 한다.

그리고 비주얼 스튜디오에서 빌드를 해주고 언리얼 에디터를 껐다가 다시 켜주어서 새로 빌드된 사항들을 반영시켜주면 C++ Classes 폴더가 사라진 것을 볼 수 있다.

C++ 클래스를 삭제하는 과정에서 이러한 과정들이 하나라도 누락되면 빌드 꼬임 이슈가 발생할 수도 있기 때문에 주의해야 한다.

액터에 컴포넌트 붙이기


먼저 아까 전에 생성했던 C++ 액터 클래스를 다시 만들어준다.


헤더파일을 보면 맨 윗줄에 pragam once를 볼 수 있는데 원래 C++의 헤더파일에선 중복 컴파일 문제를 막기 위해 #ifndef #define #endif등을 사용했지만 언리얼 내에선 pragma once를 사용한다.

CoreMinimal.h은 언리얼 내의 기본적인 타입(스트링 및 기본적인 함수)등을 포함하는 헤더파일이다.
GameFramework/Actor.h는 생성한 클래스가 Actor를 상속받는 클래스이기 때문에 Actor클래스에 대한 정보를 가지고 있는 헤더파일이다.
Item.generated.h는 리플렉션 시스템을 알아야 이해할 수 있다.
리플렉션 시스템은 우리가 작성한 코드를 블루프린트 형태로 볼 수 있게 해주는 시스템을 말한다.

UCLASS()
GENERATED_BODY()

UCLASS()와 GENERATED_BODY는 리플렉션과 관련이 있는 코드로 리플렉션 시스템에 등록하겠다는 의미를 가지고 있다.

class SPARTAPROJECT_API AItem : public AActor

생성된 클래스 이름과 상속받고 있는 액터 클래스의 이름을 보면 A가 붙은 걸 볼 수 있다. 이는 언리얼 프로젝트에서는 클래스 명 앞에 접두어를 붙이길 권장하고 있기 때문이다.
접두어 A가 붙어있으면 Actor계열임을 의미한다.

클래스 선언을 보면 SPARTAPROJECT_API를 볼 수 있는데 이것은 클래스를 모듈 밖으로 내보내기 위한 일종의 매크로라고 볼 수 있다. 빌드할 때 중요한 매크로로 작용한다.

모듈이란 Private, Public폴더가 들어있는 비주얼 스튜디오 내의 가상구조의 폴더 단위를 모듈이라고 할 수 있다.


AItem()함수는 생성자를 뜻하고 BeginPlay()와 Tick()은 Actor가 가지고 있는 라이프 사이클 함수이다.

PrimaryActorTick.bCanEverTick = true;은 Tick함수와 관련되어 있는 선언이다.
아래엔 BeginPlay와 Tick의 정의부가 있는 것을 확인할 수 있다.


언리얼 에디터에서 C++클래스로 생성한 액터를 뷰포트로 끌어서 놔보면 아무런 외형을 가지지 않고 원하는 위치에 놓아도 항상 맵의 원점에 액터 인스턴스가 생긴다는 것을 확인할 수 있다.

이것은 액터가 좌표계를 제대로 인식하지 못 하고 있다는 뜻으로 루트 컴포넌트가 없다는 것을 의미한다.

최소한 액터는 하나 이상의 가장 조상이 되는 컴포넌트 즉 루트 컴포넌트가 존재해야 좌표 인식도 가능해지고 제대로 된 액터로서 기능할 수 있게된다.

언리얼 에디터에서 블루프린트 클래스로 액터를 생성해보면 이미 루트 컴포넌트가 생성이 되어 있는 것을 볼 수 있다.
이 루트 컴포넌트가 존재하기 때문에 블루프린트 액터 클래스를 뷰포트로 옮겨도 원하는 위치에 액터를 위치시킬 수 있다.

이제 이 루트 컴포넌트 밑으로 컴포넌트들을 추가시켜 액터에 기능을 추가할 수 있는 것이다.

어떤 컴포넌트가 루트 컴포넌트가 될 수 있을까?

그렇다면 어떤 컴포넌트를 루트 컴포넌트로 설정해야 할까?

보통 Scene컴포넌트를 루트 컴포넌트로써 많이 활용하는데 씬 컴포넌트는 시각적 정보는 담지 않지만, 트랜스폼 정보를 포함하고 있어서 많은 컴포넌트를 포함시킬 수 있는 루트 컴포넌트로써 최적화 되어있다고 할 수 있다.


외형을 추가 시키기 위해 블루프린트 액터 클래스에 스태틱 메쉬 컴포넌트를 추가해보자.

스태틱 메쉬 컴포넌트에서 메쉬는 모델을 의미한다. 스태틱 메쉬란 고정된 모델이라는 의미를 가지는데 건물, 나무 등 고정된 물체들을 스태틱 메쉬로 설정하고 인물 같이 움직이는 물체들은 스켈레탈 메쉬로 설정한다.


위의 사진처럼 스태틱 메쉬와 메테리얼을 언리얼 에디터 상에서 추가시킬 수 있다.

언링러 에디터에서 했던 것처럼 루트 컴포넌트를 설정하고 스태틱 메쉬 컴포넌트를 추가하여 외형과 메테리얼을 입히는 것을 C++코드로 할 줄 알아야 한다.

스태틱 메쉬 컴포넌트 C++코드로 입혀주기



먼저 BeginPlay()와 Tick()함수는 지금 시점에선 사용하지 않을 것이기 때문에 지워준다.

// Item.h
#pragma once

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

UCLASS()
class SPARTAPROJECT_API AItem : public AActor
{
	GENERATED_BODY()
	
public:	
	AItem();

protected:
	USceneComponent* SceneRoot;		// 씬 루트 컴포넌트
	UStaticMeshComponent* StaticMeshComp;	// 스태틱 메쉬 컴포넌트

};

위는 Item의 헤더파일로 씬 루트 컴포넌트와 스태틱 메쉬 컴포넌트를 포인터 멤버변수로 선언해주었다.

이제 이 둘을 cpp파일의 생성자에서 값을 대입시켜주면 된다.

먼저 씬 루트 컴포넌트를 만들어 멤버 변수에 대입해보자

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

AItem::AItem()
{
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));		
	SetRootComponent(SceneRoot);
}

CreateDefaultSubobject는 컴포넌트를 생성해주는 함수이며 생성할 컴포넌트의 타입과 컴포넌트의 이름이 필요하다. 템플릿 함수로서 템플릿 안에 생성할 컴포넌트를 보내며 매개 변수로 컴포넌트의 이름을 받는다.
SetRootComponent함수로 루트 컴포넌트를 설정한다.

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

AItem::AItem()
{
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));		
	SetRootComponent(SceneRoot);

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh")); 
	StaticMeshComp->SetupAttachment(SceneRoot);	
}

아래엔 메쉬 컴포넌트를 추가해준 것이다. 씬 루트 컴포넌트와 똑같이 컴포넌트를 생성하여 포인터 멤버 변수에 대입시켜주고 SetupAttachment함수를 이용해 씬 루트 컴포넌트 밑으로 스태틱 메쉬 컴포넌트를 붙여주었다.

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

AItem::AItem()
{
	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));		
	SetRootComponent(SceneRoot);

	StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh")); 
	StaticMeshComp->SetupAttachment(SceneRoot);	    
}


이제 코드를 빌드하여 언리얼 에디터로 들어가서 Item 액터 클래스를 뷰포트에 끌어 놓으면 원하는 위치에 액터가 생성되는 모습을 볼 수 있다.


그리고 디테일 바를 확인하면 루트 컴포넌트가 생성된 것을 확인할 수 있는데 루트 컴포넌트 밑에 붙인 스태틱 메쉬 컴포넌트를 확인할 수 없다.
C++코드 상으로 추가한 스태틱 메쉬 컴포넌트가 언리얼 에디터 상에서 보이게 하려면 리플렉션 시스템이란 것이 필요하다.

루트 컴포넌트가 에디터 상에서 보이는 이유는 루트 컴포넌트는 무조건 리플렉션 시스템에 등록 되기 때문에 루트 컴포넌트를 연결해주기만 하면 루트 컴포넌트는 언리얼 에디터 상에서 무조건 보인다.


이제 추가하고 싶은 스태틱 메쉬 컴포넌트와 메테리얼을 고른 후 우클릭 하여 Copy Reference를 선택하여 고른 에셋의 위치를 복사해준다.


복사한 것을 붙여넣기 해주면 위와 같이 나오는데 ' '안에 있는 주소만 필요하다. ' '안에 가장 앞에 있는 Game폴더는 콘텐츠 브라우저에 나오는 Content폴더를 의미한다.

// Item.cpp
#include "Item.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())
	{
		StaticMeshComp->SetStaticMesh(MeshAsset.Object);
	}

	static ConstructorHelpers::FObjectFinder<UMaterial> MaterialAsset(TEXT("/Game/Resources/Materials/M_Metal_Brushed_Nickel.M_Metal_Brushed_Nickel"));
	if (MaterialAsset.Succeeded())
	{
		StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
	}
}

ConstructorHelpers::FObjectFinder는 말 그대로 오브젝트를 찾는 Finder다. 템플릿 안에 어떤 것을 찾을지 넣어주고 변수명을 선언해준다. 그리고 생성자 매개변수로 아까 복사한 에셋의 주소 ' ' 안의 것을 넣어주면 된다.

그리고 if문으로 에셋을 제대로 불러왔는지 확인한 후 잘 불러왔으면 스태틱 메쉬 컴포넌트에 에셋을 적용시킨다.

if (MaterialAsset.Succeeded())
{
	StaticMeshComp->SetMaterial(0, MaterialAsset.Object);
}

메테리얼 에셋을 적용하는 코드를 보면 SetMeterial의 매개변수에 0과 메테리얼 에셋이 포함된 것을 볼 수 있다.
0은 인덱스인데, 하나의 물체 여러가지 질감이 존재할 수 있는 것 처럼(신발같이) 하나나의 메쉬에도 여러 개의 메테리얼이 존재할 수 있다. 인덱스가 생성자의 매개변수로 포함되는 것은 메쉬에서 설정된 메테리얼 인덱스를 뜻한다.


이제 빌드해주고 언리얼 에디터를 켜보면 에셋이 적용된 C++액터 클래스가 보인다.

뷰포트에 올려보면 에셋이 제대로 적용된 모습을 확인할 수 있다.

공부 소감 및 느낀 점

C++코드로 에셋을 적용시켜 보기 전에 블루프린트로 에셋을 적용시켜 본 적이 있었는데 블루프린트 클래스로 적용시키는 것보다 더 어려운 것 같다.
아직 C++코드로 적용시키는 것에 이점을 파악하지 못 한 것 같아서 더 공부해봐야 할 것 같다.

profile
게임 프로그래머 공부

0개의 댓글