Post

2026-04-09 TIL (33일차)

2026-04-09 TIL (33일차)

Unreal


• SetupPlayerInputComponent

플레이어의 물리적 입력을 캐릭터의 C++ 행동(함수)과 연결해 주는 핵심 함수입니다.

핵심 특징

  1. 타이밍: 게임이 시작되고 플레이어가 이 캐릭터에 ‘빙의(Possess)’하는 순간 엔진이 자동으로 한 번 호출하여 배선 작업을 세팅합니다.
  2. 플레이어 전용: NPC나 몬스터처럼 AI가 조종하는 폰(Pawn)에게는 이 함수가 호출되지 않습니다. 오직 Local Player이 조종할 때만 작동합니다.
  3. 상속: APawn (또는 ACharacter) 클래스에 기본적으로 제공되는 가상(Virtual) 함수이므로, 우리가 Override해서 사용합니다.


• CustomTick 계산공식

언리얼 엔진의 기본 Tick(DeltaTime)은 컴퓨터의 성능에 따라 프레임 호출되는 시간이 다릅니다.
하지만 물리 연산은 시간이 변하면 결과가 달라지거나 물리 엔진이 오류가 발생하는 문제가 생깁니다.


비유: ‘물통과 종이컵’

  • DeltaTime (현실 시간): 수도꼭지에서 불규칙하게 떨어지는 물방울
  • Accumulator (누적기): 물방울을 모아두는 물통
  • TimeStep (고정 시간): 정확히 1/60초의 양만 퍼낼 수 있는 정량 종이컵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
float Accumulator = 0.0 f;
const float TimeStep = 1.0 f / 60.0 f;

void AWeek3Drone::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    Accumulator += DeltaTime;

    while (Accumulator >= TimeStep)
    {
        CustomTick(TimeStep);
        Accumulator -= TimeStep;
    }
}

원리

코드원리
Accumulator += DeltaTime;[시간 누적] 흐른 시간(DeltaTime)을 누적기(Accumulator)에 계속 더해줍니다.
while (Accumulator >= TimeStep)[실행 조건] Accumulator가 설정한 프레임보다 많거나 같아질때만 실행하게 합니다.
CustomTick(TimeStep);[고정 연산] DeltaTime 대신, 고정된 TimeStep으로 Tick을 반복함
Accumulator -= TimeStep;[차감 및 이월] 연산을 했으니 Accumulator에서 TimeStep만큼 빼주고 남은 시간은 다음 프레임으로 이월됩니다.

이게 수직동기화인가?


• Input Mapping Context에 값이 들어오는 원리

Input Action(IA)을 만들 때, 이 액션이 어떤 형태의 값을 받을지 값 타입을 정해줍니다.


🔴 Digital (bool)

  • 개념: true 아니면 false만 존재합니다.
  • 사용처: Jump, 총 쏘기, 상호작용
  • 할당 원리: 키보드를 누르면 그냥 true가 전달됩니다.


🟢 Axis 1D (float)

  • 개념: 1개의 차원(X축)만 가지는 값입니다. 보통 -1.0에서 1.0 사이의 값을 가집니다.
  • 사용처: 게임패드의 트리거 버튼(살짝 누르기/세게 누르기), 마우스 휠 굴리기.
  • 할당 원리: 키보드(W)를 누르면 1.0이 그대로 들어옵니다. 만약 반대로 작동하게 하고 싶다면 Negate만 추가해 주면 됩니다.


🔵 Axis 2D & 3D

  • 키보드의 키는 1차원(1.0) 신호만 쏘는데, IA는 2칸(X,Y) 또는 3칸(X,Y,Z)을 열어두고 기다립니다.
  • 원칙: 모든 1차원 입력은 무조건 첫 번째 칸으로 들어간다

예시

키보드 스페이스바(Spacebar)를 눌렀을 때 IA(Vector3D)에 값이 꽂히는 과정을 봅시다.

  • 기본 상태: 스페이스바를 누르면 무조건 X축으로 꽂힙니다. -> (1.0, 0.0, 0.0)
  • 드론을 만들 때, 스페이스바는 보통 Z축(상승)으로 써야 합니다. 이때 Swizzle이 사용


