강의 2 복습하기

주소리·2026년 8월 20일

TIL

목록 보기
44/48

이대로 진도 나가면 안될 듯 하여 강의 2 다시 짚고 넘어갑니다.

헷갈리는 개념 정리

1. UPROPERTY()에서 사용하는 지정자의 범위

UPROPERTY()에는 여러 지정자가 들어갈 수 있는데, 처음에는 VisibleAnywhere, BlueprintReadOnly, EditAnywhere 등이 비슷해 보여 꽤 헷갈렸습니다.

우선 크게 나누면 다음과 같습니다.

에디터에서의 표시 및 수정 범위

  • VisibleAnywhere
  • VisibleDefaultsOnly
  • VisibleInstanceOnly
  • EditAnywhere
  • EditDefaultsOnly
  • EditInstanceOnly

블루프린트에서의 접근 권한

  • BlueprintReadOnly
  • BlueprintReadWrite
지정자Details 패널에 표시수정 가능적용 범위
VisibleAnywhereOX클래스 기본값 + 배치된 인스턴스
VisibleDefaultsOnlyOX클래스 기본값
VisibleInstanceOnlyOX배치된 인스턴스
EditAnywhereOO클래스 기본값 + 배치된 인스턴스
EditDefaultsOnlyOO클래스 기본값
EditInstanceOnlyOO배치된 인스턴스

여기서 중요한 점은 에디터에서의 접근 권한과 블루프린트에서의 접근 권한은 서로 다른 문제라는 것입니다.

예를 들어 다음과 같이 선언할 수 있습니다.

UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
USpringArmComponent* SpringArmComp;

VisibleAnywhere는 해당 프로퍼티를 에디터에서 볼 수 있지만 직접 다른 값으로 교체할 수 없도록 합니다.

BlueprintReadOnly는 블루프린트에서 해당 프로퍼티를 읽을 수는 있지만 직접 새로운 값을 대입할 수 없도록 합니다.

그렇다면 컴포넌트 내부 속성도 수정할 수 없을까요?

이 부분이 특히 헷갈렸습니다.

결론부터 말하면 컴포넌트 자체를 가리키는 참조를 변경하는 것그 컴포넌트가 가지고 있는 내부 속성을 변경하는 것은 별개의 문제입니다.

예를 들어 SpringArmCompVisibleAnywhere로 선언했다면 SpringArmComp라는 변수에 다른 컴포넌트를 마음대로 집어넣는 것은 제한됩니다.

하지만 SpringArmComp 안에 있는 Target Arm Length, 회전 관련 설정 등 컴포넌트 자체가 제공하는 편집 가능한 속성은 Details 패널에서 변경할 수 있습니다.

즉, 대략 다음과 같이 이해할 수 있을 것 같습니다.

VisibleAnywhere
"이 컴포넌트가 존재한다는 것은 보여드리지만, 컴포넌트 자체를 다른 것으로 갈아끼우지는 마세요."

BlueprintReadOnly
"블루프린트에서 이 변수를 가져다 읽을 수는 있지만, 변수 자체에 새로운 값을 직접 대입하지는 마세요."

아직은 어디까지 C++에서 결정하고 어디까지 에디터에서 조절하도록 만드는 것이 효율적인가?에 대한 감각이 완전히 잡히지는 않았습니다.

현재는 대략 다음 정도로 이해하고 있습니다.

  • 게임의 구조나 반드시 지켜져야 하는 규칙 → C++
  • 이동 속도, 카메라 거리, 이펙트 크기처럼 자주 조정할 값 → 에디터
  • 기획자가 직접 조정할 가능성이 높은 값 → EditAnywhere, EditDefaultsOnly 등을 이용해 노출
  • 외부에서 함부로 변경하면 안 되는 컴포넌트 → VisibleAnywhere 등으로 제한

결국 C++과 에디터 중 하나만 사용하는 것이 아니라, 변경되어도 되는 값과 변경되면 안 되는 구조를 구분하는 것이 중요해 보입니다.


서바이벌 아케이드 게임 구현하기

