본문 바로가기
Game Engines/언리얼 엔진

언리얼 C++ 게임 개발의 정석 | Chapter 02 액터의 설계

by continue96 2022. 3. 28.

2장 액터의 설계

2.1 언리얼 콘텐츠의 구성 요소

2.1.1 월드

  • 뷰포트 윈도우에 보이는 작업 공간을 월드(world)라고 부른다.
  • 툴바의 세팅 > 월드 세팅 메뉴를 통해 확인할 수 있다.
    • 공간(space): 가상 세계를 구성하는 3차원의 영역을 의미한다. 기본 단위 cm다.
    • 시간(time): 가상공간에서 흐르는 초 단위 시간이다.
    • 물리(physics): 물체에 작용하는 물리적인 환경이다.
    • 렌더링(rendering): 엔진이 제공하는 빛과 머티리얼 기능이다.

 

 2.1.2 액터

  • 액터(actor)는 언리얼 엔진에서 콘텐츠를 구성하는 최소 단위의 물체다.
    • 이름(name): 액터에 부여된 명칭이다.
    • 유형(type): 게임 플레이에서 수행할 액터의 역할, 즉 클래스 이름이다.
    • 트랜스폼(transform): 액터가 가진 트랜스폼(위치, 회전, 크기)을 말한다.
    • 프로퍼티(property): 액터에 설정된 속성 값이다.
    • 게임 로직(logic): 액터에 행동을 명령하는 프로그래밍 코드(C++, 블루프린트)를 말한다.

 

 2.1.3 레벨

  • 레벨(level)은 플레이어에게 주어지는 스테이지를 의미한다.
    • 언리얼 엔진에서의 레벨(level)은 월드에 배치된 액터의 집합이다.
    • 기존 레벨을 구성하는 액터(흰색)와 접속한 플레이어와 관련된 새로운 액터(노란색)로 구분된다.

 

 2.1.4 컴포넌트

  • 액터를 설계할 때 액터의 역할에 따라 각 기능을 규격화하고 조합할 수 있도록 했는데, 이러한 기능을 컴포넌트(component)라고 한다.
    • 액터는 여러 개의 컴포넌트를 가질 수 있다.
    • 컴포넌트를 대표하는 루트 컴포넌트(root component)를 반드시 지정해야 한다.
컴포넌트 설명
스태틱 메시(static mesh) 애니메이션이 없는 모델링 애셋인 스태틱 메시로 시각적 기능과 물리적 기능을 제공한다.
스켈레탈 메시(skeletal mesh) 애니메이션이 있는 모델링 애셋인 스켈레탈 메시로 애니메이션과 시각적 기능, 그리고 물리적 기능을 제공한다.
콜리전(collision) 구, 박스, 캡슐 등 일정한 영역에 물리적인 기능을 설정하기 위해 제공한다.
카메라(camera) 가상 세계에서 보이는 현재 상황을 플레이어의 모니터 화면에 출력한다.
오디오(audio) 가상 세계에서 소리를 발생시키는 데 사용한다.
파티클(particle) 파티클 시스템으로 설계된 이펙트를 모니터 화면에 보여준다.
라이트(light) 물체에 광원 효과를 부여한다.
무브먼트(movement) 물체에 특정한 움직임을 부여한다.

 

2.2 액터의 설계

 2.2.1 분수대 액터 선언

  • 분수대 액터는 다음 네 가지 컴포넌트로 구성된다.
    • 분수대 구조물의 비주얼과 충돌을 담당할 스태틱 메시 컴포넌트(staticmesh component)
    • 물의 비주얼을 담당할 스태틱 메시 컴포넌트(staticmesh component)
    • 조명의 비주얼을 담당할 포인트 라이트 컴포넌트(pointlight component)
    • 찰랑거리는 이펙트를 담당할 파티클 시스템 컴포넌트(particle component)

 

  • C++에서 액터가 컴포넌트를 가지려면 멤버 변수로 클래스의 포인터를 선언해줘야 한다.
    • 분수대 액터의 데이터 멤버로 두 개의 UStaticMeshComponent 클래스의 포인터를 선언한다.
    • 조명 기능에는 UPointLightComponent 클래스의 포인터를 선언한다.
    • 이펙트에는 UParticleSystemComponent 클래스의 포인터를 선언한다.
/* Chapter 02 fountain.h */
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once

#include "EngineMinimal.h"
#include "GameFramework/Actor.h"
#include "Fountain.generated.h"

UCLASS()
class ARENABATTLE_API AFountain : public AActor {
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AFountain();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY()
	UStaticMeshComponent* Body;
    
	UPROPERTY()
	UStaticMeshComponent* Water;

	UPROPERTY()
	UPointLightComponent* Light;

