캐릭터 무브먼트 컴포넌트 는 걷기, 낙하, 수영, 비행 등 인간형 캐릭터 의 공통적인 무브먼트 모드를 갖춘 캡슐화된 무브먼트 시스템을 제공하는 액터 컴포넌트 입니다. 캐릭터 무브먼트 컴포넌트는 강력한 네트워크 게임플레이 통합 기능도 제공합니다. 디폴트 무브먼트 모드는 기본적으로 리플리케이트할 수 있도록 빌드되며, 개발자가 네트워킹된 커스텀 무브먼트를 생성할 수 있게 지원하는 프레임워크를 제공합니다.
캐릭터 무브먼트 기초
UCharacterMovementComponent는 ACharacter 액터 클래스 및 여기에서 파생되는 블루프린트 에 미리 어태치되어 있습니다.
UCharacterMovementComponent 는 TickComponent 함수 도중 PerformMovement 를 호출하여 현재 사용 중인 무브먼트 모드 및 보통 APlayerController의 입력 컨트롤 변수로 표현되는 플레이어의 입력 변수를 기반으로 월드 내에서 원하는 가속을 계산합니다. 무브먼트 계산이 완료되면 UCharacterMovementComponent 에서 최종 무브먼트를 소유 캐릭터에 적용합니다.
ACharacter 가 APawn에서 파생되지만 캐릭터는 단순히 캐릭터 무브먼트 컴포넌트가 추가된 폰이 아닙니다. UCharacterMovementComponent 와 ACharacter 는 함께 사용하도록 디자인되었습니다. ACharacter 는 리플리케이트된 여러 변수 및 함수를 오버라이드하여 UCharacterMovementComponent 의 리플리케이션을 용이하도록 해줍니다.
PerfromMovement와 무브먼트 피직스
PerformMovement 함수는 게임 월드 내 캐릭터의 물리적인 움직임을 담당합니다. 네트워킹되지 않은 게임에서 UCharacterMovementComponent 는 틱마다 PerformMovement 를 직접 호출합니다. 네트워크 게임에서 PerformMovement 는 서버 및 클라이언트를 위한 특수 함수에 의해 호출되어 플레이어 로컬 머신의 초기 무브먼트를 수행하거나 원격 머신에서 해당 무브먼트를 재생성합니다.
PerformMovement 는 다음과 같은 사항을 처리합니다.
- 임펄스, 포스, 중력 등 외부 피직스를 적용합니다.
- 애니메이션 루트 모션 및 루트 모션 소스 로부터 무브먼트를 계산합니다.
- 캐릭터가 사용 중인 무브먼트 모드를 기반으로
Phys*함수를 선택하는StartNewPhysics를 호출합니다.
각 무브먼트 모드에는 속도 및 가속 계산을 담당하는 자체 Phys* 함수가 있습니다. 예를 들어 PhysWalking 은 지면 위를 움직일 때 캐릭터의 무브먼트 피직스를 결정하며, PhysFalling 은 공중에서의 행동 방식을 결정합니다. 이러한 행동의 구체적인 사항을 디버깅하려면 함수 내부를 들여다봐야 합니다.
캐릭터가 낙하를 시작하거나 오브젝트와 충돌할 때 무브먼트 모드가 한 틱 도중 변경되면 Phys* 함수가 StartNewPhysics 를 다시 호출하여 새 무브먼트 모드에서 캐릭터의 모션을 계속합니다. StartNewPhysics 와 Phys* 함수는 각각 발생한 StartNewPhysics 의 반복작업 수만큼 통과합니다. MaxSimulationIterations 파라미터는 이 재귀가 허용되는 최대 횟수입니다.
무브먼트 리플리케이션 개요
UCharacterMovementComponent 는 오너의 네트워크 역할 을 사용하여 무브먼트 리플리케이트 방식을 결정합니다. 세 가지 네트워크 역할은 다음과 같습니다.
| 네트워크 역할 | 설명 |
|---|---|
| 자율 프록시(Autonomous Proxy) | 캐릭터가 소유 클라이언트 의 머신에서 플레이어에 의해 로컬로 제어됩니다. |
| 권한(Authority) | 캐릭터가 게임을 호스팅하는 서버에 존재합니다. |
| 시뮬레이션된 프록시(Simulated Proxy) | 캐릭터가 원격으로 제어되는 캐릭터를 볼 수 있는 다른 클라이언트에 존재합니다. AI에 의해 서버에서 제어되거나 다른 클라이언트에서 자율 프록시에 의해 제어될 수 있습니다. |
리플리케이션 프로세스가 틱마다 스스로를 반복하는 TickComponent 함수 내의 주기를 따릅니다. 캐릭터가 무브먼트를 수행할 때 해당 사본이 네트워크 게임의 모든 다른 머신에서 원격 프로시저 호출(Remote Procedure Call, RPC) 을 서로에 대해 생성하여 무브먼트 정보를 동기화합니다. 다른 네트워크 역할의 경우 다른 실행 경로를 적절하게 사용합니다.
아래 표에는 UCharacterMovementComponent 가 이 프로세스 도중 각 머신에서 수행하는 작업의 단계별 개요가 정리되어 있습니다.
| 단계 | 설명 |
|---|---|
| 자율 프록시(소유 플레이어의 클라이언트) | |
| 1 | 소유 클라이언트가 자율 프록시를 로컬로 제어합니다. PerformMovement 는 무브먼트 컴포넌트의 물리적 무브먼트 로직을 실행합니다. |
| 2 | 프록시는 방금 어떻게 움직였는지에 대한 데이터를 포함하는 FSavedMove_Character 를 빌드한 후 이를 SavedMoves 큐에 등록합니다. |
| 3 | 유사한 FSavedMove 항목은 서로 결합됩니다. 자율 프록시는 데이터의 압축된 버전을 ServerMove RPC를 통해 서버로 전송합니다. |
| 권한 있는 액터(서버) | |
| 4 | 서버에서 ServerMove를 수신하고 PerformMovement 를 사용하여 클라이언트의 무브먼트를 재생성합니다. |
| 5 | 서버는 ServerMove 이후의 위치가 클라이언트의 보고된 최종 위치와 일치하는지 확인합니다. |
| 6 | 서버와 클라이언트의 최종 위치가 일치하면 움직임이 유효하다는 신호를 클라이언트로 전송합니다. 그렇지 않으면 ClientAdjustPosition RPC를 통해 보정을 전송합니다. |
| 7 | 서버는 ReplicatedMovement 구조체를 리플리케이션하여 위치, 회전 및 현재 상태를 다른 연결된 클라이언트의 시뮬레이션된 프록시로 전송합니다. |
| 자율 프록시(소유 플레이어의 클라이언트) | |
| 8 | 클라이언트에서 ClientAdjustPosition을 수신하면 서버의 무브먼트를 재생성하며 SavedMoves 큐를 사용하여 최종 위치까지 도달한 단계를 다시 트레이스합니다. 움직임이 성공적으로 리졸브되면 큐에서 저장된 움직임을 제거합니다 |
| 시뮬레이션된 프록시(기타 모든 클라이언트) | |
| 9 | 시뮬레이션된 프록시는 리플리케이트된 무브먼트 정보를 직접 적용합니다. 네트워크 스무딩 은 최종 모션을 위한 시각적 수정 작업을 제공합니다. |
이 프로세스는 네트워크 게임 내에서 세 가지 머신 타입 간의 무브먼트를 동기화합니다. 지정된 캐릭터를 제어하는 사용자는 서버로부터 최소한의 개입만 경험해야 하며 캐릭터를 로컬로 제어한다는 느낌을 가질 수 있어야 합니다. 또한 다른 사용자의 캐릭터가 다른 사용자의 소유 머신에서 수행하는 무브먼트의 근사치를 수행하는 모습을 볼 수 있어야 합니다.
이 프로세스의 복잡성 대부분은 플레이어가 자신의 캐릭터를 제어하면서 최대한 매끄러운 경험을 하도록 자율 프록시와 서버 상대측 간에 예측과 보정을 중개하는 데 집중되어 있습니다. 이와 달리 시뮬레이션 프록시는 서버가 지시한 대로만 최신 상태를 유지하면 됩니다.
리플리케이트된 캐릭터 무브먼트 심층 정보
다음 섹션에서는 위에서 간단하게 살펴본 프로세스를 단계별로 상세하게 알아봅니다. 대다수의 프로젝트에서는 UCharacterMovementComponent 의 행동을 오버라이드하지 않을 것이기 때문에 비슷한 함수 기능을 개발하거나 어디를 수정해야 할지 찾아야 할 때 레퍼런스로 활용할 수 있습니다.
이 섹션은 캐릭터의 일반 무브먼트 모드를 리플리케이트하는 데 초점을 맞춥니다. 하지만 루트 모션을 위한 다른 실행 경로가 있으며 다른 액터를 기반으로 움직이지만 이 섹션에 나열된 것과 비슷한 단계를 따릅니다.
소유 클라이언트에서의 로컬 무브먼트
자율 프록시는 TickComponent 에서 무브먼트를 로컬로 처리하고, 기록하고, 서버로 전송하여 자율적으로 재생성 및 적용되게 합니다. 이 섹션에서는 자율 프록시가 각 틱에서 수행하는 프로세스를 설명합니다.
클라이언트 예측 데이터 빌드하기
자율 프록시는 움직임을 기록하고 서버에서 들어오는 보정을 처리하기 위해 ClientPredictionData 라는 FNetworkPredictionData_Client_Character 오브젝트를 프로세스의 일환으로 빌드합니다. 해당 파라미터에는 다음이 포함됩니다.
- 클라이언트가 서버와 통신할 때의 타임스탬프
- 저장되거나 보류 중인 움직임의 목록
- 서버 보정에서 저장된 정보
- 보정 적용 방식을 나타내는 플래그
- 스무딩 행동을 결정하는 파라미터
ClientPredictionData 는 이러한 파라미터와 인터랙션하는 유틸리티 함수도 포함합니다. 이 오브젝트 정보 및 함수의 전체 목록은 FNetworkPredictionData_Client_Character API 레퍼런스에서 확인할 수 있습니다. 해당 파라미터는 클라이언트가 로컬 무브먼트를 수행하고, 움직임을 서버로 보낼 준비를 하고, 보정을 처리할 때 빈번하게 레퍼런스 및 변경됩니다.
서버 보정 재생성
월드 내 플레이어의 입력 또는 포스가 처리되기 전에 자율 프록시가 ClientUpdatePositionAfterServerUpdate 를 호출합니다. 이는 서버가 소유 플레이어에게 보정을 전송했는지 여부를 확인합니다. 보정을 전송한 경우, ClientPredictionData 내부의 변수 bUpdatePosition 이 true가 되며 캐릭터가 클라이언트 보정 프로세스를 통해 서버에서 전송된 움직임을 재생성합니다. 서버 보정에 대한 자세한 내용은 아래의 클라이언트 오류 및 보정 처리하기 섹션을 참고하세요.
무브먼트 수행 및 기록하기
자율 프록시 캐릭터는 TickComponent 도중 PerformMovement 를 직접 호출하는 대신 ReplicateMoveToServer 를 호출합니다. 이 함수는 필요한 로직으로 PerformMovement 를 감싸서 캐릭터가 수행하는 움직임을 기록하고 해당 움직임을 서버에 제출합니다. FSavedMove_Character 구조체는 자율 프록시가 각 틱 도중 움직임을 시작하고 종료한 방식을 기록하며, 그 후 데이터의 최소 서브셋이 ServerMove RPC를 통해 서버로 전송됩니다. 해당 파라미터에는 다음이 포함됩니다.
- 캐릭터의 최종 위치 및 회전에 대한 정보
- 캡처된 무브먼트 입력
- 캐릭터의 속도 및 가속도
- AnimMontages 에서 캡처한 루트 모션 정보
이 구조체 파라미터의 전체 목록은 FSavedMove_Character API 레퍼런스에서 확인할 수 있습니다. 이 정보는 서버가 플레이어의 무브먼트를 재생성하고 클라이언트의 최종 위치를 확인할 수 있도록 지원합니다.
PerformMovement 를 처리한 뒤 ReplicateMoveToServer 함수는 클라이언트 예측 데이터에 캐릭터 무브먼트의 결과를 NewMove 라는 FSavedMove_Character 구조체와 함께 기록합니다. 그런 다음 이를 SavedMoves 라는 버퍼에 추가합니다. 이 버퍼는 가장 오래 전에 저장된 움직임에서 가장 최근에 저장된 움직임 순서로 정렬되며, 저장된 움직임이 서버에 제출될 수 있을 때까지 큐 역할을 합니다. 버퍼에 있는 유사한 움직임은 제출되기 전에 대역폭의 부하를 줄이기 위해 단일 FSavedMove_Character 로 결합됩니다. PendingMove 파라미터는 다음 무브먼트와 결합되기를 대기하는 움직임의 스토리지 역할을 합니다.
이는 승인됨, 즉 ACKed 상태가 되면 버퍼에서 제거됩니다. 서버는 클라이언트의 위치가 유효하다는 것을 승인함으로써 즉시 움직임을 확인할 수 있습니다. 또는 클라이언트가 서버에서 들어오는 보정을 처리할 때 움직임을 승인할 수 있습니다. 마지막으로 승인되는 움직임은 향후 보정을 처리하는 데 사용되도록 LastAckedMove 에 저장됩니다.
서버에 움직임 제출하기
ReplicateMoveToServer 는 CallServerMove 함수를 실행하여 완료됩니다. 이 함수는 큐로부터 서버에서 아직 승인되지 않은 최신 움직임과 가장 오래된 움직임을 취합니다. 이 함수는 서버에 움직임을 제출하기 위한 최종 준비를 실행하며, 가능한 경우 오래된 움직임부터 먼저 제출을 시도한 다음 적절한 ServerMove 함수를 호출하여 새 움직임을 위해 최종화된 무브먼트를 제출합니다. 최종 ServerMove는 신뢰할 수 없는 서버 RPC로 UCharacterMovementComponent 의 소유 캐릭터에게 직접 제출됩니다.
ServerMove 함수가 신뢰할 수 없는 이유는 두 가지입니다.
- 일반적인 게임플레이 도중 ServerMove 함수가 신뢰할 수 있다고 지정될 정도로 자주 호출되면 신뢰할 수 있는 함수에 대해 버퍼 오버플로를 유발하여 소유 플레이어의 연결 해제를 강제할 수 있습니다.
- 저장된 움직임을 버퍼링하는 시스템은 전송 중 손실된 무브먼트 정보를 다시 제출 및 평가하도록 이미 보장합니다. 이는 신뢰할 수 있는 함수와 유사한 안전망을 제공하며, 신뢰할 수 있는 RPC 버퍼의 오버플로 위험도 없습니다. 또한 너무 오래된 무브먼트 데이터를 버려지게 하는 프로비전을 추가합니다.
서버에서 움직임 평가하기
서버는 게임의 틱 주기와 동기화되도록 움직임을 규칙적으로 틱하지 않습니다. 그 대신 자율 프록시에서 ServerMove 호출을 수신하기를 기다리며, ServerMove_Implementation 이 서버 측 무브먼트를 처리하고 클라이언트 무브먼트를 재구성한 후 불일치를 확인합니다. 이 섹션에서는 ServerMove가 수행하는 프로세스를 상세하게 살펴봅니다.
이 문서는 ServerMove 및 ServerMove_Implementation 를 광범위하게 언급하지만 ServerMove 호출에는 큐에 등록된 정보의 종류에 따라 다양한 타입이 있습니다.
서버 예측 데이터 빌드하기
캐릭터 무브먼트 컴포넌트의 권한 버전은 ServerPredictionData 라는 FNetworkPredictionData_Server_Character 오브젝트를 생성하며, 이는 캐릭터의 수명 동안 존재합니다. ServerMove_Implementation 동안 이 오브젝트는 향후 소유 클라이언트의 무브먼트를 재생성하는 데 사용할 정보를 저장합니다. 이 오브젝트는 서버가 데이터를 수신할 때 백그라운드에서 지속적으로 수정되며, 해당 파라미터는 다음과 같습니다.
- 서버의 델타 시간을 계산하는 데 사용되는 타임스탬프
- 보류 중인 클라이언트 조정
- 시간 불일치 해결과 관련된 플래그
- 서버가 움직임을 승인 또는 보정하는지 나타내는 플래그
파라미터 및 함수의 전체 목록은 FNetworkPredictionData_Server_Character API 레퍼런스 가이드에서 확인할 수 있습니다.
클라이언트 타임스탬프 검증하기
ServerMove RPC를 통해 전송된 정보는 움직임이 발생한 시점의 타임스탬프를 포함합니다. 서버 타임스탬프 및 클라이언트 타임스탬프에 불일치가 너무 많으면 클라이언트 타임스탬프가 만료된 것으로 고려되며 해당 움직임이 버려집니다. 그렇지 않으면 불일치가 해결될 것으로 플래그가 지정되며 UCharacterMovementComponent 는 ProcessClientTimeStampForTimeDiscrepancy 를 사용하여 다음 단계에서 델타 시간에 대한 오버라이드를 생성합니다.
델타 시간 계산하기
델타 시간은 보통 현재 틱과 지난 틱 간의 지나간 시간을 계속 트래킹하여 얻어지지만, 서버에서 캐릭터는 무브먼트 계산에 TickComponent 를 사용하지 않습니다. 그 대신 ServerMove가 수신될 때 ServerMove_Implementation 에서 GetServerMoveDeltaTime 을 호출하고 무브먼트를 계산합니다. 타임스탬프 불일치를 해결하려고 시도할 때 서버 예측 데이터가 플래그 지정되면 TimeDiscrepancyResolutionMoveDeltaOverride 를 사용합니다. 시간 불일치가 없는 경우에는 서버 예측 데이터를 사용하여 현재 ServerMove RPC의 타임 스탬프와 마지막 ServerMove RPC의 타임스탬프 사이의 차이를 사용하여 델타 시간을 생성합니다. 추가 보안 레이어를 제공하기 위해 이러한 계산 대다수는 클라이언트가 아닌 서버의 타임스탬프로 수행되어 클라이언트가 로컬 게임 클럭을 빠르게 조정하여 속도를 해킹하지 않도록 방지합니다.
움직임 평가하기
다음으로, 서버는 ServerMove RPC의 데이터를 사용하여 소유 플레이어 컨트롤러의 컨트롤 회전을 재구성한 후 MoveAutonomous 함수를 호출하여 캐릭터의 가속, 회전 및 점프 입력을 처리합니다.
MoveAutonomous 는 PerformMovement 함수를 사용하여 이 재구성된 데이터 및 이전 단계에서 제공된 델타 시간을 사용하여 캐릭터 무브먼트 피직스를 시뮬레이션합니다. 클라이언트가 시작한 위치에서 무브먼트를 시뮬레이션하는 대신 서버는 ServerMove 호출을 받았을 때 캐릭터의 자체 사본이 있었던 위치에서 시뮬레이션합니다.
캐릭터가 애니메이션에서 루트 모션을 수행하는 경우 MoveAutonomous 또한 제공된 델타 시간을 사용하여 캐릭터의 애니메이션 포즈를 틱합니다. 모든 애니메이션 이벤트가 적절하게 트리거되거나 애니메이션이 정상적으로 틱됩니다.
클라이언트 오류 및 보정 처리하기
서버 무브먼트는 서버 및 소유 클라이언트가 같은 위치에서 시작한다는 추정을 바탕으로 작동하며, 클라이언트가 보고하는 움직임을 서버가 동일하게 수행하는 경우 두 움직임이 끝나는 위치 또한 같을 것이라고 추정합니다. 클라이언트의 움직임이 연결 문제로 인해 드롭되거나 클라이언트가 불량한 데이터를 제출할 경우 양측의 최종 위치가 달라져서 보정이 필요하게 됩니다. ServerMoveHandleClientError 함수가 이러한 작업을 담당합니다.
조정 필요 여부 판정하기
보정이 자주 발생하면 대역폭에 부담이 가해져 클라이언트가 다수의 저장된 움직임을 지나치게 자주 다시 시뮬레이션하게 됩니다. 따라서 먼저 WithinUpdateDelayBounds 에서 반환된 값을 검토하여 움직임 간에 최소한의 시간이 흘렀는지 확인합니다. 이때 false 가 반환되면 보정이 발생하지 않습니다. 그렇지 않고 true가 반환되면 프로세스의 나머지 부분의 실행이 허용됩니다.
다음으로, ServerCheckClientError 를 사용하여 서버와 클라이언트 사이의 오차가 보정이 필요할 정도로 큰지 확인합니다. true를 반환하거나 뭔가가 bForceClientUpdate 를 true로 설정하여 보정을 강제하는 경우 ServerMoveHandleClientError 가 나머지 프로세스를 진행합니다.
이러한 두 작업을 조정하기 위한 파라미터는 BaseGame.ini 에서 찾을 수 있으며, 프로젝트의 DefaultGame.ini 에서 프로젝트별 오버라이드를 제공할 수 있습니다. ClientErrorUpdateRateLimit 값은 서버가 클라이언트로 오차 보정을 전송하기 위한 최소 딜레이 시간(초)을 결정합니다. MAXPOSITIONERRORSQUARED 값은 네트워크가 보정 없이 플레이하도록 허용되는 최대 위치 오차의 제곱입니다. 이 두 값은 환경설정 파일의 [/Script/Engine.GameNetworkManager] 섹션에 있습니다.
조정이 필요한 경우, 서버 예측 데이터가 PendingAdjustment 라는 FClientAdjustment 구조체를 서버의 캐릭터 사본에서 샘플링된 무브먼트 변수로 채웁니다. 여기에는 위치, 회전, 속도 및 기타 캐릭터 무브먼트의 베이스가 되는 오브젝트가 포함됩니다. 그렇지 않으면 PendingAdjustment 의 bAckGoodMove 값을 true 로 설정하여 클라이언트의 무브먼트를 유효한 것으로 플래그 지정합니다.
클라이언트 조정 전송 또는 움직임 승인하기
클라이언트에 대한 무브먼트 승인 최종 호출은 SendClientAdjustment 로 이뤄집니다. 이 함수는 ServerMove_Implementation 의 일부로 수행되지 않습니다. 그보다는 서버의 틱 끝에 호출되는 UNetDriver::ServerReplicateActors 의 일부이며, 마찬가지로 다른 클라이언트 조정 RPC를 호출하는 작업을 담당합니다. SendClientAdjustment 가 호출될 때 이전 단계에서 빌드한 예측 데이터가 플래그 지정된 방식에 따라 다르게 작동합니다.
서버 예측 데이터의 PendingAdjustment 가 bAckGoodMove 를 true 로 플래그 지정하면 ClientAckGoodMove RPC를 호출하여 움직임을 승인하며, 소유 클라이언트 머신의 자율 프록시에 움직임이 유효하다고 알립니다. 그러면 소유 클라이언트 측의 SavedMoves 버퍼에서 원본 움직임이 제거되고 미래 예측 데이터를 빌드하는 데 사용할 LastAckedMove 로 기록됩니다.
PendingAdjustment 에 false로 플래그 지정된 bAckGoodMove 가 있는 경우, 클라이언트 조정 함수를 호출하여 최종 보정을 클라이언트로 전송합니다.
자율 프록시에서 클라이언트 조정 수신하기
클라이언트 조정 RPC는 ClientAdjustPosition, ClientAdjustRotation, 속도가 0일 때 발생하는 이러한 함수의 단축 버전 및 루트 모션 기반 무브먼트에 사용되는 버전을 포함합니다. 서버가 보정의 성질과 심각도에 따라 SendClientAdjustment 의 일부로 이 중에서 하나 이상을 호출할 수 있습니다. 각각은 ClientPredictionData 에 필요한 보정이 적용된 뒤 움직임을 승인하도록 지시할 수 있으며, 그 각각은 bUpdatePosition 을 true로 플래그 지정합니다.
그런 다음 최종 보정이 클라이언트의 다음 TickComponent 시작 시 ClientUpdatePosition 을 사용하여 적용됩니다.
시뮬레이션된 프록시로 무브먼트 리플리케이트하기
오너가 아닌 클라이언트 머신의 캐릭터는 자율 프록시가 아니라 시뮬레이션된 프록시입니다. 서버에서 시뮬레이션된 프록시로 무브먼트를 리플리케이트하는 프로세스는 매우 단순화됩니다. 시뮬레이션된 프록시의 유일한 작업은 서버에 응답하는 것이기 때문입니다. 무브먼트 피직스를 시뮬레이션하는 대신 서버에서 무브먼트 업데이트를 수신했을 때 위치, 회전 및 속도를 서버에서 전달받은 대로 설정하고 몇 가지 추가 프로세스를 통해 무브먼트를 보다 매끄럽고 신뢰할 수 있게 만듭니다.
리플리케이트된 무브먼트 정보 저장하기
액터가 무브먼트를 리플리케이트할 때 트랜스폼을 직접 리플리케이트하지는 않습니다. 그 대신 모든 액터가 ReplicatedMovement 라는 리플리케이트된 변수를 유지하며, 이는 구조체 FRepMovement를 사용합니다.
블루프린트의 무브먼트 리플리케이트(Replicate Movement) 변수로 표현되는 bReplicateMovement 부울은 액터가 이 구조체에 무브먼트 정보를 저장하고 이를 클라이언트로 리플리케이트하도록 플래그 지정합니다. 클라이언트가 ReplicatedMovement 에 대한 업데이트를 수신할 때 RepNotify 함수 OnRep_ReplicatedMovement 가 저장된 무브먼트 데이터를 언패킹하고 그에 따라 액터의 위치 및 속도에 대해 업데이트를 수행합니다.
ReplicatedMovement 또는 해당 OnRep 모두 블루프린트 내에서 액세스 가능하지 않지만 OnRep_ReplicatedMovement 는 C++에서 오버라이드될 수 있으며 ReplicatedMovement 의 리플리케이션 조건 또한 GetLifetimeReplicatedProps 에서 오버라이드할 수 있습니다. 이를 통해 C++ 기반 액터 클래스에서 무브먼트 리플리케이션 방식을 커스터마이징할 수 있습니다.
ACharacter 에서 ReplicatedMovement 구조체는 시뮬레이션된 프록시에 대해서만 리플리케이트됩니다. 자율 프록시는 이를 무시하며, 무브먼트 프로세스를 위해 서버 움직임 및 클라이언트 조정 RPC를 사용합니다.
캐릭터가 다른 액터를 베이스로 사용하는 경우 ReplicatedBasedMovement 를 대신 사용하며, 이는 추가 로직을 적용하여 클라이언트가 서버에 따라 정확하게 기반하도록 합니다. 캐릭터가 루트 모션 시스템을 사용하는 경우에는 이 모든 프로세스는 무시되고 RepRootMotion 을 사용합니다.
시뮬레이션된 프록시에서 무브먼트 틱하기
UCharacterMovementComponent 는 시뮬레이션된 프록시에서 TickComponent 를 실행할 때 SimulatedTick 을 호출하여 시뮬레이션하는 무브먼트를 위한 로직을 처리합니다. 이는 위에서 설명한 리플리케이트된 무브먼트를 수행하지 않습니다. 그 대신 SimulatedTick 은 계속해서 가장 최근에 제공된 리플리케이트된 무브먼트 데이터에 따라 움직입니다. 표준 무브먼트 피직스를 수행하는 경우에는 SimulateMovement 함수를 호출한 후 최종 유효성 검사 및 SmoothClientPosition 을 통한 네트워크 스무딩을 수행합니다.
시뮬레이션된 무브먼트 수행하기
SimulateMovement 함수는 시뮬레이션된 프록시 캐릭터를 움직이는 역할을 합니다. 이 함수는 SimulatedTick 또는 OnRep_ReplicateMovement 에 의해 호출됩니다. 이 함수는 다음 프로세스를 수행합니다.
- 소유 캐릭터의
GetReplicatedMovement함수를 호출하여ReplicatedMovement에 대한 레퍼런스를 얻습니다. - 안전 검사를 수행하여 리플리케이트된 무브먼트 데이터가 유효한지, 클라이언트의 베이스가 리졸브되었는지 확인합니다.
- 네트워크 업데이트가 수신되었는지 확인합니다.
- 서버에서
GetReplicatedMovementMode로 얻은 캐릭터 무브먼트 모드를 적용합니다. - 네트워크 업데이트와 관련된 모든 플래그를 리셋합니다.
- 현재
MovementMode및 캐릭터의 현재 상태에 대한 정보를 기반으로 시뮬레이션된 움직임에 대한 로직을 수행합니다.
시뮬레이션된 움직임에 대한 로직은 표준 무브먼트 피직스에 비해 매우 단순화됩니다. 작은 함수로 나뉘지 않고 대부분 SimulateMovement 함수 자체에 들어 있습니다. 하지만 이 함수는 여전히 캐릭터의 로컬 무브먼트 상태 업데이트를 담당합니다. 여기에는 트랜지션할 무브먼트 모드, 캐릭터의 지면 착지 여부, 캐릭터가 가져야 할 속도 등이 포함됩니다. 이 정보는 캐릭터가 애니메이션을 올바르게 업데이트하고 무브먼트가 정확하게 표현되게 합니다.
네트워크 스무딩
단순히 캐릭터의 위치 및 회전을 리플리케이트하여 무브먼트를 리플리케이트했다면 캐릭터가 몇 초마다 텔레포트하는 것처럼 보일 것입니다. 이는 로컬 머신의 렌더링 속도가 네트워크를 통해 데이터를 전송하려는 속도보다 빠르기 때문입니다. 예를 들어 클라이언트는 240Hz 주사율로 모니터에 렌더링하지만 리플리케이트된 무브먼트는 30Hz로 전송될 수 있습니다.
네트워크 스무딩은 캐릭터를 소스 위치에서 타깃 위치로 즉시 스냅하지 않고 서서히 보간함으로써 이 모션을 스무드 아웃하는 프로세스입니다. 소스 위치는 캐릭터의 현재 위치에 의해 지정되며, 타깃은 클라이언트 예측 데이터에 의해 지정됩니다. 보간 자체는 SmoothClientPosition 에서 처리되며, 이는 NetworkSmoothingMode를 사용하여 어떤 보간을 사용할지 결정합니다.
특수 무브먼트 사례
다음 섹션에서는 특수 어빌리티에서 볼 수 있는 순간 이동, 커스텀 무브먼트, 코드 기반 무브먼트 등 일반적인 특수 무브먼트 사례에 대한 정보를 살펴봅니다.
멀티플레이어에서 캐릭터 텔레포트하기
네트워크 게임에서 SetLocation 함수 또는 Teleport 블루프린트 노드를 호출하여 캐릭터를 텔레포트할 수 있습니다. 이를 위해서는 다음 조건을 충족해야 합니다.
- 서버에서 호출해야 합니다.
SetLocation함수를 사용하는 경우,bTeleport변수를 true로 설정해야 움직임을 텔레포트로 인식합니다.
이 조건이 충족되면 무브먼트가 서버의 예측 데이터와 리플리케이트된 무브먼트에 텔레포트로 기록되며, 이에 따라 모든 클라이언트가 캐릭터를 원하는 위치로 스무딩하는 대신 스내핑합니다.
커스텀 무브먼트 모드 사용하기
MOVE_Custom 무브먼트 모드는 다른 모든 무브먼트 피직스를 정지시켜 UCharacterMovementComponent 의 일반 프로세스로부터 방해받지 않고 커스텀 무브먼트 로직을 구현할 수 있도록 지원합니다.
UCharacterMovementComponent 는 일반적으로 블루프린트로 만들 수 없으므로 블루프린트에서 커스텀 무브먼트는 보통 캐릭터 내에서 UpdateCustomMovement 이벤트를 사용하여 직접 구현됩니다. 커스텀 무브먼트 모드(Custom Movement Mode) 바이트 변수를 사용하여 인티저에 따른 스위치나 커스텀 열거형으로의 변환을 포함하는 서브 모드를 제공할 수 있습니다.
UpdateCustomMovement 는 UCharacterMovementComponent 에서 PhysCustom 함수에 의해 호출됩니다. StartNewPhysics, PhysCustom 함수와 다른 모든 무브먼트 피직스 함수는 가상 함수이므로 C++에서 커스텀 UCharacterMovementComponent 를 생성하면 이러한 함수를 직접 오버라이드할 수 있습니다.
루트 모션으로 특수 사례 무브먼트 리플리케이트하기
게임플레이 어빌리티 시스템(Gameplay Ability System) 으로 생성된 어빌리티나 애니메이션 기반 액션 도중과 같이 짧은 기간 동안 캐릭터의 무브먼트를 직접 제어해야 할 수도 있습니다. 로컬 전용 게임에서는 이 작업이 간단하지만 리플리케이트된 특수 사례 무브먼트에는 루트 모션 활용이 필요하며, 이는 일반적으로 애니메이션에서 무브먼트의 애플리케이션을 레퍼런스합니다. 루트 모션 시스템은 또한 코드 기반 특수 사례 무브먼트를 허용하도록 변형되었습니다.
루트 모션은 UCharacterMovementComponent 가 어떤 무브먼트 모드를 사용하든 항상 표준 무브먼트 피직스보다 먼저 실행됩니다. 즉, 루트 모션이 완료된 뒤에 일반 무브먼트가 재개됩니다.
AnimMontages 활용
대부분의 루트 모션 애플리케이션은 AnimMontages에서 올 것으로 예상되며, 이는 코드로 트리거되는 원샷 애니메이션에 사용됩니다. 이렇게 루트 모션을 활용하면 캐릭터가 수행 중인 다른 무브먼트가 애니메이션이 끝날 때까지 정지됩니다. 그 대신 캐릭터는 스켈레톤의 루트 본에서 무브먼트를 소비하여 월드 스페이스 무브먼트로 변환함으로써 캐릭터의 움직임을 애니메이션이 제어할 수 있게 합니다. 이 작업이 완료되면 캐릭터의 일반 피직스 사용이 복원됩니다.
캐릭터가 낙하 무브먼트 모드에 있는 경우, 캐릭터가 루트 모션을 수행하고 있더라도 여전히 중력이 캐릭터의 Z 무브먼트에 적용됩니다.
위에서 설명한 리플리케이션 프로세스 내에서 루트 모션 정보는 FSavedMove_Character 구조체에 의해 캡처됩니다. 여기에는 소스인 AnimMontage, 몽타주 내 캐릭터의 트랙 위치 및 캐릭터 무브먼트 자체의 파라미터가 포함됩니다.
서버와 소유 클라이언트의 자율 프록시는 자신들이 같은 애니메이션을 재생 중인지 확인하지 않습니다. 이는 보통 장식용 기능으로 여겨지기 때문입니다. 따라서 AnimMontage가 게임에 연결된 모든 머신에서 올바르게 트리거되도록 게임플레이 로직을 프로그래밍해야 합니다. 하지만 시뮬레이션된 프록시는 위에서 설명한 것과 같이 루트 모션 기반 무브먼트 동기화를 위한 병렬 프로세스를 갖추고 있습니다.
게임플레이 어빌리티 시스템 플러그인은 트리거하는 어빌리티를 리플리케이트함으로써 AnimMontage를 루트 모션과 동기화합니다.
루트 모션 소스 활용
때로는 특수 사례를 위해 캐릭터의 위치를 수동으로 제어해야 할 수도 있습니다. 예를 들어 캐릭터가 공중의 특정 높이로 뛰어오르거나 움직이는 타깃 위에 착지하는 특수 어빌리티를 만들어야 할 수도 있습니다.
SetLocation 및 SetRotation 으로 캐릭터를 수동으로 제어하는 것이 독립형 게임에서는 가능하지만 네트워크 게임에서는 이 모션이 위의 리플리케이션 프로세스에서 캡처되지 않습니다. 따라서 서버는 클라이언트의 최종 위치를 오류로 보고 보정을 발생시킵니다. 이와 달리 AnimMontage에서의 루트 모션은 애니메이션의 사전 연산된 모션만 따릅니다. 즉, 루트 모션은 일반적으로 게임 월드에서 다른 캐릭터의 위치 등 리얼타임 정보를 취하지 못하며, 게임플레이 변수를 사용하여 손쉽게 미세조정할 수도 없습니다.
루트 모션 소스 는 프로그래머가 캐릭터의 루트 모션을 수동으로 제어할 수 있게 해줍니다. 이를 통해 캐릭터의 무브먼트를 프로그래밍 방식으로 제어하는 동시에 네트워킹 도중 루트 모션을 처리하는 위의 시스템도 활용할 수 있습니다.
루트 모션 소스는 소유 클라이언트에서 자율 프록시에 적용되어야 합니다.
이 시스템을 사용하려면 새 FRootMotionSource 구조체를 생성해야 합니다. 서로 다른 무브먼트 타입마다 다른 FRootMotionSource 베리언트가 있습니다. 예를 들어 FRootMotionSource_MoveToForce는 시작 위치에서 타깃 위치까지의 직선 움직임에 사용되며, FRootMotionSource_JumpForce는 점프 같은 호 모양의 움직임을 따릅니다. 적절한 루트 모션 소스를 생성한 후에는 해당 프로퍼티를 원하는 소스 위치, 타깃 위치 및 무브먼트 방식을 위한 파라미터로 초기화할 수 있습니다.
UCharacterMovementComponent::ApplyRootMotionSource 함수는 루트 모션 소스를 캐릭터에 적용하고 나중에 레퍼런스할 때 사용 가능한 핸들을 반환합니다. 루트 모션 소스 자체는 무브먼트를 처리하지 않습니다. 그 대신에 캐릭터 무브먼트 컴포넌트는 애니메이션 대신 FRootMotionSource 에서 제공된 파라미터와 일치하는 무브먼트를 수행합니다. FRootMotionSource 가 자율 프록시에 적용된 경우, 이는 결과적으로 FSavedMove_Character 구조체 내의 SavedRootMotion 에 추가되어 리플리케이션 주기에서 이를 캡처합니다.
무브먼트가 완료되면 UCharacterMovementComponent::RemoveRootMotionSource 호출을 통해 ApplyRootMotionSource 에서 반환된 핸들을 사용하여 이를 제거해야 합니다.
게임플레이 어빌리티 시스템 플러그인은 루트 모션 소스를 활용하는 다양한 어빌리티 작업을 포함하며, 어빌리티가 복잡한 프로그램 방식의 무브먼트 시퀀스를 수행할 수 있도록 지원합니다. 기본 예시는 AbilityTask_ApplyRootMotionMoveToForce 를 참고하세요.
네트워킹된 캐릭터 무브먼트 커스터마이징하기
언리얼 엔진은 리플리케이트된 캐릭터 무브먼트의 커스텀 함수 파라미터를 지원합니다. 이 기능이 필요하지 않고 레거시 API를 유지하려는 개발자는 프로젝트 빌드 파일에서 SUPPORT_DEPRECATED_CHARACTER_MOVEMENT_RPCS 를 0이 아닌 값으로 정의하고 콘솔 변수 'p.NetUsePackedMovementRPCs'를 0으로 설정합니다.
캐릭터 무브먼트 컴포넌트는 FSavedMove_Character 구조체를 사용하여 데이터를 네트워크에 걸쳐 전송합니다. 이 시스템은 네트워크에 걸쳐 전송하기 위해 움직임 데이터를 하나 이상의 업데이트에서 단일 변수 길이 비트 스트림으로 통합합니다. 이 메서드는 오래된 데이터와 새 데이터를 함께 패키징함으로써 ServerMoveOld RPC가 ServerMove 이후에 호출되어 오래된, 하지만 중요한 데이터가 잘못 무시되는 잠재적인 순서 문제를 방지할 수 있습니다. 내부적으로 캐릭터 무브먼트 컴포넌트는 새 CallServerMovePacked 함수를 사용하여 다수의 FSavedMove_Character 인스턴스를 FCharacterNetworkMoveDataContainer 로 시리얼라이즈하고 이전의 CallServerMove 사용을 대체합니다.
저장된 움직임 데이터 확장하기
새 데이터를 추가하려면 우선 FSavedMove_Character 를 확장하여 캐릭터 무브먼트 컴포넌트가 필요로 하는 정보를 포함하게 합니다. 다음으로, FCharacterNetworkMoveData 를 확장하여 네트워크에 걸쳐 전송할 커스텀 데이터를 추가합니다. 대부분의 경우 이는 FSavedMove_Character 에 추가된 데이터를 미러링합니다. 또한 FCharacterNetworkMoveDataContainer 도 확장하여 네트워크 전송을 위한 FCharacterNetworkMoveData 를 시리얼라이즈하고 수신 시 시리얼라이즈를 해제할 수 있게 합니다. 이 구성이 완료되면 시스템을 다음과 같이 환경설정합니다.
-
SetNetworkMoveDataContainer함수로 생성한FCharacterNetworkMoveDataContainer서브클래스를 사용하도록 캐릭터 무브먼트 컴포넌트를 수정합니다. 가장 간단한 방법은FCharacterNetworkMoveDataContainer인스턴스를 캐릭터 무브먼트 컴포넌트 자손 클래스에 추가하고 생성자에서SetNetworkMoveDataContainer를 호출하는 것입니다. -
FCharacterNetworkMoveDataContainer에는FCharacterNetworkMoveData의 자체 인스턴스가 필요하므로 일반적으로 생성자에서 이를FCharacterNetworkMoveData서브클래스의 인스턴스로 가리킵니다. 자세한 내용과 예시는 베이스 생성자를 참고하세요. -
확장된 버전의
FCharacterNetworkMoveData에서ClientFillNetworkMoveData함수를 오버라이드하여 저장된 움직임에서 데이터를 복사 또는 계산합니다.Serialize함수를 오버라이드하고FArchive를 사용하여 데이터를 읽고 씁니다. 이는 RPC에서 필요로 하는 비트 스트림입니다.
클라이언트에 대한 서버 응답을 확장하여 올바른 움직임을 승인하거나 보정 데이터를 전송하게 하려면 FCharacterMoveResponseData, FCharacterMoveResponseDataContainer 를 확장하고 캐릭터 무브먼트 컴포넌트의 SetMoveResponseDataContainer 버전을 오버라이드합니다.
확장된 움직임 데이터에 액세스하기
하위 호환성을 유지하기 위해 서버의 클라이언트 움직임으로 작업하고 보정을 수신한 뒤 클라이언트에서 이를 다시 플레이하기 위한 함수 스택은 변경하지 않았습니다. 이는 레거시 함수에 대해 안정적인 API를 제공하지만, 이 함수 시그니처가 새 무브먼트 데이터를 받아들이지 못한다는 뜻이기도 합니다. GetCurrentMoveData 를 호출하고 반환된 FCharacterNetworkMoveData 를 서브클래스로 형변환하면 움직임을 처리할 때 서버에서 이 데이터에 액세스하거나 움직임을 다시 플레이할 때 클라이언트에서 액세스할 수 있습니다.