이번 프로젝트에서는 기본적인 캐릭터 조작부터 아이템, 웨이브, HUD, 이펙트까지 단계적으로 구현해 볼 예정입니다.

할 일 목록

  1. 캐릭터 구현

  2. 캐릭터 입력 매핑 구현

  3. 캐릭터 기본 동작 구현

    • WASD 이동
    • 점프
    • 스프린트
    • 카메라 회전
  4. 캐릭터 애니메이션 적용

    • Idle
    • Walk
    • Jump
    • Sprint
  5. 아이템 상호작용 구현

    • 범위 내에 있을 경우 체력 감소
    • 획득 시 점수 증가
    • 획득 시 체력 증가
  6. 아이템 랜덤 스폰 및 레벨별 아이템 개수 설정

  7. 캐릭터 데미지 및 아이템 점수 관리 시스템 구현

  8. 웨이브 시스템을 통한 게임 흐름 제어

  9. HUD에 실시간 정보 반영

  10. 게임 흐름에 맞춘 메뉴 UI 구현

  11. UI 애니메이션 효과 및 3D 위젯 UI 구현

  12. 파티클 효과 적용

    • 지뢰 폭발
    • 아이템 습득
  13. 사운드 효과 적용

    • 지뢰 폭발
    • 아이템 습득
    • 발걸음
  14. 프로젝트 패키징 및 배포


1. GameMode - 게임의 규칙을 관리하는 역할

GameMode는 게임 전체의 규칙과 기본 구성을 결정하는 클래스입니다.

현재 공부한 내용을 기준으로 보면 다음과 같은 클래스들을 지정할 수 있습니다.

  1. Default Pawn / Character

    • 플레이어가 기본적으로 조종하게 될 Pawn 또는 Character 클래스를 지정합니다.
  2. PlayerController

    • 플레이어가 사용할 PlayerController 클래스를 지정합니다.
    • Controller는 Pawn 또는 Character를 Possess하여 조종할 수 있습니다.
  3. 게임 규칙

    • 게임 시작, 종료, 승패 조건 등의 게임 로직을 관리할 수 있습니다.
  4. GameState

    • 현재 게임 전체에서 공유해야 하는 상태를 관리할 때 사용합니다.
  5. PlayerState

    • 각 플레이어별로 관리해야 하는 상태를 저장할 때 사용합니다.

GameModeBaseGameMode

GameMode 클래스를 생성할 때 보면 GameModeBaseGameMode라는 두 종류가 있습니다.

GameModeBase는 비교적 단순화된 기본 GameMode이고, GameMode는 여기에 Match State와 같은 경기 진행 상태 관리 기능이 추가된 클래스입니다.

따라서 단순한 게임에서는 GameModeBase만으로도 충분할 수 있고, 경기 시작·진행·종료와 같은 명확한 매치 흐름이 필요한 게임에서는 GameMode가 유용합니다.

GameMode는 프로젝트 설정에서 기본값을 지정할 수도 있고, 각 레벨의 World Settings에서 별도로 지정할 수도 있습니다.

이 경우 레벨의 World Settings에서 GameMode를 따로 지정했다면 해당 설정이 우선 적용됩니다.

GameMode에서는 대표적으로 다음 클래스들을 지정할 수 있습니다.

  • Default Pawn Class
  • Player Controller Class
  • Player State Class
  • Game State Class
  • HUD Class
  • Spectator Class

즉, GameMode는

"이 게임 또는 이 레벨에서는 어떤 규칙과 기본 클래스들을 사용할 것인가?"

를 결정하는 역할에 가깝다고 이해했습니다.


Pawn과 Character의 차이

PawnCharacter의 차이도 처음에는 상당히 헷갈렸습니다.

CharacterPawn을 상속받은 클래스입니다.

특히 CharacterMovementComponent, Capsule Collision 등 걷고 뛰는 캐릭터를 구현하기 편리한 기능들이 기본으로 준비되어 있습니다.

따라서 사람이나 몬스터처럼 지면 위를 걸어다니는 캐릭터를 만들 때 상당히 편리합니다.

반면 Pawn은 Character보다 더 기본적인 형태입니다.