	UPROPERTY()
	UParticleSystemComponent* Splash;
};
언리얼 C++ 참고 언리얼 프로젝트의 구성이 4.15 버전부터 언리얼 오브젝트가 동작할 수 있는 최소 기능만 선언된 CoreMinimal.h 공용 헤더 파일만 참조하도록 C++ 코드의 템플릿이 변경되었다. 하지만 콘텐츠 제작에는 다양한 엔진 기능이 필요하기 때문에 엔진 클래스의 선언을 모아둔 EngineMinimal.h 파일을 주로 사용한다.

 

 2.2.2 언리얼 액터

  • 언리얼 실행 환경(run time)은 동적으로 할당된 메모리를 자동으로 소멸시키는 기능을 제공한다.
  • 언리얼 오브젝트 객체를 UPROPERTY 매크로로 지정하면 언리얼 실행 환경이 자동으로 메모리를 관리한다.
  • C++ 클래스가 언리얼 오브젝트 클래스가 되려면 클래스 선언에 언리얼 엔진이 정의한 매크로와 규칙을 따라야 한다.
매크로 규칙
클래스 선언 매크로 클래스 외부에 UCLASS 매크로를 선언하고 내부에는 GENERATED_BODY 매크로를 선언한다.
클래스 이름 접두사 언리얼 오브젝트에는 항상 규칙에 맞게 U와 A 등의 접두사가 붙어야 한다. A는 액터 클래스에 사용하고 U는 액터가 아닌 클래스에 사용한다.
generated.h 헤더 파일 언리얼 엔진은 언리얼 헤더 툴(unreal header tool)을 사용해 클래스 선언을 분석하고 언리얼 실행 환경에 필요한 정보를 별도의 파일에 생성한다. 자동으로 생성되는 이 파일이 generated.h 파일이다.
외부 모듈 공개 여부 윈도우의 DLL 시스템은 DLL 안의 클래스 정보를 외부에 공개할지 결정하는 _declspec(dllexport) 키워드를 제공한다. 언리얼 엔진에서 이 키워드를 사용하려면 '모듈명_API' 키워드를 선언에 추가한다.

 

 2.2.3 분수대 액터 구현

  • 분수대 액터의 구축은 클래스의 생성자 코드에서 컴포넌트를 실제로 생성하는 로직을 구현한다.
    • 언리얼 엔진은 컴포넌트를 생성할 때 new가 아닌 CreateDefaultSubobject API를 사용한다.

 

  • 네 컴포넌트가 생성되면 그중에서 액터를 대표할 루트 컴포넌트(root component)를 지정해야 한다.
    • 분수대 구조물을 담당할 Body 컴포넌트를 루트 컴포넌트로 지정한다.
    • Water, Light, Splash는 SetupAttachment 함수를 사용해 Body의 자식이 되도록 설정한다.
/* Chapter 02 fountain.cpp */
// Fill out your copyright notice in the Description page of Project Settings.
#include "Fountain.h"

// Sets default values
AFountain::AFountain() {
 	// 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;

	Body = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BODY"));
	Water = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WATER"));
	Light = CreateDefaultSubobject<UPointLightComponent>(TEXT("LIGHT"));
	Splash = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("SPLASH"));

	RootComponent = Body;
	Water->SetupAttachment(Body);
	Light->SetupAttachment(Body);
	Splash->SetupAttachment(Body);
}

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

// Called every frame
void AFountain::Tick(float DeltaTime) {
	Super::Tick(DeltaTime);
}
언리얼 C++ 참고 CreateDefaultSubObject API에서 사용하는 문자열 값은 액터에 속한 컴포넌트를 구별하기 위한 해시 값 생성에 사용된다. 이 문자열은 다른 컴포넌트와 중복되지 않는 유일한 값을 지정해야 한다.

 

2.3 액터와 에디터 연동

 2.3.1 스태틱 메시 애셋 지정

  • 디테일 윈도우에서 컴포넌트의 속성을 편집하기 위해서는 UPROPERTY 매크로 안에 VisibleAnywhere 키워드를 추가하고 컴파일한다.
/* Chapter 02 fountain.h */
...
UCLASS()
class ARENABATTLE_API AFountain : public AActor {
...
public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* Body;
    
	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* Water;
    
	UPROPERTY(VisibleAnywhere)
	UPointLightComponent* Light;
    
	UPROPERTY(VisibleAnywhere)
	UParticleSystemComponent* Splash;
};

 

  •  스태틱 메시 컴포넌트에 스태틱 메시 애셋을 지정해 분수대를 표현한다.
    • 분수대 액터의 루트 컴포넌트인 Body를 선택하고 스태틱 메시 섹션에서 SM_Plains_Castle_Fountain_01 애셋을 선택해 분수대가 보이도록 만든다.
    • Water 컴포넌트의 스태틱 메시를 선택하고 드롭다운에서 SM_Plains_Fountain_02 애셋을 선택한다.
    • Splash 컴포넌트 이펙트를 담당하는 파티클을 선택하고 Template 속성에 P_Water_Fountain_Splash_Base_01 애셋을 지정해 물이 찰랑거리는 효과를 부여한다.

그림 2-1 분수대 컴포넌트 스태틱 메시 설정

 

2.4 액터 기능의 확장

 2.4.1 액터의 기본값

  • 액터의 생성자에서 컴포넌트에 SetRelativeLocation을 사용하면 컴포넌트의 기본 위치 값을 변경할 수 있다.
    • 변경할 위치 값은 구조체 FVector를 사용해 전달한다.
    • 생성자 코드에서 설정한 값은 언리얼 오브젝트의 기본값이 된다.
