Unreal 개발 핵심 정리

Unreal Engine을 처음 보면 기능이 상당히 많다.

Actor, Component, Pawn, Character, Controller, GameMode, GameState, Blueprint, Gameplay Ability System 같은 개념이 한꺼번에 등장한다.

그런데 기본 구조를 먼저 잡아두면 각각의 역할이 꽤 명확해진다.

Unreal을 이해하는 데 가장 먼저 볼 구조는 이 정도다.

World -> Actor -> Component -> Gameplay Logic

여기에 플레이어가 조작하는 객체와 게임 전체 상태를 관리하는 객체가 추가된다.

World -> GameMode / GameState / PlayerController / Pawn / Character / Components / Other Actors

Unreal 개발은 결국 World 안에 Actor를 배치하고, Component와 C++ 또는 Blueprint를 이용해 게임의 상태와 동작을 구성하는 것에 가깝다.


World

Unreal에서 게임이 실행되는 공간을 World라고 볼 수 있다.

World 안에는 여러 Actor가 존재한다.

World -> Player / Enemy / Camera / Light / Item / Environment

Unity의 Scene과 비슷하게 생각할 수 있는 부분도 있지만 완전히 같은 개념은 아니다.

Unreal에서는 World를 중심으로 게임의 실행 환경과 Actor들이 관리된다.


Actor

Unreal에서 가장 기본적인 게임 객체가 Actor다.

게임 월드에 배치되거나 존재하는 대부분의 객체가 Actor를 기반으로 만들어진다.

Actor -> Player / Enemy / Weapon / Item / Projectile / Trigger

C++에서는 AActor를 상속해서 Actor를 만들 수 있다.

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
};

Actor 자체가 모든 기능을 가지고 있는 것은 아니다.

필요한 기능을 Component로 조합한다.


Component

Actor의 기능을 분리해서 구성할 때 사용하는 것이 Component다.

예를 들어 캐릭터라면

Character -> Capsule Component / Skeletal Mesh Component / Character Movement Component / Custom Component

같은 구조가 된다.

무기라면

Weapon -> Static Mesh Component / Collision Component / Weapon Component

처럼 구성할 수 있다.

Actor가 “무엇이 존재하는가”에 가깝다면 Component는 “그 객체가 어떤 기능을 가지고 있는가”를 표현하는 데 사용한다.


Actor와 Component

둘의 관계를 단순하게 보면 다음과 같다.

Actor -> Transform / Mesh / Collision / Movement / Custom Logic

실제 Unreal에서는 각 Component가 더 구체적인 책임을 가진다.

예를 들어

StaticMeshComponent -> 정적 메시
SkeletalMeshComponent -> 스켈레탈 메시
Collision Component -> 충돌
Movement Component -> 이동 처리

이런 식으로 기능을 분리할 수 있다.


Pawn

Pawn은 플레이어나 AI가 조종할 수 있는 Actor다.

Actor -> Pawn

Pawn은 “조종 가능한 객체”라는 의미가 있다.

예를 들어

Player Pawn / Enemy Pawn / Vehicle Pawn

등을 만들 수 있다.

하지만 Pawn 자체가 반드시 인간형 캐릭터일 필요는 없다.

자동차나 비행기처럼 플레이어나 AI가 조작하는 객체도 Pawn으로 만들 수 있다.


Character

CharacterPawn을 기반으로 만들어진 인간형 캐릭터를 구현하기 위한 클래스다.

Actor -> Pawn -> Character

기본적인 캐릭터 이동을 위한 기능들이 이미 제공되기 때문에 플레이어 캐릭터를 만들 때 자주 사용한다.

대표적으로

Capsule Component / Skeletal Mesh / Character Movement Component

등이 포함된다.

그래서 직접 모든 이동 시스템을 처음부터 구현할 필요가 없다.


Controller

Pawn을 실제로 조작하는 역할은 Controller가 담당한다.