이동 방법이 미리 강하게 정해져 있지 않기 때문에 자동차, 비행기, 드론, 특수한 이동체처럼 직접 이동 방식을 설계하고 싶은 경우 더 자유롭게 사용할 수 있습니다.

다만,

"사람이 아니면 무조건 Pawn을 사용한다."

라고 이해하는 것은 정확하지 않습니다.

인간이 아닌 몬스터라고 하더라도 Character의 이동 방식이 적합하다면 Character를 사용할 수 있습니다.

반대로 인간형 캐릭터라고 하더라도 매우 특수한 움직임이 필요하다면 Pawn을 사용할 수도 있습니다.

결국 기준은 외형보다는

"Character가 기본으로 제공하는 이동 시스템을 활용할 것인가?"

에 더 가깝습니다.


전방 선언(Forward Declaration)

코드를 보다 보면 다음과 같은 형태가 자주 등장합니다.

class USpringArmComponent;

처음에는 #include도 아닌데 갑자기 클래스를 선언해서 무엇을 하는 것인지 알기 어려웠습니다.

이것을 전방 선언(Forward Declaration)이라고 합니다.

쉽게 말하면 컴파일러에게

"USpringArmComponent라는 클래스가 어딘가에 존재합니다. 자세한 내용은 나중에 알려드리겠습니다."

라고 미리 알려주는 것입니다.

헤더 파일에서 클래스의 포인터나 참조만 필요하다면 굳이 해당 클래스의 헤더 전체를 #include하지 않고 전방 선언만 사용할 수 있습니다.

예를 들어,

class USpringArmComponent;

USpringArmComponent* SpringArmComp;

처럼 사용할 수 있습니다.

그리고 실제로 USpringArmComponent의 함수나 내부 기능을 사용하는 .cpp 파일에서는 필요한 헤더를 #include합니다.

이렇게 하면 헤더끼리 불필요하게 많은 파일을 불러오는 것을 줄일 수 있습니다.


2. PlayerController

PlayerController플레이어와 게임 속 Pawn을 연결하는 Controller입니다.

이번 프로젝트에서는 PlayerController를 중심으로 입력을 처리하도록 구성했습니다.

PlayerController에서 담당할 수 있는 대표적인 기능은 다음과 같습니다.

  1. 플레이어 입력 처리
  2. 카메라 제어 로직
  3. UI와의 상호작용
  4. Pawn / Character에 대한 Possess, UnPossess

여기서 한 가지 주의할 점은

"언리얼에서는 모든 입력을 반드시 PlayerController에서 처리해야 한다."

는 의미는 아닙니다.

Character나 Pawn에서도 입력을 바인딩할 수 있습니다.

프로젝트 구조에 따라서 PlayerController에서 입력을 관리할 수도 있고, Character 또는 Pawn에서 실제 움직임과 관련된 입력을 처리할 수도 있습니다.

이번에는 플레이어의 입력을 PlayerController 쪽에서 분리해서 관리하는 구조를 공부했습니다.


Enhanced Input System

Enhanced Input은 기존 입력 시스템보다 Input Action, Mapping Context, Trigger, Modifier 등을 이용해 입력을 더 구조적으로 관리할 수 있게 해주는 시스템입니다.

UE5에서 일반적으로 사용하는 입력 시스템이기도 합니다.

Input Mapping Context (IMC)

Input Mapping Context, 줄여서 IMC는 어떤 입력 설정들을 현재 사용할 것인지 묶어 놓은 컨텍스트입니다.

처음에는 스위치와 비슷하다고 이해했습니다.

예를 들어 다음과 같이 나눌 수 있습니다.

IMC_Human
 ├─ IA_Move
 ├─ IA_Look
 └─ IA_Jump

IMC_Car
 ├─ IA_Accelerate
 ├─ IA_Brake
 └─ IA_Steer

사람을 조종할 때는 IMC_Human을 사용하고, 자동차에 탑승하면 IMC_Car를 적용하는 식으로 입력 구성을 전환할 수 있습니다.

따라서 IMC는 단순히 키 하나를 지정하는 것이 아니라,

