티스토리 뷰

🟩 학습 목표

  • 기존의 하드코딩된 변수 구조를 탈피하고 확장성 높은 Gameplay Tag 기반 상태 제어 아키텍처 구축
  • 향상된 입력(Enhanced Input)과 바인딩되는 하체 기본 액션(걷기, 달리기, 앉기, 점프)을 토글 방식 구현
  • 멀티스레드 애니메이션 환경의 Thread-Safe 제약 조건을 우회하고 모션 매칭과의 물리 속도 동기화 수행

🟧 1. 구현 내용 (What I Did)

🟦 Gameplay Tag 기반 상태 제어 시스템 도입

기존의 복잡한 하드코딩 Enum 방식이나 단순 bool 변수(bIsWalking 등)들을 전면 제거하였다. 대신 확장성이 뛰어난 Gameplay Tag 시스템으로 아키텍처를 교체하였다. NCBaseCharacter에 CurrentGaitTag(이동 상태)와 CurrentStanceTag(자세 상태) 속성을 추가하여 C++에서 캐릭터의 모든 상태를 직관적이고 유연하게 관리하도록 설계하였다.

🟦 하체 기본 액션 구현 및 토글(Toggle)화

입력 이벤트를 기존의 Triggered(홀드) 방식에서 Started(단일 클릭) 방식으로 변경하여 유저 편의성을 높인 토글 형태로 구현하였다.

  • 달리기(Sprint): StartSprint / StopSprint 함수를 호출하여 MaxWalkSpeed를 동적으로 변경(500 ↔ 800)하고 관련 이동 태그를 갱신하도록 처리하였다.
  • 걷기(Walk): 현재 이동 태그를 검사하여 스니킹 속도(150)와 기본 조깅 속도(500) 사이를 상호 전환하는 ToggleWalk 함수를 구현하였다.
  • 앉기(Crouch): 엔진 내장 bIsCrouched 상태 및 스탠스 태그를 확인하여 서기 ↔ 앉기를 전환하는 ToggleCrouch 함수를 구현하였다. 이를 위해 생성자 단계에서 GetNavAgentPropertiesRef().bCanCrouch = true 설정 작업을 선행하였다.
  • 점프(Jump): ACharacter 라이브러리 기본 함수인 Jump와 StopJumping을 향상된 입력 체계에 직접 바인딩하여 작동되도록 구성하였다.

🟦 모션 매칭 및 애니메이션 시스템 연동 디테일 향상

  • 애니메이션 속도 동기화: 캐릭터의 물리적인 이동 속도(MaxWalkSpeed)를 GAS 오리지널 애니메이션의 기준 속도(조깅: 500, 스니킹: 150)와 완벽히 일치시켰다. 이를 통해 발이 미끄러지거나 비정상적으로 떨리는 발 꼬임 현상(Thrashing)을 근본적으로 해결하였다.
  • 카메라 래그(Camera Lag) 적용: Crouch 호출 시 캐릭터 캡슐 컴포넌트의 크기가 급격히 변하면서 카메라 뷰포트가 위아래로 튀는 이질감이 발생하였다. 이를 방지하고자 SpringArmComponent에 bEnableCameraLag = true를 설정하고 보간 스무딩을 적용하여 완벽하게 제어하였다.
  • 무한 착지 모션 방지(Quick Fix): 착지 시 캐릭터의 bJustLanded 상태가 해제되지 않고 무한 유지되는 버그를 발견하였다. GASP 원본 설계 의도를 반영하여, Landed 함수 내부에서 FTimerHandle을 활용한 지연 리셋(0.15초) 로직을 적용함으로써 임시 상태 풀림 현상을 정상화하였다.

🟧 2. 트러블슈팅 및 배운 점 (Troubleshooting & Learnings)

🟦 Live Coding과 블루프린트 리플렉션 버그

C++ 코드에 UPROPERTY 변수를 새로 추가하고 라이브 코딩을 완료했음에도 블루프린트 에디터 검색 창에 에셋이 노출되지 않는 문제를 경험하였다. 이는 엔진의 일시적인 리플렉션 필터링 버그로 파악되었으며, 기존 노드의 핀 연결을 해제(Alt + Click)하고 다시 검색하거나 에디터를 재시작함으로써 인스턴스를 정상 인식시킬 수 있음을 파악하였다.

🟦 애니메이션 워커 스레드와 스레드 세이프(Thread-Safe) 제약

언리얼 엔진 5에서 도입된 다중 스레드 애니메이션 업데이트 환경으로 인해, 애니메이션 블루프린트(AnimGraph) 내부에서는 Switch on Gameplay Tag 노드를 사용할 수 없는 치명적인 제약(Non-thread safe 오류)에 부딪혔다.

이를 해결하기 위해 각 태그를 일대일로 비교하는 == (Equal) 노드와 Branch 노드를 복합적으로 결합한 폭포수 체인(Cascade Chain) 형태로 구조를 우회 설계하여 멀티스레드 호환성을 확보하였다.

🟦 유령 노드(Orphaned Node) 관리 규칙

C++ 캐스트 대상 클래스나 변수 타입 구조를 크게 수정했을 때, 기존 블루프린트에 배치되어 있던 Get/Set 노드들이 변경 사항을 즉각 반영하지 못하고 내부 충돌을 일으키는 유령 노드 현상을 확인하였다. 데이터 타입 개편 시에는 기존 컴포넌트 노드를 에디터 상에서 완전히 지우고 다시 배치해야 컴파일러 락을 방지할 수 있다는 점을 인지하였다.

🟦 객체지향적 입력 분배 아키텍처 수립

물리적 이동 및 점프와 같이 캐릭터 육체 데이터와 직접 통신하는 입력은 Character 클래스에 바인딩하고, UI 호출이나 시스템 명령 같은 제어 신호는 Player Controller에서 전담하여 분리 처리하는 구조적 설계의 당위성을 명확히 이해하였다.


🟧 3. 후속 작업 (Next Steps)

  • 제자리에 서서 시점을 회전할 때 발을 자연스럽게 떼는 방향 전환(Turn In Place) 메커니즘 추가 구현
  • 시선 상하 이동에 맞춰 캐릭터의 허리가 부드럽게 각도를 조절하도록 상체 로직(Aim Offset 및 Spine 구조체 회전)을 분석하고 C++ 클래스 통합

🟧 핵심 요약

  • 확장성 확보를 위해 하드코딩 방식을 전면 배제하고 C++ 레벨에서 Gameplay Tag 제어 아키텍처를 정립하였다.
  • 물리 이동 속도와 모션 매칭 애니메이션 데이터 속도를 1:1 매핑하여 발 꼬임 현상을 제거하였다.
  • 애니메이션 스레드 세이프 오류는 Branch 노드 체인 우회 방식을 통해 해결하여 멀티스레드 안정성을 확보하였다.
  • 육체 액션(Character)과 시스템 명령(Controller)의 조작 권한을 엄격히 분리하여 객체지향 플러그형 입력 설계를 완수하였다.