Controller는 크게 두 가지로 나눠서 생각할 수 있다.

Controller -> PlayerController / AIController

PlayerController

플레이어의 입력과 연결된다.

Input -> PlayerController -> Pawn

AIController

AI가 Pawn을 제어한다.

AI Logic -> AIController -> Pawn

이 구조를 이용하면 캐릭터 자체가 “키보드 입력”이나 “AI 판단”을 직접 담당하지 않아도 된다.


Possess

Controller가 Pawn을 조종하는 것을 Possess라고 한다.

PlayerController -> Possess -> Player Pawn

플레이어가 캐릭터를 조작할 때는 PlayerController가 Pawn을 소유하고 조종하는 구조로 볼 수 있다.

AI도 마찬가지다.

AIController -> Possess -> Enemy Pawn

이 구조 덕분에 하나의 Pawn을 플레이어가 조작하다가 다른 Controller가 조작하도록 바꾸는 것도 가능하다.


GameMode

GameMode는 게임의 규칙을 정의하는 역할을 한다.

예를 들어

게임 시작 / 플레이어 생성 / 승리 조건 / 패배 조건 / 게임 종료

같은 게임 규칙을 관리할 수 있다.

GameMode -> Default Pawn / Player Controller / Game Rules / Match Rules

중요한 특징이 하나 있다.

GameMode는 서버에 존재한다.

따라서 네트워크 게임에서 클라이언트가 GameMode를 직접 기준으로 게임 상태를 판단하는 구조로 만들면 안 된다.


GameState

게임 전체의 현재 상태를 표현하는 것이 GameState다.

예를 들어

GameState -> 현재 게임 시간 / 현재 라운드 / 팀 점수 / 경기 상태

같은 정보를 저장할 수 있다.

GameMode가

게임 규칙

을 담당한다면 GameState는

현재 게임 상태

를 담당한다고 볼 수 있다.

네트워크 게임에서는 GameState가 클라이언트에 복제될 수 있다는 점도 중요하다.


PlayerState

플레이어 한 명에 대한 상태는 PlayerState에서 관리할 수 있다.

예를 들어

PlayerState -> Player Name / Score / Team / Player Status

같은 정보가 들어갈 수 있다.

여기서 CharacterPlayerState를 분리하는 것이 중요하다.

캐릭터는 죽거나 교체될 수 있다.

Player -> Character

캐릭터가 죽으면 새로운 Character가 생성될 수 있다.

하지만 플레이어의 점수나 팀 정보 같은 데이터는 계속 유지되어야 한다.

그래서

PlayerState -> 플레이어 자체의 상태
Character -> 현재 조작하는 캐릭터

처럼 분리할 수 있다.


Unreal의 기본 게임 구조

지금까지의 내용을 묶으면 대략 이런 구조가 된다.

World -> GameMode / GameState / PlayerController / PlayerState / Pawn / Character / AIController / Other Actors

처음 Unreal의 클래스를 보면 복잡해 보이는 이유가 이런 책임 분리가 이미 엔진 구조에 들어 있기 때문이다.


C++와 Blueprint

Unreal에서는 C++와 Blueprint를 함께 사용하는 방식이 일반적이다.

C++는 게임의 핵심적인 시스템이나 복잡한 로직을 구현하기 좋다.

Blueprint는 노드 기반으로 로직을 구성할 수 있어서 빠른 프로토타이핑이나 디자이너가 직접 조정해야 하는 부분에 유용하다.

둘을 경쟁 관계로 볼 필요는 없다.

C++ -> 시스템 / 핵심 로직 / 성능이 중요한 코드
Blueprint -> 조합 / 설정 / 빠른 프로토타이핑 / 콘텐츠 제작

실제로는 C++로 기반 클래스를 만들고 Blueprint에서 이를 상속해 세부적인 설정을 하는 구조를 많이 사용한다.


Reflection

