포스트

TIL 2026-04-20

TIL 2026-04-20

TIL — 2026-04-20

오늘 한 일 요약

  1. 7번 과제 도전 구현 마무리 — C++ 6DOF 드론 Pawn 물리 구현 완성
  2. 8번 과제 Void 구현계획 + VoidUnreal C++ 모듈 분리
  3. Ch3 팀플 MVP 브레인스토밍 작성

1. Enhanced Input 세팅 (IMC_Drone)

IA Value Type 변경

생성 직후 기본값이 Digital(bool) 이므로 먼저 바꿔야 입력값이 제대로 넘어온다.

IAValue Type
IA_MoveAxis2D (Vector2D)
IA_LookAxis2D (Vector2D)
IA_MoveUpAxis1D (float)
IA_RollAxis1D (float)

IMC_Drone 키 매핑

IAModifier
IA_MoveW없음
 SNegate
 DSwizzle (YXZ)
 ASwizzle (YXZ) + Negate
IA_LookMouse XY 2D-Axis없음
IA_MoveUpSpace없음 (+1)
 Left ShiftNegate (-1)
IA_RollMouse Wheel Axis없음

핵심: Triggers / Modifiers는 IA 에셋이 아닌 IMC에서 키별로 설정한다.
ETriggerEvent::Triggered = 누르는 매 프레임 호출 (기본값, IA에서 따로 설정 안 해도 됨)


2. C++ Pawn 구조 정리

1
2
3
4
BoxComp (루트, 반크기 XY=50 / Z=15 — 드론 형태에 맞게 납작한 박스)
└─ MeshComp (SM_Drone)
└─ SpringArmComp (400cm)
   └─ CameraComp

AutoPossessPlayer = EAutoReceiveInput::Player0 — PlayerController 없이 바로 빙의
bUsePawnControlRotation = false — 카메라가 Pawn 회전을 따라가지 않음


3. 물리 구현 — 중력 + 착지 스냅

CheckGround

1
2
3
4
5
6
7
8
9
10
11
// 발밑 (박스 반높이 Z + 1cm) LineTrace로 착지 여부 반환
bool ANBC_AssignmentPawn::CheckGround(FHitResult& OutHit)
{
    const float HH = BoxComp->GetScaledBoxExtent().Z;
    FCollisionQueryParams Params;
    Params.AddIgnoredActor(this);
    return GetWorld()->LineTraceSingleByChannel(
        OutHit, GetActorLocation(),
        GetActorLocation() + FVector(0, 0, -(HH + 1.f)),
        ECC_Visibility, Params);
}

Tick 물리 루프

1
2
3
4
5
6
7
8
9
10
11
12
13
매 프레임:
  CheckGround → bIsGrounded
  if 착지:
    FallVelocity = 0, Z 스냅
  else:
    if bAscending:
      중력 누적 억제 (Space 누르는 동안)
    else:
      FallVelocity += GravityZ * DeltaTime
      예측 위치(PredictedLoc)에서 LineTrace
        충돌 → 이동 전 스냅 (바닥 뚫기 방지)
        없으면 → AddActorWorldOffset
    bAscending = false (매 프레임 리셋)

4. 헤맨 점과 해결

Space 바 통통 튀는 현상

  • 원인: FallVelocity가 한 프레임에 크게 누적돼 드론이 바닥 아래로 터널링 → 스냅이 위로 밀어내면서 반복
  • 해결: 다음 프레임 위치를 먼저 예측(PredictedLoc)하고 그 지점에서 LineTrace → 충돌 시 이동 전에 스냅

착지 중 Shift 하강 / 마우스 Pitch 차단

  • Shift 하강 차단: if (bIsGrounded && V < 0.f) return; — 지상에서 아래로 파고드는 입력 무시
  • Pitch 차단: const float Pitch = bIsGrounded ? 0.f : V.Y; — 착지 중 상하 회전 억제, 좌우(Yaw)는 허용

Space 바 지속 상승 불가

  • 원인: GravityZ = -980MoveSpeed = 600을 이겨서 약 0.6초 후 하강 전환
  • 해결: bAscending 플래그 — Space 입력 프레임에 true 세팅, Tick에서 중력 누적 건너뜀

Space 바 기울기 방향이 아닌 월드 Z로만 이동

  • 원인: AddActorLocalOffset(GetActorUpVector() * ...)GetActorUpVector()는 월드 공간 벡터인데 로컬 오프셋으로 넘겨서 이중 회전 발생. 드론이 수평일 때는 우연히 일치해 보임
  • 해결: AddActorWorldOffset(GetActorUpVector() * V * MoveSpeed * DT, true) 로 변경