/* Chapter 02 fountain.cpp */
// Fill out your copyright notice in the Description page of Project Settings.
#include "Fountain.h"

// Sets default values
AFountain::AFountain() {
 	// 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;

	Body = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BODY"));
	Water = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WATER"));
	Light = CreateDefaultSubobject<USPointLightComponent>(TEXT("LIGHT"));
	Splash = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("SPLASH"));

	RootComponent = Body;
	Water->SetupAttachment(Body);
	Light->SetupAttachment(Body);
	Splash->SetupAttachment(Body);

	Water->SetRelativeLocation(FVector(0.0f, 0.0f, 135.0f));
	Light->SetRelativeLocation(FVector(0.0f, 0.0f, 195.0f));
	Splash->SetRelativeLocation(FVector(0.0f, 0.0f, 195.0f));
}
언리얼 C++ 참고 언리얼 엔진 프로그래밍에서 클래스 이름에 붙은 F 접두사는 언리얼 오브젝트와 관련 없는 일반 C++ 클래스 혹은 구조체를 의미한다.

 

2.5 객체 유형과 값 유형

  • 언리얼 오브젝트의 속성 값은 객체를 관리하는 객체 유형과 값을 관리하는 값 유형으로 나뉜다.
    • 객체 유형(object type): 언리얼 오브젝트 클래스
    • 값 유형(value type): 바이트(uint8), 정수(int32), 실수(float), 문자열(FString, FName), 구조체(FVector, FRotator, FTransform)
  • 언리얼 에디터에서 속성의 데이터를 변경하려면 EditAnywhere 키워드를 사용한다.
  • 언리얼 에디터에서 매크로 안에 'Category=분류명' 키워드를 추가하면 지정한 분류에서 속성 값을 관리할 수 있다.
/* Chapter 02 fountain.h */
UCLASS()
class ARENABATTLE_API AFountain : public AActor {
...
public:
	UPROPERTY(EditAnywhere, Category=ID)
	int32 ID;
};

 

2.6 에셋의 지정

  • 애셋은 고유한 키 값으로 경로 값을 사용한다.
    • 애셋을 우클릭한 후 메뉴에서 레퍼런스 복사 메뉴를 선택한다.
    • 애셋을 선택한 후 Ctrl+C 키를 누르면 경로 정보가 클립보드에 복사된다.

그림 2-2 애셋의 경로 정보

 


{오브젝트 타입}'{폴더명}/{파일명}.{애셋명}'

StaticMesh'/Game/InfinityBladeGrassLands/Environments/Plains/Env_Plains_Ruins/StaticMesh/SM_Plains_Castle_Fountain_01.SM_Plains_Castle_Fountain_01'

  • 경로 정보는 다음과 같은 규칙을 갖고 있다.
    • 오브젝트 타입: 애셋의 타입을 명시적으로 지정한다.
    • 폴더명/파일명: 물리적인 디스크에 위치한 애셋의 경로 정보를 의미한다. 경로는 다른 애셋과 중복될 수 없다.
    • 애셋명: 애디터에서 보이는 애셋의 이름을 의미한다. 애셋의 이름은 중복될 수 있다.

 

  • C++ 코드로 애셋을 로드하는 순서는 다음과 같다.
    • 생성자 코드에서 ConstructorHelpers 클래스의 FObjectFinder를 사용해 변수를 선언한다.
    • 이 변수에는 {폴더명}/{파일명}.{애셋명}에 해당하는 경로 값을 전달한다.
    • FObjectFinder 변수의 Object를 사용해 스태틱 메시 컴포넌트의 SetStaticMesh 함수에 전달한다.
/* Chapter 02 fountain.cpp */
// Fill out your copyright notice in the Description page of Project Settings.

#include "Fountain.h"

// Sets default values
AFountain::AFountain() {
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	...
	static ConstructorHelpers::FObjectFinder<UStaticMesh>
		SM_BODY(TEXT("/Game/InfinityBladeGrassLands/Environments/Plains/Env_Plains_Ruins/StaticMesh/SM_Plains_Castle_Fountain_01.SM_Plains_Castle_Fountain_01"));
	if (SM_BODY.Succeeded()) {
		Body->SetStaticMesh(SM_BODY.Object);
	}
    
	static ConstructorHelpers::FObjectFinder<UStaticMesh>
		SM_WATER(TEXT("/Game/InfinityBladeGrassLands/Effects/FX_Meshes/Env/SM_Plains_Fountain_02.SM_Plains_Fountain_02"));
	if (SM_WATER.Succeeded()) {
		Water->SetStaticMesh(SM_WATER.Object);
	}

	static ConstructorHelpers::FObjectFinder<UParticleSystem>
		SM_SPLASH(TEXT("/Game/InfinityBladeGrassLands/Effects/FX_Ambient/Water/P_Water_Fountain_Splash_Base_01.P_Water_Fountain_Splash_Base_01"));
	if (SM_SPLASH.Succeeded()) {
		Splash->SetTemplate(SM_SPLASH.Object);
	}
}

댓글