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.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 키를 누르면 경로 정보가 클립보드에 복사된다.
{오브젝트 타입}'{폴더명}/{파일명}.{애셋명}' 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);
}
}
'Game Engines > 언리얼 엔진' 카테고리의 다른 글
언리얼 C++ 게임 개발의 정석 | Chapter 05 폰의 제작과 조작 (0) | 2022.07.25 |
---|---|
언리얼 C++ 게임 개발의 정석 | Chapter 04 게임플레이 프레임워크 (0) | 2022.07.16 |
언리얼 C++ 게임 개발의 정석 | Chapter 03 움직이는 액터의 제작 (0) | 2022.07.03 |
댓글