Unreal C++에서 일반적인 C++과 다른 부분 중 하나가 Reflection System이다.

예를 들어

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
};

처럼 매크로를 사용한다.

UCLASS, UPROPERTY, UFUNCTION 등의 매크로를 통해 Unreal의 시스템이 해당 타입과 프로퍼티, 함수를 인식할 수 있다.

예를 들어

UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Health;

처럼 작성하면 에디터나 Blueprint에서 해당 값을 사용할 수 있다.

Unreal C++은 단순히 C++ 코드를 작성하는 것이 아니라 Unreal의 Object와 Reflection 시스템 위에서 C++을 사용하는 구조라고 보는 것이 좋다.


UCLASS와 UPROPERTY

Unreal에서는 UObject 계열 객체를 엔진 시스템에서 관리하기 위해 다양한 매크로를 사용한다.

UCLASS()
class UMyComponent : public UActorComponent
{
    GENERATED_BODY()
};

프로퍼티는

UPROPERTY(EditAnywhere)
float Damage;

처럼 정의할 수 있다.

이렇게 하면 해당 값이 Unreal Editor나 Serialization, Garbage Collection, Blueprint 등의 시스템과 연결될 수 있다.

단순한 C++ 변수와 Unreal의 UPROPERTY가 동일한 역할을 하는 것은 아니다.


UObject

Unreal의 여러 시스템에서 기반이 되는 클래스가 UObject다.

UObject -> UActorComponent / AActor / UDataAsset / ...

모든 클래스가 UObject를 상속해야 하는 것은 아니다.

일반적인 순수 C++ 데이터 구조나 알고리즘은 일반 C++ 클래스로 구현할 수도 있다.

Unreal의 Reflection, Serialization, Garbage Collection 등이 필요한 객체라면 UObject 계열을 사용한다.


Garbage Collection

Unreal은 UObject 계열 객체에 대해 자체적인 Garbage Collection 시스템을 사용한다.

중요한 것은 UObject를 직접 newdelete로 관리하는 방식이 일반적인 Unreal 객체 관리 방식이 아니라는 것이다.

예를 들어 UObject 계열 객체는 Unreal의 참조 시스템을 통해 생명주기가 관리된다.

그래서 UObject를 다룰 때는

UObject -> Unreal Object System -> Reference Tracking -> Garbage Collection

구조를 이해해야 한다.

반대로 일반 C++ 객체는 일반적인 C++의 수명 관리 방식을 따른다.


Asset

Unreal 프로젝트에서도 코드와 별개로 다양한 Asset이 존재한다.

Asset -> Static Mesh / Skeletal Mesh / Material / Texture / Animation / Sound / Niagara System / Data Asset

게임에서 사용하는 리소스는 대부분 Asset으로 관리된다.

예를 들어 캐릭터는

Character -> Skeletal Mesh / Animation / Material / Sound

같은 여러 Asset을 조합해서 만들어진다.


Blueprint Class와 Prefab의 차이

Unity의 Prefab과 비슷한 역할을 하는 개념을 Unreal에서 찾으면 Blueprint Class가 일부 대응된다.

하지만 완전히 동일한 개념은 아니다.

Blueprint Class는 Actor나 다른 클래스를 기반으로 만들어서

Components / Variables / Events / Functions / Logic

등을 구성할 수 있다.

예를 들어

C++ AEnemy -> BP_Enemy -> Mesh 설정 / Animation 설정 / 값 조정 / Blueprint Logic

같은 구조를 만들 수 있다.


Collision

Unreal에서도 충돌은 중요한 시스템이다.

Collider라는 하나의 객체보다는 다양한 Collision Component와 Collision Channel을 이용해 충돌을 구성한다.

대표적으로

Collision -> Block / Overlap / Ignore

같은 응답을 설정할 수 있다.

예를 들어 총알과 적의 관계를

Bullet -> Enemy -> Overlap