Modifier

아무 설정도 하지 않으면, 언리얼은 들어온 값을 X축(앞뒤) 에 때려 넣습니다. 그래서 우리는 Modifier를 사용해 “값을 X가 아니라 Y로 보내”라고 지시해야 합니다.

  • Negate (반전): 들어온 값에 -1을 곱합니다.
  • Swizzle Input Axis Values (축 방향 틀기): 들어온 값의 위치(X, Y, Z)를 강제로 다른 축으로 보냅니다. (예: YXZ를 선택하면, 기본 X로 들어오던 값을 Y로 보냅니다.)


• 언리얼 이동 공식


이동할 거리 = 방향 × 속도 × 시간


언리얼 매칭표

기본 공식언리얼 엔진 (C++) 개념설명
방향 (입력)MoveInput (FVector)향상된 입력에서 받아온 1, 0, -1 등의 방향 값
속도MoveSpeed (float)1초에 얼만큼 이동할지 정해둔 변수 (예: 500.0f)
시간DeltaTime (float)프레임과 프레임 사이의 시간 (또는 FixedDeltaTime)
= 거리 (결과)DeltaLocation (FVector)이번 프레임에서 최종적으로 더해질 이동 결괏값


• 언리얼 엔진의 PITCH와 현실 Pitch


마우스 좌표 입력

‘마우스’의 특징은 모니터 화면은 왼쪽 맨 위가 (0, 0)이기 때문에 다음과 같이 작동합니다.

  • 마우스를 위로 올릴 때: Y값은 감소 (-) 합니다.
  • 마우스를 아래로 내릴 때: Y값은 증가 (+) 합니다.


현실 pitch의 행동

수학적인 3D 각도(Rotation)의 기준

  • 고개를 위로 들 때 (하늘 보기): Pitch 각도가 증가 (+) 합니다.
  • 고개를 아래로 숙일 때 (땅 보기): Pitch 각도가 감소 (-) 합니다.


언리얼에서의 pitch의 행동

언리얼 엔진에서 카메라를 제어하는 ‘컨트롤 로테이션(Control Rotation)’ 시스템에 값을 전달할 때는 결과가 정반대로 나타납니다.

  • 엔진 시스템에 값을 전달하면: - 값이 들어갈 때 위를 보고, + 값이 들어갈 때 아래를 봅니다.


그러면 왜 캐릭터 Movement를 이용할 때 Negate를 사용하는가?

제공되는 캐릭터나 PlayerController를 쓸 때, 입력 세팅(IMC)에서 마우스 Y축에 Negate(반전) 모디파이어를 넣는 이유가 바로 여기에 있습니다.

그 이유는 언리얼 내부 엔진 코드인 PlayerController::AddPitchInput(float Val)에 숨어있습니다. 이 함수 내부를 까보면, 우리가 넘겨준 값에 InputPitchScale (기본값이 음수, 주로 -1.0 또는 -2.5) 이라는 변수를 강제로 곱해버립니다.

  • 즉, 엔진이 내부적으로 들어온 값에 마이너스(-)를 한 번 더 곱해서 부호를 뒤집어 버리기 때문에, 우리가 원하는 정상적인 조작(마우스 올리면 위 보기)을 맞추기 위해 입력 단계에서 미리 Negate를 걸어 ‘마이너스의 마이너스’로 상쇄시키는 작업이 필요한 것입니다.


커스텀 구현 시: 그대로 값을 받아도 되는 이유

AddPitchInput 같은 엔진 기본 함수를 쓰지 않고, 직접 회전(Rotation) 공식을 코딩해서 구현한다면? 상황이 Negate가 필요 없어집니다.

  1. 마우스를 위로 올린다 -> 순수한 -이 들어온다.
  2. 엔진의 기본 함수를 안 거치므로 중간에 쓸데없이 -가 곱해지지 않는다.
  3. 마우스에서 넘어온 - 값을 우리가 짠 로직에 그대로 적용하면 언리얼의 Pitch 특성에 맞물려 위를 보게 구현할 수 있다.