WASD 방향이 이상함 (카메라 회전 후)

  • 원인: GetActorForwardVector()가 Pitch 기울기를 포함 → 드론이 기울어지면 W가 비스듬하게 이동
  • 해결: Yaw만 추출한 회전 행렬에서 Forward/Right 계산
1
2
3
4
const FRotator YawOnly = FRotator(0.f, GetActorRotation().Yaw, 0.f);
const FVector Forward = FRotationMatrix(YawOnly).GetScaledAxis(EAxis::X);
const FVector Right   = FRotationMatrix(YawOnly).GetScaledAxis(EAxis::Y);
AddActorWorldOffset((Forward * V.X + Right * V.Y) * MoveSpeed * DeltaTime, true);

CapsuleComponent → BoxComponent 교체

  • 이유: 캡슐은 반지름·반높이만 조절 가능 — 드론처럼 X/Y/Z를 각각 다르게 맞춰야 할 때 부적합
  • 방법: #include "Components/BoxComponent.h", SetBoxExtent(FVector(50, 50, 15)), 반높이 참조는 GetScaledBoxExtent().Z
  • 장점: 에디터 Details 패널에서 XYZ 개별 조정 가능 → 메시에 정확히 피팅

LineTrace 시작점이 액터 중심 → 바닥 뚫기 가능성

  • 원인: CheckGround 트레이스가 GetActorLocation()(중심)에서 시작 — 드론이 기울어지면 실제 박스 바닥이 중심보다 낮은 다른 위치에 있어서 감지가 틀어짐
  • 해결: 박스 로컬 바닥점 (0, 0, -HH)GetActorTransform().TransformPosition() 으로 월드 변환한 위치에서 1cm만 내려서 트레이스
1
2
const FVector WorldBottom = GetActorTransform().TransformPosition(FVector(0.f, 0.f, -BoxComp->GetScaledBoxExtent().Z));
GetWorld()->LineTraceSingleByChannel(OutHit, WorldBottom, WorldBottom + FVector(0, 0, -1.f), ...);
  • 스냅 오프셋도 수정: 기울어진 상태에서는 HH(로컬 Z 반크기)가 실제 월드 Z 거리와 다름 → BottomOffset = Location.Z - WorldBottom.Z 로 계산
1
2
const float BottomOffset = Location.Z - WorldBottom.Z;  // 기울기 반영
SnapLoc.Z = ImpactPoint.Z + BottomOffset;

5. 오늘 배운 것 정리

  • LineTrace 충돌 채널: ECC_Visibility 기준으로 바닥이 막혀있어야 감지됨. 콜리전 없는 Static Mesh는 무시된다.
  • 바닥 뚫기 방지는 예측이 핵심: 이동 스냅이 아니라 이동 에 예측 위치를 체크해야 바운스가 없다.
  • bAscending 패턴: 입력 콜백에서 플래그만 세팅하고 실제 처리는 Tick에서 — 입력과 물리를 분리하는 깔끔한 구조.
  • FRotationMatrix(YawOnly): 6DOF 회전 중 수평 이동만 뽑아낼 때 유용. Pitch/Roll 기울어짐과 무관하게 카메라 방향으로 이동 가능.
  • AddActorLocalOffset vs AddActorWorldOffset: GetActorUpVector()는 월드 공간 벡터이므로 AddActorLocalOffset에 넘기면 이중 회전이 적용된다. 액터 Up 방향 이동은 반드시 AddActorWorldOffset(GetActorUpVector() * ...) 으로 써야 한다.
  • CapsuleComponent → BoxComponent: 드론처럼 납작한 형태는 캡슐보다 박스가 적합. SetBoxExtent(FVector(X, Y, Z))로 반크기를 지정하고, 반높이는 GetScaledBoxExtent().Z로 읽는다. 에디터 Details 패널에서 XYZ를 개별 조정 가능해 메시에 정확히 맞출 수 있다.
  • 착지 중 입력 제한: bIsGrounded 플래그를 입력 핸들러에서 체크해 Shift(하강)와 마우스 Pitch(상하 회전)를 차단. 단순한 early return / 조건식으로 자연스러운 지상 동작을 구현할 수 있다.
  • LineTrace는 반드시 월드 변환된 바닥점에서: GetActorLocation()(중심)에서 쏘면 기울어진 액터에서 실제 바닥 접촉점을 놓친다. GetActorTransform().TransformPosition(로컬_바닥점) 으로 월드 바닥 위치를 구한 뒤 트레이스해야 정확하다. 스냅 오프셋도 Location.Z - WorldBottom.Z 로 기울기를 반영해야 한다.