으로 설정해 공격 판정을 처리할 수도 있다.


Collision Channel

Unreal의 Collision은 단순히 “충돌한다 / 안 한다”로 끝나지 않는다.

객체의 종류를 Channel로 구분할 수 있다.

예를 들어

WorldStatic / WorldDynamic / Pawn / PhysicsBody / Vehicle

등의 기본 채널이 있고 프로젝트에 필요한 Custom Channel을 만들 수도 있다.

이를 통해

총알 -> Pawn은 Hit / Trigger는 Ignore / World는 Block

같은 규칙을 구성할 수 있다.


Line Trace

Unity의 Raycast와 비슷한 기능으로 Unreal에서는 Line Trace를 많이 사용한다.

Camera -> Line Trace -> Enemy

예를 들어 FPS 게임에서 총을 쏠 때

Camera -> Line Trace -> Hit Result -> Hit Actor -> Damage

같은 흐름을 만들 수 있다.

Line Trace는 공격 판정뿐만 아니라

상호작용 대상 확인 / 바닥 감지 / 시야 검사 / 조준 대상 확인

등에도 사용할 수 있다.


Damage System

Unreal에는 Actor에 Damage를 전달하기 위한 기본적인 시스템도 존재한다.

개념적으로는

Attacker -> Damage -> Target -> Health 감소

형태다.

게임 규모가 커지면 단순히 Health -= Damage만 사용하는 것보다

Damage Type / Damage Modifier / Armor / Resistance / Critical / Status Effect

등을 분리할 수 있다.


Animation

Unreal의 캐릭터 애니메이션은 주로 Skeletal Mesh와 Animation System을 사용한다.

Skeletal Mesh -> Skeleton / Animation Sequence / Animation Blueprint

Animation Blueprint에서는 캐릭터 상태에 따라 어떤 애니메이션을 재생할지 구성할 수 있다.

예를 들어

Speed = 0 -> Idle
Speed > 0 -> Walk / Run
Attack -> Attack Animation

같은 구조다.

복잡한 캐릭터라면 Animation Blueprint의 State Machine과 Blend Space 등을 활용하게 된다.


Niagara

Unreal에서 파티클과 시각 효과를 만들 때 사용하는 시스템이 Niagara다.

Niagara -> Particle / Emitter / System / Module

폭발, 불, 연기, 마법 효과, 환경 효과 등을 구현할 수 있다.

게임 로직과 이펙트 로직을 분리하면 관리하기 편하다.

예를 들어

Gameplay -> Enemy Death -> Death Event -> Niagara Effect

같은 방식으로 연결할 수 있다.


Enhanced Input

Unreal에서는 Enhanced Input을 이용해 입력을 액션 단위로 추상화할 수 있다.

예를 들어

Keyboard W / Gamepad Stick -> IA_Move -> Character Movement

같은 구조다.

입력 장치와 게임 로직을 분리할 수 있기 때문에 키보드, 게임패드 등 여러 입력 장치를 지원하기 편하다.


Gameplay Framework

Unreal을 제대로 사용하려면 Gameplay Framework를 이해할 필요가 있다.

대표적인 객체들을 정리하면 다음과 같다.

GameMode -> 게임 규칙
GameState -> 게임 전체 상태
PlayerController -> 플레이어 입력 / 제어
PlayerState -> 플레이어 상태
Pawn -> 조종 가능한 객체
Character -> 캐릭터용 Pawn

이 구조를 이해하면 Unreal에서 “이 로직을 어디에 넣어야 하지?”라는 문제가 많이 줄어든다.

예를 들어 플레이어의 점수를 Character에 넣는 것보다 PlayerState가 더 적절할 수 있다.

게임 전체의 승리 조건은 Character보다 GameMode가 더 적절하다.


네트워크

Unreal은 네트워크 멀티플레이를 엔진 차원에서 상당히 많이 지원한다.

기본적인 구조는