여러 Input Action과 실제 키 입력의 대응 관계를 묶어서 관리하는 설정

이라고 보는 편이 더 정확합니다.


Input Action (IA)

Input Action은 특정한 행동을 추상화한 것입니다.

예를 들어

점프 → IA_Jump
카메라 회전 → IA_Look
이동 → IA_Move

와 같이 만들 수 있습니다.

여기서 중요한 점은 IA_Move 자체가 W키를 의미하는 것은 아니라는 것입니다.

IA_Move는 단순히 "이동이라는 행동"을 의미하고,

실제로 어떤 키가 이동을 발생시키는지는 IMC에서 연결합니다.

이 구조 덕분에 같은 IA_Move를 사용하면서도 키보드, 게임패드 등 서로 다른 입력 장치를 연결할 수 있습니다.


이번 캐릭터에서 필요한 행동

이번 캐릭터에는 다음 입력을 구현합니다.

  1. WASD 이동
  2. 마우스 회전
  3. 점프
  4. 스프린트

Input Action을 생성한 뒤 각각의 행동에 필요한 Value Type을 지정합니다.


Value Type

Input Action에서 특히 중요한 설정 중 하나가 Value Type입니다.

해당 Input Action이 실행될 때 어떤 형태의 값을 전달할 것인지를 결정합니다.

Bool

참 또는 거짓을 전달합니다.

단순히 눌렸는지 아닌지를 확인할 때 사용하기 좋습니다.

예:

  • 점프
  • 공격
  • 상호작용

Axis1D

하나의 축 값을 전달합니다.

예를 들어 -1 ~ 1 범위의 값처럼 한 방향의 정도를 표현할 수 있습니다.

예:

  • 게임패드 트리거
  • 전진 / 후진
  • 가속 페달

Axis2D

X, Y 두 개의 축 값을 동시에 전달합니다.

예:

  • WASD 이동
  • 마우스 이동
  • 게임패드 스틱

예를 들어 캐릭터 이동이라면 다음처럼 생각할 수 있습니다.

X = 좌우 이동
Y = 앞뒤 이동

Axis3D

X, Y, Z 세 개의 축 값을 사용합니다.

3차원 방향 입력이 필요한 경우 사용할 수 있습니다.

예:

  • 비행체의 3축 입력
  • 특수한 3차원 이동 시스템

Trigger

Trigger는

"입력이 어느 조건을 만족했을 때 Input Action을 발동시킬 것인가?"

를 결정합니다.

예를 들어 단순히 키를 누르는 것뿐만 아니라 일정 시간 누르고 있거나, 키를 떼는 순간 등의 조건을 만들 수 있습니다.

Pressed

키가 눌리는 순간을 감지합니다.

예:

스페이스바를 누르는 순간 → 점프

Hold

키를 일정 시간 이상 누르고 있는 경우 작동합니다.

예:

상호작용 키를 1초간 누름 → 문 열기

Released

누르고 있던 키를 놓는 순간 작동합니다.

예:

활시위를 당기고 있다가 버튼을 놓음 → 화살 발사

이외에도 Tap, Chorded Action 등 여러 종류의 Trigger를 조합할 수 있습니다.


Modifier

처음에는 Modifier가 Trigger보다 훨씬 이해하기 어려웠습니다.

정리해 보니 Trigger와 Modifier의 차이는 다음과 같습니다.

Trigger
"언제 실행할 것인가?"

Modifier
"들어온 입력 값을 어떤 값으로 바꿀 것인가?"

예를 들어 마우스를 오른쪽으로 움직였을 때 1이라는 값이 들어왔다고 생각해 보겠습니다.

Modifier를 사용하면 이 1이라는 입력값을 실제 게임 로직으로 보내기 전에 가공할 수 있습니다.

Scalar

입력 값에 특정 배율을 곱합니다.

원래 입력 : 1
Scalar × 2
결과 : 2

마우스 감도를 높이는 것과 비슷하게 사용할 수 있습니다.

Negate

입력값의 방향을 반대로 바꿉니다.

1 → -1

카메라 축을 반전하거나 WASD에서 왼쪽/뒤쪽 방향 값을 만드는 데 사용할 수 있습니다.

