언리얼엔진/구현·실습

Unreal Engine 캐릭터 시스템 구현기

write76465 2026. 7. 1. 11:49

입력, 이동, 애니메이션, UI, 플레이어 상태 관리까지

이번 프로젝트의 Character 폴더는 플레이어가 실제로 조작되는 흐름을 중심으로 구성되어 있다. 단순히 캐릭터를 움직이는 코드만 있는 것이 아니라, 입력 처리, 카메라 기반 이동, 달리기, 애니메이션 상태 갱신, UI 입력 모드 전환, 플레이어 데이터 복제와 저장까지 여러 시스템이 연결되어 있다.

핵심 클래스는 다음과 같다.

  • APlayerCharacter
  • UPlayerCharacterAnimInstance
  • AMyPlayerController
  • APlayerCharacterState
  • ATestGameMode

각 클래스는 하나의 거대한 캐릭터 클래스에 모든 기능을 몰아넣지 않고, 역할에 따라 책임을 나누는 방식으로 구현되어 있다.


1. PlayerCharacter: 플레이어 조작의 중심

APlayerCharacter는 ACharacter를 상속받아 실제 플레이어가 조작하는 Pawn 역할을 한다.

생성자에서는 캐릭터 메시의 위치와 회전을 조정하고, 3인칭 시점을 위한 SpringArmComponent와 CameraComponent를 생성한다. 카메라 붐은 컨트롤러 회전을 따라가도록 설정되어 있으며, 카메라는 붐에 부착된다.

이 구조 덕분에 마우스 입력으로 시점을 돌리면 카메라가 캐릭터 주변을 자연스럽게 회전한다. 즉, 기본적인 구조는 Unreal Engine에서 흔히 사용하는 TPS 캐릭터 구조를 따른다.


2. Enhanced Input 기반 입력 처리

입력 처리는 SetupPlayerInputComponent에서 이루어진다.

이번 프로젝트에서는 기존 Input 방식이 아니라 Unreal Engine의 Enhanced Input 시스템을 사용했다. 이동, 시점 회전, 점프, 달리기, 옵션 UI 열기 같은 기능이 각각 Input Action으로 분리되어 있다.

이 방식의 장점은 명확하다.

  • 키 입력을 코드에 직접 고정하지 않아도 된다.
  • 키보드, 마우스, 게임패드 입력을 유연하게 매핑할 수 있다.
  • 입력 액션 단위로 기능을 관리할 수 있다.
  • 기능이 추가되어도 입력 구조를 비교적 깔끔하게 확장할 수 있다.

실제 Mapping Context 등록은 AMyPlayerController::BeginPlay에서 처리된다. 즉, 캐릭터는 “입력이 들어왔을 때 무엇을 할지”를 담당하고, 컨트롤러는 “어떤 입력 체계를 사용할지”를 담당하는 구조다.


3. 카메라 기준 이동 구현

Move 함수에서는 입력값을 FVector2D로 받아온다.

여기서 중요한 점은 이동 방향을 월드 기준으로 고정하지 않는다는 것이다. 컨트롤러의 회전값 중 Yaw만 가져와서 전방 벡터와 우측 벡터를 계산한다.

그 결과 플레이어는 카메라가 바라보는 방향을 기준으로 이동하게 된다.

예를 들어 카메라가 오른쪽을 바라보고 있다면, W 키를 눌렀을 때 월드의 X축 방향이 아니라 화면에서 앞으로 보이는 방향으로 이동한다. TPS 게임에서 기대하는 “화면 기준 이동”이 이 방식으로 구현된다.

또한 이동 입력값을 기반으로 LastInputDirection을 갱신한다. 이 값은 이후 애니메이션 인스턴스에서 전후좌우 이동 방향을 구분하는 데 활용된다.


4. 달리기 구현과 서버 RPC

달리기는 StartSprint, StopSprint 함수에서 처리된다.

로컬에서는 먼저 MaxWalkSpeed를 변경해 즉각적인 반응성을 확보한다. 동시에 서버 RPC를 호출해 서버에서도 같은 이동 속도 상태를 반영한다.

서버에서는 ServerStartSprint_Implementation, ServerStopSprint_Implementation을 통해 이동 속도와 bIsSprinting 값을 변경한다.

bIsSprinting은 DOREPLIFETIME을 통해 복제 대상으로 등록되어 있기 때문에, 네트워크 환경에서도 달리기 상태를 다른 클라이언트와 공유할 수 있다.

이 구조는 멀티플레이 캐릭터 구현에서 중요한 포인트다. 클라이언트의 입력 반응성은 유지하면서도, 최종 상태는 서버가 관리하도록 설계되어 있다.


5. AnimInstance: 애니메이션에 필요한 상태 계산

UPlayerCharacterAnimInstance는 캐릭터의 애니메이션 상태를 계산하는 클래스다.

NativeUpdateAnimation에서는 매 프레임 캐릭터 정보를 가져와 다음 값들을 갱신한다.

  • 이동 속도
  • 이동 방향
  • 공중 상태
  • 정지 애니메이션 재생 여부