Client -> Request -> Server -> Authoritative State -> Clients

형태로 생각할 수 있다.

여기서 중요한 개념이 Server Authority다.

중요한 게임 상태를 클라이언트가 결정하는 것이 아니라 서버가 결정한다.

예를 들어

Client -> 아이템 획득 요청 -> Server -> 거리 확인 / 아이템 존재 확인 / 획득 가능 여부 확인 -> 상태 변경 -> Replication

같은 구조다.


Replication

Unreal 네트워크에서 중요한 기능이 Replication이다.

서버의 Actor 상태를 클라이언트에 동기화할 수 있다.

Server -> Replication -> Client

예를 들어 플레이어의 위치나 체력 같은 상태를 복제할 수 있다.

하지만 모든 변수를 무조건 Replication하면 네트워크 비용이 커진다.

어떤 데이터가 실제로 네트워크를 통해 동기화되어야 하는지 구분해야 한다.


RPC

네트워크 환경에서 특정 함수를 다른 컴퓨터에서 실행하기 위해 RPC(Remote Procedure Call)를 사용할 수 있다.

대표적으로

Server RPC / Client RPC / Multicast RPC

를 사용한다.

예를 들어

Client -> Server RPC -> 서버에서 공격 요청

처럼 사용할 수 있다.

다만 RPC는 단순히 “원격에서 함수 호출”이라는 개념으로 끝나지 않는다.

누가 호출할 수 있는지, 어느 방향으로 호출되는지, 신뢰성을 어떻게 처리할지까지 네트워크 구조와 함께 봐야 한다.


Async와 Gameplay Thread

Unreal에서도 무거운 작업을 메인 게임 스레드에서 수행하면 프레임에 영향을 준다.

Game Thread -> Heavy Calculation -> Frame Time 증가

필요한 경우 Task Graph, Async Task, Unreal의 멀티스레딩 기능 등을 활용해 작업을 분리할 수 있다.

다만 Unreal 객체와 게임 상태는 Game Thread와 강하게 연결되어 있는 경우가 많다.

그래서 아무 코드나 다른 스레드로 옮기면 되는 것은 아니다.

스레드 안전성과 Unreal Object 접근 가능 여부를 함께 확인해야 한다.


Gameplay Ability System

규모가 있는 게임에서 능력, 스킬, Attribute, Effect 등을 체계적으로 관리해야 한다면 Gameplay Ability System(GAS)을 사용할 수 있다.

개념적으로

Ability -> 스킬 / 행동
Attribute -> Health / Mana / Strength
Gameplay Effect -> 버프 / 디버프 / 수치 변경
Gameplay Tag -> 상태 / 조건 분류

같은 구조를 제공한다.

예를 들어

Fireball -> Gameplay Ability
Damage -> Gameplay Effect
Burning -> Gameplay Tag

같은 형태로 구성할 수 있다.

GAS는 강력하지만 구조가 복잡하다.

간단한 게임에서 모든 시스템을 GAS로 만드는 것이 항상 좋은 선택은 아니다.


Unreal 성능 최적화

Unreal에서도 최적화는 추측보다 측정이 먼저다.

대표적으로

CPU / GPU / Memory / Rendering / Animation / Physics / Network

등을 확인해야 한다.

예를 들어 GPU가 병목이라면

Shader / Material / Lighting / Shadow / Draw Call / Overdraw

등을 살펴볼 수 있다.

CPU가 병목이라면

Game Thread / Animation / Physics / AI / Actor Tick

등을 확인한다.

Unreal에서는 Tick()을 사용하는 Actor가 많아질수록 CPU 비용이 커질 수 있다.

그래서 모든 Actor가 매 프레임 Tick을 수행하도록 만드는 것보다 이벤트 기반 처리나 필요한 경우에만 Tick을 활성화하는 방식을 고려할 수 있다.


Data-Driven Design