Dead Zone

일정 크기보다 작은 입력값을 무시합니다.

게임패드의 아날로그 스틱은 손을 놓고 있어도 아주 작은 입력값이 발생할 수 있습니다.

예를 들어,

0.01
0.02
0.03

같은 작은 입력을 무시하도록 만들어 캐릭터가 가만히 있는데 조금씩 움직이는 현상을 방지할 수 있습니다.

즉, Modifier는

입력 장치에서 들어온 값을 게임에서 사용하기 좋은 형태로 가공하는 과정

이라고 이해하면 조금 더 와닿습니다.


Input Mapping Context에서 키 매핑하기

Swizzle Input Axis Values란?

Swizzle Input Axis Values는 입력 값의 축 순서를 바꾸는 Modifier입니다.

처음에는 이것이 상당히 이해하기 어려웠습니다.

WASD를 예로 들어 보겠습니다.

키보드의 W키 하나는 기본적으로 하나의 값만 전달하는 1차원 입력입니다.

하지만 IA_MoveAxis2D로 만들었다면 다음과 같은 값이 필요합니다.

X = 좌우
Y = 앞뒤

여기서 D키의 입력값 1은 X축에 그대로 넣으면 됩니다.

D → X = +1

A키는 반대 방향이므로 Negate를 사용합니다.

A → X = -1

문제는 W와 S입니다.

W를 눌렀을 때 들어오는 1차원 입력도 기본적으로 X 위치에 들어오기 때문에, 이것을 Y축 값으로 옮겨야 합니다.

이때 사용하는 것이 Swizzle Input Axis Values입니다.

W
1차원 입력 +1
↓
Swizzle
↓
Y = +1

S는 여기에 Negate까지 적용합니다.

S
1차원 입력 +1
↓
Swizzle
↓
Y = +1
↓
Negate
↓
Y = -1

따라서 WASD를 하나의 Axis2D Input Action으로 묶으면 대략 다음과 같이 정리할 수 있습니다.

W → Swizzle → Y +1
S → Swizzle + Negate → Y -1
A → Negate → X -1
D → 그대로 → X +1

처음에는 Swizzle이라는 이름 때문에 굉장히 복잡한 기능처럼 느껴졌지만,

"지금 X 자리에 들어온 값을 Y 자리로 옮겨 주세요."

정도로 이해하면 훨씬 간단합니다.


Look 입력에서도 프로젝트에서 원하는 마우스 조작 방향에 따라 특정 축에 Negate를 적용하여 상하 또는 좌우 방향을 반전시킬 수 있습니다.

중요한 것은 카메라 입력이라서 반드시 Y축을 반전해야 하는 것은 아니며, 원하는 조작 방향과 현재 입력값의 방향에 따라 결정해야 한다는 점입니다.


Trigger와 Modifier 다시 정리

둘이 자꾸 헷갈려서 한 문장으로 정리했습니다.

Modifier → 입력 값을 가공합니다.
Trigger  → 그 입력을 언제 실행할지 판단합니다.

예를 들어,

마우스 Y 입력
↓
Negate Modifier
↓
방향 반전
↓
Trigger 조건 확인
↓
IA_Look 실행

과 같은 흐름으로 이해할 수 있습니다.

Enhanced Input의 구조를 전체적으로 보면 다음과 같습니다.

키보드 / 마우스 / 게임패드
        ↓
Input Mapping Context
        ↓
Modifier
        ↓
Trigger
        ↓
Input Action
        ↓
C++ / Blueprint 로직

정확한 내부 처리 과정을 모두 표현한 그림은 아니지만, 각 요소가 어떤 역할을 담당하는지 구분하기 위한 학습용으로는 이렇게 기억해 두려고 합니다.


참고 자료

https://shjz.tistory.com/32


덤: 다중 커서 사용법

코드를 작성하다가 다중 커서를 사용하는 방법도 알게 되었습니다.

Alt + Ctrl + 클릭

여러 위치를 동시에 수정할 때 꽤 편리하게 사용할 수 있습니다.

profile
게임개발 및 아트, TA

0개의 댓글