Youngki Jeong | Game Dev Lab
HomeProjectsEvidenceJourneyAbout
SearchGitHub글쓰기

Youngki Jeong | Game Dev Lab

Unreal C++ 게임개발 포트폴리오 & 개발 기록

Projects·Evidence·Journey·About·GitHub Portfolio·GitHub

빠른 검색

프로젝트, Evidence, 디버깅 노트, 학습 기록을 검색합니다.

← 목록으로
← 목록으로
IT-B01

ADS를 단순 FOV 변경이 아니라 CameraModeStack으로 나눈 이유

2026년 9월 7일
·
JEONGYOUNGKI
Description

Gameplay State와 Camera View 계산 및 Blend 책임을 CameraMode와 CameraModeStack으로 분리한 C++ Camera 구조를 설명한 글입니다.

Project

ImitationTrigger

ArticleType

Architecture

SourceBoundary

My Implementation

EvidenceLevel

Asset

Priority

S++

Tags

Camera

ProofType

Git

Audience

Technical Interviewer

CareerTrack

Game Client

목차

ImitationTrigger — ADS를 단순 FOV 변경이 아니라 CameraModeStack으로 나눈 이유

시작은 단순한 ADS 카메라였습니다

ADS만 놓고 보면 가장 빠른 구현은 단순합니다.

C++
if (bIsADS)
{
    Camera->SetFieldOfView(60.0f);
}
else
{
    Camera->SetFieldOfView(80.0f);
}

작은 기능이라면 이 방식도 충분합니다.

하지만 ImitationTrigger에서 ThirdPerson 카메라를 다루다 보니 ADS에서 바뀌는 것은 FOV 하나가 아니었습니다.

Text
Location
Rotation
FieldOfView
Pitch 기반 Offset
Shoulder 위치
Blend 시간

Camera 상태가 늘어날 때마다 Character나 Ability 안에 조건문을 추가하면 Gameplay State와 Camera 계산, 전환 로직이 한곳에 섞이기 쉬웠습니다.

그래서 이 프로젝트에서는 Camera를 값 하나가 아니라 CameraMode라는 상태 단위로 나누고, 선택된 Mode의 전환과 View 합성은 CameraModeStack이 담당하도록 구성했습니다.


CameraMode 선택과 실행을 분리했습니다

제가 Camera 구조를 다시 보면서 가장 중요하게 구분한 것은 다음입니다.

Text
Selection
!=
Execution

ADS 상태에서 CM_ThirdPerson_ADS를 선택할지 판단하는 것은 CameraModeStack의 책임이 아닙니다.

Text
Gameplay State
→ Camera Policy
→ Selected CameraMode Class

선택이 끝난 뒤부터 제가 구현한 Camera Runtime으로 넘어옵니다.

Text
Selected CameraMode
→ UITCameraComponent
→ UITCameraModeStack
→ View Update / Blend
→ Player View

즉 CameraModeStack은 RMB나 InputTag.ADS, GA_DefaultADS, CameraMode.ADS를 직접 알지 않습니다.

Text
CameraMode Selection
= 지금 어떤 CameraMode를 사용할 것인가?
 
CameraMode Execution
= 선택된 CameraMode를 실제 View로 어떻게 만들 것인가?

이 경계를 나누면서 Gameplay 상태와 Camera Presentation을 직접 결합하지 않을 수 있었습니다.


Camera Runtime의 역할도 나누었습니다

Camera 쪽의 핵심 Class는 다음과 같습니다.

Text
AITPlayerCameraManager
UITCameraComponent
UITCameraMode
UITCameraModeStack
UITCameraMode_ThirdPerson

각 역할을 가장 짧게 정리하면:

Text
AITPlayerCameraManager
= Engine Camera Pipeline 쪽 Anchor
 
UITCameraComponent
= Project Camera Runtime Hub
 
UITCameraMode
= Camera 상태 하나의 View / Blend State
 
UITCameraModeStack
= 선택된 Mode의 전환과 View 합성
 