속도 계산 시에는 Z축 값을 제거하고 평면 이동 속도만 사용한다. 이렇게 하면 점프나 낙하 중 발생하는 수직 속도가 걷기, 달리기 애니메이션 계산에 영향을 주지 않는다.

이동 방향은 UKismetAnimationLibrary::CalculateDirection을 사용해 계산한다. 이 값은 블렌드 스페이스에서 전방, 후방, 좌우 이동 애니메이션을 자연스럽게 섞는 데 사용할 수 있다.

또한 이전 프레임의 이동 상태와 현재 이동 상태를 비교해 bShouldPlayStop 값을 만든다. 이를 통해 캐릭터가 이동하다가 멈추는 순간에 정지 애니메이션을 재생할 수 있다.

 


6. PlayerState를 통한 플레이어 데이터 복제

APlayerCharacterState는 플레이어 데이터를 관리한다.

핵심 데이터인 FPlayerDatas는 ReplicatedUsing=OnRep_PlayerInfo로 선언되어 있다. 서버에서 플레이어 데이터가 변경되면 클라이언트에서는 OnRep_PlayerInfo가 호출된다.

또한 OnPlayerDataChanged 델리게이트를 브로드캐스트해 다른 시스템이 데이터 변경을 감지할 수 있도록 되어 있다.

이 구조는 UI, 인벤토리, 스탯 시스템과 연결하기 좋다. 플레이어 데이터가 변경될 때마다 관련 시스템이 직접 값을 계속 확인하지 않아도 되고, 변경 이벤트를 받아 필요한 처리를 할 수 있다.


7. GameMode에서 접속과 저장 처리

ATestGameMode는 플레이어 접속과 퇴장 시점의 데이터 처리를 담당한다.

PostLogin에서는 플레이어의 UniqueId를 기준으로 저장 데이터를 찾는다. 기존 데이터가 있으면 복원하고, 없으면 새 데이터를 생성해 저장한다.

로그아웃 시에는 현재 PlayerState의 데이터를 다시 저장 시스템에 넘긴다.

흐름을 정리하면 다음과 같다.

플레이어 접속
→ UniqueId 확인
→ 저장 데이터 로드
→ PlayerState에 적용

플레이어 퇴장
→ PlayerState 데이터 가져오기
→ 저장 시스템에 저장

이렇게 GameMode가 접속과 퇴장 흐름을 관리하고, PlayerState가 실제 플레이어 데이터를 보관하는 구조로 나뉘어 있다.


전체 구조 정리

이번 캐릭터 시스템은 다음과 같은 책임 분리를 가진다.

PlayerCharacter
→ 실제 캐릭터 조작, 이동, 점프, 달리기 입력 처리

PlayerCharacterAnimInstance
→ 애니메이션에 필요한 이동 상태 계산

MyPlayerController
→ Enhanced Input Mapping Context 등록, UI 입력 모드 전환

PlayerCharacterState
→ 플레이어 데이터 복제 및 변경 이벤트 처리

TestGameMode
→ 접속/퇴장 시 플레이어 데이터 로드 및 저장

이 구조의 장점은 각 클래스의 역할이 비교적 명확하다는 점이다. 캐릭터 클래스 하나에 모든 기능을 몰아넣지 않고, 입력, 애니메이션, UI, 데이터 저장 흐름을 분리하고 있다.


개선해볼 수 있는 부분

현재 구현에서 개선해볼 수 있는 부분도 있다.

첫 번째는 달리기 속도 값이다. 코드에 300, 600처럼 직접 작성된 값이 있다면, 이미 선언된 WalkSpeed, SprintSpeed 변수로 통일하는 편이 좋다. 이렇게 하면 나중에 밸런싱을 조정할 때 코드 곳곳을 찾아다니지 않아도 된다.

두 번째는 LastInputDirection의 복제 여부다. 현재 이 값이 로컬 애니메이션에서만 필요하다면 문제가 없지만, 다른 클라이언트에서도 같은 방향 기반 애니메이션이 필요하다면 복제나 서버 동기화 여부를 검토할 수 있다.

세 번째는 UI 입력 처리 확장성이다. 현재 옵션 UI만 처리한다면 충분하지만, 인벤토리, 상점, 맵 UI 등이 추가된다면 UI 상태를 통합 관리하는 구조가 필요해질 수 있다.


마무리

이번 캐릭터 시스템은 단순한 이동 구현을 넘어서, 실제 게임 플레이에 필요한 여러 요소를 함께 다루고 있다.

카메라 기준 이동으로 TPS 조작감을 만들고, Enhanced Input으로 입력 구조를 유연하게 관리한다. 또한 서버 RPC와 변수 복제를 통해 멀티플레이 환경에서 달리기 상태를 동기화하고, AnimInstance에서는 애니메이션에 필요한 상태를 매 프레임 계산한다.

여기에 PlayerState와 GameMode를 활용한 데이터 저장 흐름까지 연결되어 있어, 캐릭터 시스템이 게임 전체 구조 안에서 어떻게 동작하는지 확인할 수 있었다.

결과적으로 이 구조는 “플레이어가 움직인다”는 단순한 기능을 입력, 이동, 애니메이션, UI, 네트워크, 저장 시스템이 함께 만드는 과정으로 잘 보여준다.