언리얼 엔진에서 캐릭터의 구성 요소는 무엇일까요?
게임 모드(Game Mode)/게임 스테이트(Game State) 섹션에서 언급했듯이 언리얼 엔진 게임플레이 프레임워크를 통해 언리얼 엔진에서 캐릭터가 작동하는 방식을 이해할 수 있습니다. 개념적으로 플레이어는 게임 월드에서 물리적 존재를 나타내는 폰과 행동을 결정하는 컨트롤러로 구성됩니다. 이렇게 분리되어 있는 것이 AI뿐 아니라 네트워크 리플리케이션에도 유용합니다. 참고로, 그 계층구조는 다음과 같습니다.
먼저 에셋을 임포트하고 폰을 생성한 후 플레이어 컨트롤러를 구성해 보겠습니다. 패럿(Parrot)에서 플레이어가 제어하는 주인공은 바르바로사 선장(Captain Barbarossa)입니다. Quaternius 웹사이트에서 Quaternius에서 제공하는 해적 키트(Pirate Kit)를 다운로드할 수 있습니다.
아트 에셋 임포트하기
원하는 3D 모델링 툴에서 메시/애니메이션을 엔진 호환 포맷으로 익스포트합니다. 이 경우에는 개발자가 .fbx 포맷을 사용했습니다. 바르바로사의 에셋은 Content/Assets/Quaternius/PirateKit/Characters/Barbarossa 아래에 있습니다. .fbx 파일은 컨텍스트 메뉴를 우클릭하거나 콘텐츠 브라우저로 드래그 앤 드롭하여 임포트할 수 있습니다. .fbx 임포트 세팅 창이 표시될 것입니다. 임포트에 적합한 옵션을 선택합니다. 이 경우, 기존 스켈레톤은 선택하지 말고 애니메이션 임포트(Import Animations)를 체크해야 합니다. 또한, 메시로 인덱싱할 수 있는 새 아틀라스 머티리얼을 생성해야 합니다. 이 머티리얼은 해적 키트의 에셋 전반에 걸쳐 재사용할 수 있습니다. 이제 .fbx에 포함된 모든 애니메이션과 다음과 같은 3개의 파일이 있을 것입니다.
정리를 위해 애니메이션 시퀀스 파일은 3개의 파일 옆에 있는 Animations 폴더 아래에 있습니다.
스켈레톤, 스켈레탈 메시 및 피직스 에셋
스켈레톤은 계층구조로서, 스켈레탈 메시에서 조인트라고도 하는 본을 정의하는 데 사용됩니다. 이러한 본은 3D 모델링 툴의 릭과 일치해야 합니다. 릭이 호환되는 한, 스켈레톤은 스켈레탈 메시 전체에 걸쳐 재사용할 수 있습니다. 피직스 에셋이 생성된 것도 확인할 수 있습니다. 피직스 에셋은 스켈레탈 메시가 사용하는 피직스와 콜리전을 정의합니다. 여기에는 시뮬레이션을 위한 리지드 바디와 컨스트레인트가 포함되어 있습니다. 스켈레탈 메시당 하나의 피직스 에셋만 가질 수 있으며 조건에 따라 켜거나 끌 수 있습니다. 피직스 에셋은 임포트할 때 조정할 수도 있고 스켈레탈 메시에서 새 에셋을 생성할 수도 있습니다.
애니메이션 블루프린트
이제 작업할 스켈레탈 메시가 생겼으니, 애니메이션 블루프린트로 애니메이팅을 시작할 수 있습니다. 애니메이션 블루프린트는 Unity의 Mecanim Animation System과 유사합니다. 애니메이션 블루프린트의 애니메이션 그래프는 익숙한 모습으로, 작동 방식도 비슷합니다. 두 시스템 모두에 애니메이션 스테이트를 최종 출력 포즈로 전환할 수 있는 조건부 로직 플로가 있습니다. 폴더에서 컨텍스트 메뉴를 통해 새 애니메이션 블루프린트를 생성하고 스켈레톤을 선택합니다. 애니메이션 블루프린트는 ABP_Captain_Barbarossa라는 이름으로 Content/Blueprints/Player 아래 생성될 것입니다.
블루프린트 인스펙터(Blueprint Inspector)의 왼쪽에 이벤트 그래프(Event Graph)와 애니메이션 그래프(Animation Graph)라는 두 개의 그래프가 있는 것이 보일 것입니다. 애니메이션 그래프는 최종 출력 포즈를 제어하는 스테이트 머신입니다. 이벤트 그래프에서는 애니메이션과 관련된 모든 로직을 정의할 수 있습니다. Mecanim 컴포넌트에 데이터를 전달해야 하는 Unity와 달리, 애니메이션 블루프린트는 주어진 이벤트 시점에 필요한 데이터를 가져올 수 있습니다. 예를 들어, 캐릭터의 속도를 쿼리하고 그 값에 따라 애니메이션을 구동하고 싶을 수 있습니다. 이를 깔끔하게 구현하는 방법은 이벤트 그래프에서 Event Blueprint Initialize Animation 노드를 사용하고, Get the Owning Actor를 통해 소유 액터를 가져온 후, 해당 무브먼트 컴포넌트를 변수에 캐시하는 것입니다. 그러고 나면 BlueprintThreadSafeUpdateAnimation의 틱마다 무브먼트 컴포넌트에서 바로 속도를 쿼리할 수 있습니다. 이벤트 그래프와 애니메이션 그래프 간에 변수를 공유할 수도 있습니다.
바르바로사에 대한 환경설정된 애니메이션 블루프린트는 직접 자세히 살펴볼 가치가 있습니다. 노드를 더블클릭하면 최종 출력 포즈에 대해 스테이트 머신, 스테이트 및 스테이트 규칙이 어떻게 환경설정되어 있는지 확인할 수 있습니다. 시퀀스 플레이어를 사용하고 블렌드 스페이스를 사용하는 예시도 있습니다. 마찬가지로, 이벤트 그래프는 애니메이션이 캐릭터 데이터에 의해 어떻게 구동되는지 보여줍니다. 이 관계는 이 문서를 계속 진행하면서 더 명확하게 알 수 있을 것입니다.
폰
이제 애니메이팅할 수 있는 스켈레탈 메시가 생겼으니 게임 모드에서 사용할 폰을 생성할 수 있습니다. 개발자들은 디폴트 폰 C++ 클래스의 자손 클래스인 언리얼 엔진의 디폴트 캐릭터를 기반으로 폰을 생성하기로 했습니다. 캐릭터 클래스는 이 게임에 특히 유용한 캐릭터 무브먼트 컴포넌트를 포함하고 있으며, 추가 구현의 기반이 되는 또 하나의 클래스입니다.
플레이어와 적 폰 모두 히트포인트, 피격, 사망과 같은 일부 기능을 공유합니다. 이 기능은 ACharacter에서 상속되는 AParrotCharacterBase C++ 클래스를 생성하여 공유됩니다. 이 클래스는 적 폰과 플레이어 폰이 서로 다른 행동을 하게 될 때 기반이 될 수 있는 좋은 베이스라인을 제공합니다.
다음으로, 플레이어별 로직을 처리할 AParrotPlayerCharacter C++를 생성합니다. 마지막으로, 캐릭터의 시각적 표현을 처리할 블루프린트를 생성합니다. 패럿의 블루프린트는 Content/Blueprints/Player 아래 있는 BP_ParrotPlayerCharacter입니다. 상속 계층구조는 다음과 같습니다.
BP_ParrotPlayerCharacter를 열면 에디터 창 왼쪽의 컴포넌트 인스펙터 아래에 몇 가지 컴포넌트가 보일 것입니다. 메시 컴포넌트(Mesh Component)에서는 스켈레탈 메시 에셋(Skeletal Mesh Asset)뿐 아니라 애님 클래스(Anim Class) 아래의 애니메이션 블루프린트를 설정할 수 있습니다. 메시가 올바르게 배치되지 않았다면, 트랜스폼 값을 조정하여 캡슐 컴포넌트에 맞춰 정렬할 수 있습니다. 결과적으로 다음과 같은 모습이어야 합니다.
다음으로, 게임 모드 에셋의 블루프린트에 디폴트 폰 클래스를 할당할 수 있습니다.
이제 게임에서 사용할 수 있는 애니메이팅되는 폰이 생겼습니다!
플레이어 컨트롤러
사용할 수 있는 폰이 있으니 이제 플레이어 컨트롤러를 생성할 수 있습니다. 플레이어 컨트롤러는 기본적으로 인간 플레이어의 의지를 나타냅니다. 게임을 설계할 때는 입력 이벤트를 정의할 위치를 고려해야 합니다. 폰이 변경되지 않는 간단한 게임의 경우, 폰에 정의하면 됩니다. 하지만 행동이 복잡해지기 시작하면, 플레이어 컨트롤러에 정의하는 것이 더 낫습니다. 폰은 휘발성이며 플레이어 컨트롤러에 의해 빙의되거나 빙의 해제될 수 있습니다. 폰은 스폰되거나 소멸될 수 있지만, 플레이어 컨트롤러는 게임 내내 지속됩니다.
플레이어 컨트롤러에는 C++ 로직이 필요하지 않으므로 필요한 작업을 수행할 블루프린트만 생성하면 됩니다. BP_ParrotPlayerController는 Content/Blueprints/Player 아래에 있습니다. 상속 계층구조는 다음과 같습니다.
이 블루프린트 안에는 몇 가지 기본 게임플레이 로직으로 이어지는 입력 이벤트 노드가 있는 것을 볼 수 있습니다. 이러한 입력 이벤트는 특히 향상된 입력(Enhanced Input)과 관련되어 있습니다. 해당 환경설정 방법에 대한 자세한 내용은 향상된 입력 문서에서 확인할 수 있습니다. 플레이 시작 시점에 패럿 플레이어 캐릭터 폰을 캐싱해 두면 나중에 Add Movement Input, Jump 및 StopJump 같은 베이스 무브먼트 함수 호출에 사용할 수 있습니다.
이제 테스트해 볼 시간입니다! 게임 모드 BP에서 새 플레이어 컨트롤러 클래스를 설정한 다음 게임에서 컨트롤러를 사용할 수 있는지 확인합니다.
레벨 에디터의 이 드롭다운에서 올바른 게임 모드가 선택되어 있는지 확인합니다.
마지막으로, 맵에 PlayerStart 액터와 볼 수 있는 카메라가 있는지 확인합니다. 플레이 버튼을 누르면 캐릭터가 움직이며 애니메이팅되는 것을 볼 수 있습니다.
패럿 캐릭터 무브먼트 컴포넌트
베이스 ACharacter 클래스에는 유용한 함수 기능이 많이 내장되어 있습니다. 그 예로 UCharacterMovement 액터 컴포넌트를 들 수 있습니다. 캐릭터 무브먼트 컴포넌트는 캐릭터가 월드에서 이동하는 방식에 대한 모든 로직을 처리합니다. 걷기, 달리기, 낙하, 수영, 비행 및 커스텀 무브먼트 모드를 지원합니다. 기본적인 캐릭터의 무브먼트가 어떻게 작동하는지, 그리고 액터 컴포넌트가 얼마나 강력한지 이해하기 위해 직접 살펴볼 필요가 있습니다.
패럿의 경우, 베이스 캐릭터 무브먼트 컴포넌트는 개발자가 원하는 게임 느낌을 바로 제공하지 못했습니다. 하지만, 탄탄한 기반을 제공한 것은 사실입니다. UParrotCharacterMovementComponent C++ 클래스는 UCharacterMovementComponent에서 파생됩니다. 그런 다음, BP_ParrotPlayerCharacter의 CDO 무브먼트 컴포넌트에 다음과 같이 무브먼트 컴포넌트를 설정할 수 있습니다.
그렇다면 패럿 이동 컴포넌트는 실제로 어떤 역할을 할까요? 본질적으로 이 컴포넌트를 사용하면 점프와 관련된 베이스 무브먼트 컴포넌트의 함수를 오버라이드하여 '플랫포머 스타일' 점프를 정의할 수 있습니다. 디자인을 미세조정할 수 있는 몇 가지 값을 사용하면, 점프가 발생할 때 플레이어 캐릭터의 중력 스케일과 점프 속도를 수정하여 정해진 시간 내에 정점 높이에 도달하게 할 수 있습니다. 정점에 도달하면 낙하 중력 스케일을 적용하기 시작할 수 있습니다. 플레이어가 점프 입력을 일찍 해제하면 배수를 적용하여 중력 스케일을 증가시킬 수 있습니다.
그 결과, 단순히 Z축에 충격량을 적용하는 것이 아니라 플랫포머에서 기대할 수 있는 것과 같은 '느낌이 있는' 점프가 구현됩니다.
어려운 부분은 베이스 캐릭터 무브먼트 컴포넌트에 맡겨두고, 패럿 무브먼트 컴포넌트 로직에서는 점프에만 집중하면 됩니다. 무브먼트 컴포넌트의 모든 요소가 함께 작동하는 방식을 전체적으로 파악하려면 무브먼트 컴포넌트의 BP_ParrotPlayerCharacter에 대한 인스펙터의 값도 살펴볼 필요가 있습니다. 무브먼트 로직은 다양한 사용 사례에 맞춰 확장할 수 있습니다.