UITCameraMode_ThirdPerson
= ThirdPerson 계열 View 위치 계산

UITCameraComponent::GetCameraView()에서는 현재 사용할 CameraMode를 받아 Stack을 평가하고, 최종 Location / Rotation / FOV를 Unreal의 Camera View로 전달합니다.

Text
GetCameraView
→ CameraMode 확인
→ PushCameraMode
→ EvaluateStack
→ Final View

기본 TPS와 ADS는 같은 ThirdPerson 계산을 공유합니다

기본 ThirdPerson과 ADS를 완전히 다른 Camera System으로 만들지는 않았습니다.

Text
공통 C++ 계산
= UITCameraMode_ThirdPerson
 
기본 TPS
= CM_ThirdPerson
 
ADS
= CM_ThirdPerson_ADS

UITCameraMode_ThirdPerson은 선택된 Mode가 실제로 어디에 Camera를 둘지 계산합니다.

큰 흐름은 다음과 같습니다.

Text
Pawn View Pivot
→ Pitch
→ TargetOffsetCurve
→ Local Offset
→ Pivot Rotation 기준으로 회전
→ View.Location

핵심은 TargetOffsetCurve에서 가져온 상대 Offset을 World 좌표에 그대로 더하지 않는다는 점입니다.

C++
const FVector TargetOffset =
    TargetOffsetCurve->GetVectorValue(PivotRotation.Pitch);
 
View.Location =
    PivotLocation +
    PivotRotation.RotateVector(TargetOffset);

이렇게 해야 Character와 View 방향이 바뀌어도 Camera Offset이 현재 방향을 기준으로 따라갈 수 있습니다.

제가 UITCameraMode_ThirdPerson에서 직접 구현한 핵심도 Pitch에 따라 다른 ThirdPerson Offset을 가져오고, 현재 View 방향에 맞춰 실제 Camera 위치로 변환하는 부분입니다.


TPS와 ADS의 차이는 CameraMode Asset에 둡니다

CM_ThirdPerson과 CM_ThirdPerson_ADS는 같은 ThirdPerson C++ Base를 사용하지만 각 Asset의 설정은 다를 수 있습니다.

Text
공통
- Pivot / Rotation
- Pitch Clamp
- TargetOffsetCurve Sampling 방식
- CameraModeStack Update / Blend
 
Mode별 설정
- FieldOfView
- BlendTime
- TargetOffsetCurve
- Camera Distance
- Shoulder Offset
- Pitch별 Offset

즉 ADS CameraMode는 새로운 Camera System이라기보다 같은 계산 구조 위에 ADS에 맞는 View 설정을 가진 CameraMode입니다.

이렇게 나누면 C++에서:

C++
if (bIsADS)
{
    // ADS 전용 위치 계산
}
else
{
    // TPS 위치 계산
}

같은 분기를 계속 늘리기보다, 공통 계산은 C++에 두고 상태별 View 차이를 CameraMode Asset으로 분리할 수 있습니다.


CameraModeStack은 “얼마나 섞을 것인가”를 담당합니다

UITCameraMode_ThirdPerson이 한 Mode의 View를 계산한다면, UITCameraModeStack은 이전 Mode와 새 Mode를 얼마나 섞을지 담당합니다.

Text
UITCameraMode_ThirdPerson
= 이 Mode의 Camera는 어디에 있어야 하는가?
 
UITCameraModeStack
= 이전 View와 새 View를 얼마나 섞을 것인가?

TPS에서 ADS로 전환될 때를 단순화하면:

Text
CM_ThirdPerson
 
→ CM_ThirdPerson_ADS Push
 
→ TPS View를 Base로
   ADS View Blend
 
→ ADS Weight 1.0
 
→ CM_ThirdPerson_ADS

합성하는 것은 FOV만이 아닙니다.

Text
Location
Rotation
ControlRotation
FieldOfView

가 하나의 Camera View로 함께 합성됩니다.

그래서 CameraModeStack을 단순한 FOV Transition System이라고 설명하지 않습니다.


