본문 바로가기

게임 개발 공부/언리얼엔진

UE5 | C++개발학습 - 액터클래스 생성 및 컴포넌트 그리고 라이프사이클

컴포넌트란 성격일수도 있고 외형일수도 있고 재능일수도 있다.

존재만 하는 단순 루트 컴포넌트만 있을 수도 있고 메시나 오디오 또는 이펙트 등의 느낄 수 있는 컴포넌트도 있다.

컴포넌트를 붙여주면 액터에게 개성이 생기고 쓸모가 생기기 시작한다.

허전한 것엔 컴포넌트를 붙여 쓸모있게 유용하게 만들어주자.


 

1. UObject

- 모든 클래스의 최상위 부모. 스스로 월드에 배치될 수 없다. 화면에 보이지 않는 추상적인 부분을 처리한다.

 

2. AActor

- UObject를 확장(상속)한 클래스. 배치가능한 모든 것들을 말한다. 연극으로 치면 무대에서 관객들에게 보여지고 들려지는 모든 것일 수 있겠다.

 

3. Actor 기본 헤더

- 언리얼엔진 에디터 내에서 클래스 생성을 실행하면 기본 프레임이 갖춰진 헤더파일과 소스파일이 생성된다. 그리고 헤더 또한 기본으로 입력되는데 기본 헤더는 다음과 같다.

#include "CoreMinimal.h"               // 로그함수 및 언리얼 기본 타입들이 포함된 헤더
#include "GameFramework/Actor.h"       // 상속받은 Actor클래스에 대한 정보가 담긴 헤더
#include "MyActor.generated.h"         // 리플렉션 시스템 등을 포함. 항상 마지막에 있어야 한다.

 

4. 클래스별 접두어(예시)

- Actor : A (AActor)

- Object : U (UObject)

- 구조체 : F (FVector)

- Enum : E (EEndPlayReason)

 

5. 컴포넌트 만들기

//======헤더파일에서

UCLASS()
//^해당 클래스를 리플렉스 시스템에 등록하는 매크로
class MYPROJECT_API AMyActor : public AActor
{  // ^프로젝트명_API  ^클래스명         ^액터 클래스 상속
    GENERATED_BODY()
//    ^UCLASS와 쌍으로 리플렉션 데이터를 자동으로 생성하는 매크로
    
public:
   AMyActor();
//    ^ 생성자
    
protected:
    
USceneComponent* (SceneRoot);
//^씬 컴포넌트 포인터 ^루트로 지정할 컴포넌트 이름

UStaticMeshComponenet* (StaticMeshComp);
//^스태틱메시 컴포넌트 포인터 ^스태틱메시 컴포넌트 이름

};

//======소스파일에서

AMyActor::AMyActor()
{//^클래스명   ^생성자명

SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
//^헤더 내 선언변수  ^컴포넌트 생성 함수   ^컴포넌트 타입        ^컴포넌트 이름

SetRootComponent(SceneRoot);
//^루트 지정 함수  ^지정할 컴포넌트 이름

StaticMeshComp = CreateDefualtSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
// 1줄과 동일구조

StaticMeshComp->SetAttachment(SceneRoot);
//^붙일 컴포넌트  ^컴포넌트 붙이기 ^목표 컴포넌트 이름

//위 컴포넌트에 넣을 메쉬선언
static CostructorHelpers::FObjectFinder<UStaticMesh> MeshAsset(TEXT("Game/에디터 내 경로/MyMesh.MyMesh"));
//^에셋 리소스를 찾아서 로드하는 클래스       ^에셋 타입 ^오브젝트변수이름      ^로드할 에셋 주소

if(MeshAsset.Succeeded())
{// ^로드 성공 확인
    StaticMeshComp->SetStaticMesh(MeshAsset.Object);
} //^에셋을 넣을 컴포넌트 ^스태틱메시를 넣어주는 함수(메시 이름과 FObjectFinder로 찾은 오브젝트의 포인터) 

}

- 루트 컴포넌트를 만들어주고 붙일 컴포넌트를 생성해 붙인 뒤, 컴포넌트에 넣을 에셋 등을 적용해주는 순서다.

 

 

6. 커스텀 로그 선언

//====헤더파일 상단
DECLARE_LOG_CATEGORY_EXTERN(LogTemp, Warning, All);
                           //^로그명, 로그최소수준, 로그최대수준
                           
//====소스파일 상단
DEFINE_LOG_CATEGORY(LogTemp)
                   //^로그명

//로그 출력
UE_LOG(LogTemp, Warning, TEXT("Log Message");
//^로그함수, 로그명, 로그수준, 로그 메시지내용

- 로그 카테고리명 과 로그 수준을 직접 만들 수 있다.

- 로그 카테고리는 보통 별도의 헤더로 관리하는 경우가 많다.

 

7. 액터 라이프사이클 함수

- 라이프 사이클 : 액터가 소(Spawn)되었다가 소멸(Destroy)되는 과정의 주기

//-탄생단계

AMyActor();
// 생성자. 오브젝트가 메모리에 생성. 딱한번 호출. 액터 생성 전 여러가지 초기화 단계

PostInitializeComponents();
// 액터 컴포넌트들이 초기화된 직후 자동 호출. 컴포넌트끼리 월드 등장 전 데이터주고받기 또는 상호작용이 필요할때 유용

BeginPlay();
// 배치(Spwan) 직후 (플레이 시작)


//-생단계

Tick(float DelaTime);
// 매 프레임마다 호출


//-죽음단계

Destroyed();
//삭제가 되기 직전 호출. 리소스 정리할 때.

EndPlay(const EEndPlayReason::Type EndPlayReason);
//게임 종료, 파괴(Destroyed()), 레벨 전환

- 부모클래스에 상속되어있는 기능들이기 때문에 Super::(함수명)으로 사용한다.

 

8. Super::

//헤더파일에서
virtual void BeginPlay();

//소스파일에서
void MyActor::BeginPlay()
{
   Super::BeginPlay();
}

- Super::는 부모클래스로부터 해당 기능을 받아오는 기능이다.

BeginPlay를 예로 들자면 아버지가 아들에게 "시작!" 하고 신호를 알려주면 그 신호에 맞춰 작업을 시작할 수 있다.

Destroyed를 예로 들자면 아버지가 아들에게 "이렇게 삭제하는거란다"하고 알려주면 아들이 그 방법대로 삭제하는 것이다.

 


 

그 전까진 항상 에디터를 이용해서만 액터를 생성해봤었는데 오늘 처음으로 C++코드를 이용해서 제대로 된 액터를 구현해봤다.

블루프린트를 이용한 액터생성작업이 저렴하고 사기 쉽지만 어딘가 불편한 기성복이라면

C++을 이용한 액터생성작업은 비싸고 맞추기 어렵지만 몸에 딱 맞출 수 있는 맞춤정장의 느낌인 것 같다.

그만큼 c++을 이용하면 디테일한 개성이 있는 액터를 만들어 낼 수 있을 것 같다.