6. 8번 과제 — Void Tier 0 프로토타입 구현계획

개요

항목내용
프로젝트Void — 쿼터뷰 좀비 서바이벌 익스트랙션
카메라쿼터뷰 Top-Down 45°
마감2026-04-30 (10일)

3웨이브 구조 (익스트랙션 루프 재해석)

Wave내용시간
Wave 1 진입저위험 파밍, 좀비 소수3분
Wave 2 심층중위험 파밍, 사이렌 이벤트(좀비 몰림)4분
Wave 3 탈출차량 부품 3개 수집 → 장착 → 시동4~5분

탈출 성공 시만 점수 인정 — Void 익스트랙션 철학.

타르코프식 무게 시스템

1
2
3
4
5
WeightRatio = TotalWeight / MaxCarry (MaxCarry = 30kg)

이동속도 배수 = 1.0 - (WeightRatio × 0.6)   // 최대 60% 감소
소음 배수     = 1.0 + (WeightRatio × 1.2)   // 최대 120% 증가
스태미나 소모 = 1.0 + (WeightRatio × 1.0)   // 최대 2배
  • 배터리(8kg) + 연료통(12kg) 동시 소지 = 이속 −40%, 소음 +80%
  • 부품 합산 22kg → 한 번에 못 옮김 → 전략적 동선 필수

VoidUnreal C++ 모듈 구조 (36개 파일)

1
2
3
4
5
6
7
8
Source/VoidUnreal/
├── Core/        AVOIDGameMode / AVOIDGameState / AVOIDPlayerController / UVOIDGameInstance
├── Characters/  AVOIDBaseCharacter / AVOIDPlayerCharacter / AVOIDZombieCharacter
├── AI/          AVOIDZombieAIController
├── Components/  UVOIDHealthComponent / UVOIDInventoryComponent / UVOIDNoiseComponent / UVOIDDebuffComponent
├── Items/       UVOIDItemDataAsset / IVOIDItemInterface / AVOIDPickupBase / AVOIDVehiclePart
├── Waves/       FVOIDWaveData / AVOIDSpawnVolume
└── UI/          UVOIDHUDWidget

네임 컨벤션: VOID 대문자 접두사, VOIDUNREAL_API 매크로, TObjectPtr<T> 포인터 기본형.

“Noise is Currency” 구현 흐름:

1
2
3
4
입력 → PlayerCharacter (발걸음·사격·픽업)
     → NoiseComponent::EmitNoise(Source, WeightMultiplier)
     → OnNoiseChanged → HUD 업데이트
     → BroadcastNoise(Radius) → 좀비 탐지

과적 상태이면 소음이 최대 2.2× 증폭 → 좀비가 더 잘 몰림.


7. Ch3 팀플 MVP 브레인스토밍

현재 상태 진단

  • ✅ 컨셉: 좀비 TPS + 뱀서 장르 융합
  • ⚠️ 문제: “이 게임만의 시그니처 훅”이 없음

추천 조합

선택이유
세계관 (A)A2 K-좀비 (서울 지하철/아파트)한국 개발자 팀 — 익숙한 공포
주인공 (B)B3 감염된 각성자체력 시스템에 서사 내장, 감염도 메카닉 직결
시그니처 (C)C1 처형 연쇄 + C3 감염도 타이머SM2 타격감 + 뱀서 생존 리듬
무기 (F)F3 하이브리드 (근접 고정 + 원거리 카드)4주 스코프에 적합
이동 (G)G1 단순 대시 (쿨다운 1초, 무적 없음)감염 타이머로 긴장감 이미 확보
카드 트리거 (H)H1 웨이브 클리어 후감염 타이머 리듬 보존
워크플로우 (W)W3 병렬 타임박스04/21~23 에셋 탐색 + 초안 병행

게임 가제 “Infected”

서울 한복판, 당신도 이미 감염되었다. 좀비를 처형할수록 인간성이 회복되지만, 시간이 흐를수록 감염도는 올라간다. 5웨이브를 버텨 살아남거나 — 스스로 괴물이 되거나.

한 줄 피치: “감염 타이머를 안은 처형 액션 서바이벌”

다음 액션

  • 04/21~23: 에셋 탐색 타임박스 (좀비/환경/무기/VFX — 각자 후보 3개)
  • 04/23: 킥오프 회의 — A~H 11개 항목 확정
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.