• AddActorLocalOffset과 AddActorLocalRotation 매개변수

첫 번째 매개변수: DeltaLocation / NewRotation (변화량)

의미: “현재 상태에서 얼마나 더 움직이거나 회전할 것인가?”입니다.

예를 들어 현재 위치가 (100, 0, 0)일 때 DeltaLocation에 (5, 0, 0)을 넣으면 (105, 0, 0)으로 갑니다.

Local의 특징: 드론이 90도 꺾여 있다면, 월드의 X축이 아니라 드론이 바라보는 앞방향으로 5만큼 전진합니다.

두 번째 매개변수: bSweep (true / false)

이것이 질문하신 핵심 스위치입니다. 한 문장으로 정의하면 “가는 길에 장애물이 있는지 검사(Sweep)할 것인가?”입니다.

true (충돌 감지 모드):

캐릭터가 벽을 향해 이동할 때, 벽에 닿으면 더 이상 가지 못하고 멈춥니다.

CustomPhysicsTick에서 구현한 물리적 일관성을 유지하려면 반드시 true여야 합니다.

false (유령/텔레포트 모드):

벽이 있든 말든 상관하지 않고 계산된 좌표로 위치를 고정해버립니다.

결과적으로 벽을 뚫고 들어가는 현상이 발생합니다.

• SetCollisionProfileName

충돌 컴포넌트를 생성하고 충돌 프로필을 지정하지 않으면, 엔진은 기본값인 OverlapAllDynamic 상태로 만듭니다.

이름에서 알 수 있듯이 이 상태는 모든 것과 겹침을 허용하겠다는 뜻이라서 통과가 됩니다.

1
SphereComp->SetCollisionProfileName(TEXT("Pawn"));

바닥을 뚫고 떨어지거나 벽을 통과하지 않고 정상적으로 부딪히게(충돌하게) 만들어 준 핵심 코드입니다.


코드 해석

코드 부분역할 및 의미
SphereComp->이 드론의 가장 바깥쪽 껍질(루트 컴포넌트) 역할을 하는 구체(Sphere)에게 명령합니다.
SetCollisionProfileName“너의 충돌 세팅(Collision Settings)을 지금 내가 적어주는 ‘이름표’의 규칙대로 세팅해라!”
TEXT("Pawn")언리얼 엔진 프로젝트 세팅에 미리 만들어져 있는 “Pawn”이라는 충돌 규칙 세트(Preset)의 이름입니다.


• FHitResult

언리얼 엔진에서 Line Trace을 쏘거나 물체끼리 부딪혔을 때, 누가, 어디서, 어떻게 맞았는지에 대한 모든 정보가 담겨있는 결과 보고서(구조체)입니다.


자주 사용하는 정보 (변수 & 함수)

코드 (HitResult.XXX)의미
GetActor()정확히 누구랑 부딪혔어?
GetComponent()적의 어느 부위(컴포넌트)에 맞았어?
ImpactPoint정확한 3D 우주상의 타격 좌표가 어디야?
ImpactNormal맞은 표면이 어느 방향을 보고 있어?
Distance총구에서부터 맞은 곳까지 거리가 얼마야?
BoneName(캐릭터일 경우) 어느 뼈에 맞았어?


• FCollisionQueryParams

언리얼 엔진에서 Line Trace를 쏘거나 충돌 검사를 할 때, 누구는 무시하고, 얼마나 정밀하게 검사할 것인지 엔진에게 세팅해 주는 옵션/필터 구조체입니다.


자주 사용하는 함수

코드 (TraceParams.XXX)의미
AddIgnoredActor(AActor*)“얘는 쏴도 무시하고 통과시켜라!”
bTraceComplex“대충 박스로 치지 말고, 실제 모델링(폴리곤) 모양대로 아주 정밀하게 검사해라!”
bReturnPhysicalMaterial“맞은 곳의 재질(나무, 철, 살점) 정보도 같이 조사해 와라!”
This post is licensed under CC BY 4.0 by the author.