Stack이 항상 더 좋은 설계라고 생각하지는 않습니다

Camera 상태가 ADS 하나뿐이고 앞으로 확장할 이유가 없다면:

Text
CurrentMode
PreviousMode
TransitionAlpha

정도의 더 작은 구조가 더 이해하기 쉬울 수도 있습니다.

CameraModeStack은 그보다 구조가 복잡합니다.

Text
장점
- Mode별 View 계산 분리
- Mode별 Blend 설정
- Gameplay State와 Camera Execution 분리
- 같은 전환 구조 재사용
 
비용
- Instance / Active Stack / Blend State를 이해해야 함
- Debug 지점 증가
- 상태가 단순하면 과설계가 될 수 있음

그래서 “Stack이 더 고급이기 때문에 사용했다”기보다, ImitationTrigger에서는 Camera 상태의 계산과 전환 책임을 분리하기 위해 이 구조가 필요했다고 설명하는 편이 정확합니다.


팀 Framework와 제 구현 범위도 구분합니다

이 Camera Runtime이 사용하는 상태의 upstream에는 팀 Framework가 있습니다.

Text
Input / GAS / PawnData / HeroComponent
= Existing Team Framework

제가 직접 구현으로 설명하는 Camera 영역은:

Text
AITPlayerCameraManager
UITCameraComponent
UITCameraMode
UITCameraModeStack
UITCameraMode_ThirdPerson

입니다.

또 CameraMode / Stack 패턴 자체를 제가 처음 만든 새로운 Unreal Camera Architecture라고 주장하지 않습니다. Unreal Camera 확장 지점과 Lyra-style CameraMode / Stack 패턴을 참고해 ImitationTrigger 프로젝트에 맞는 Camera Runtime으로 구현한 작업으로 설명합니다.


현재 검증 범위

Camera C++ Core는 Source / Git 기준으로 구현 근거가 확인되어 있습니다.

Text
Camera C++ Core
= SOURCE-CONFIRMED

하지만:

Text
Source / Git 구현 근거
!=
최신 TPS → ADS → TPS Runtime Result

입니다.

또 현재 Asset 검수에서는 기본 CM_ThirdPerson.TargetOffsetCurve가 Production Curve가 아니라 ITTest Plugin Curve를 참조하는 Miswire가 확인되어 있습니다.

따라서 현재는:

Text
말할 수 있음
→ Pitch 기반 TargetOffsetCurve Sampling 구조를 구현했다.
→ 기본 TPS와 ADS가 같은 ThirdPerson C++ Base를 사용한다.
→ CameraModeStack 기반 View 전환 구조를 구현했다.
 
말하지 않음
→ 현재 TPS / ADS Camera Tuning이 모두 최종 완료됐다.
→ 최신 TPS → ADS → TPS End-to-End Runtime이 검증 완료됐다.

로 구분합니다.


정리

ImitationTrigger에서 ADS를 CameraModeStack으로 나눈 이유는 단순히 구조를 복잡하게 만들기 위해서가 아니었습니다.

Text
Camera Policy
→ 어떤 Mode를 사용할지 선택
 
CameraMode
→ 한 상태의 View 계산
 
ThirdPerson Base
→ TPS / ADS 공통 위치 계산
 
CameraModeStack
→ Mode 전환과 View 합성
 
CameraComponent
→ 최종 Player View 출력

으로 책임을 나누기 위해서였습니다.

그 결과 기본 TPS와 ADS는 같은 ThirdPerson 계산을 공유하면서도, 각 상태의 View 설정과 전환은 서로 분리할 수 있었습니다.

다음 글에서는 Camera보다 한 단계 위로 올라가, InputTag.ADS → GA_DefaultADS → CameraMode.ADS → PawnData.CameraModeRules가 어떻게 이어지고 팀 Gameplay Framework의 State가 제가 구현한 Camera Runtime으로 넘어오는지 정리합니다.

빠른 검색

프로젝트, Evidence, 디버깅 노트, 학습 기록을 검색합니다.

목차