게임의 숫자와 설정값을 코드에 직접 박아 넣기보다 데이터를 분리하는 것이 좋다.

예를 들어

Weapon -> Damage / Fire Rate / Range / Ammo / Recoil

같은 데이터를 Data Asset이나 별도의 데이터 구조로 관리할 수 있다.

그러면

Weapon Code -> Weapon Data

처럼 로직과 데이터를 분리할 수 있다.

이 방식은 무기 종류가 10개에서 100개로 늘어날 때 특히 차이가 크다.

코드를 수정하지 않고 데이터만 변경해서 밸런스를 조정할 수 있기 때문이다.


Unreal 프로젝트의 전체 구조

게임 프로젝트가 어느 정도 커졌다고 생각하면 대략 이런 구조가 된다.

Game -> Core / Character / Gameplay / UI / Animation / Data / Network / Assets

각 영역을 세부적으로 보면

Core -> GameMode / GameState / PlayerState
Character -> Player / Enemy / AI
Gameplay -> Combat / Ability / Inventory / Quest

Unreal이 제공하는 Gameplay Framework 위에 프로젝트 자체의 시스템을 쌓아가는 형태다.


Unity와 비교해서 보면

Unity와 Unreal은 모두 Component 기반의 게임 엔진이지만 구조를 바라보는 방식에는 차이가 있다.

Unity에서는

Scene -> GameObject -> Component

가 기본적으로 눈에 들어온다.

Unreal에서는

World -> Actor -> Component
Gameplay Framework -> GameMode / GameState / Controller / Pawn / PlayerState

가 추가된다.

특히 Unreal은 Multiplayer Gameplay Framework가 엔진 구조에 깊게 들어가 있어서 네트워크 게임을 만들 때 알아야 할 클래스와 책임 분리가 더 명확하게 제공되는 편이다.


Unreal을 하나의 흐름으로 보기

결국 게임 하나를 실행하면 여러 시스템이 연결된다.

Input -> PlayerController -> Pawn / Character -> Gameplay Logic -> Component -> Animation / Physics / Rendering

네트워크 게임이라면

Client Input -> Server -> Game Logic -> Game State -> Replication -> Clients

데이터는

Asset -> Data -> Gameplay System

으로 들어오고,

게임의 시각적인 결과는

Gameplay State -> Animation -> Rendering -> Screen

으로 표현된다.

이 전체 흐름을 잡으면 Unreal의 수많은 클래스와 시스템을 각각 외울 필요가 줄어든다.


정리

Unreal 개발에서 가장 먼저 잡아야 할 구조를 정리하면 이렇다.

World -> Actor -> Component

게임플레이 구조는

GameMode -> 게임 규칙
GameState -> 게임 전체 상태
PlayerController -> 플레이어 제어
PlayerState -> 플레이어 상태
Pawn -> 조종 가능한 객체
Character -> 캐릭터 구현

네트워크에서는

Server Authority -> Replication -> RPC

를 중심으로 이해할 수 있다.

그리고 프로젝트 규모가 커지면

C++ / Blueprint / Data Asset / Gameplay Ability System / Animation / Niagara / AI / Networking / Optimization

같은 시스템이 추가된다.

Unreal은 기능이 많은 엔진이지만 모든 기능을 알아야 게임을 만들 수 있는 것은 아니다.

중요한 것은 각 시스템이 어떤 책임을 가지고 있고 서로 어떤 방식으로 연결되는지 파악하는 것이다.

특히 Actor, Component, Pawn, Controller, GameMode, GameState, PlayerState의 역할을 제대로 구분해두면 이후 Unreal의 Gameplay Framework를 이해하기 훨씬 쉬워진다.

결국 엔진이 제공하는 기능을 많이 아는 것보다, 게임 상태를 어디에서 관리하고 어떤 객체가 어떤 책임을 가져야 하는지 결정하는 능력이 프로젝트가 커질수록 더 중요해진다.