렉돌로 인한 나머지 오류를 해결해보자
지난번에 렉돌을 구현했고 이를 테스트하는 도중 발생한 기절 도중 점프가 되는 현상과 머리와 몸통이 분리되는 현상은 해결할 수 있었다. 오늘은 나머지 기절에서 풀리면 캐릭터가 사라지는 현상과 클라이언트의 화면마다 위치가 다르게 보이는 현상을 해결해보려고 한다. 일단 레그돌이 켜지게 되면 축 늘어지게 되며 별도로 엔진에서 물리를 계산해 굴러다니게 된다. 이때 이 계산은 각 클라이언트에서 하게 된다. 이때 굴러다니는 연산은 각 클라이언트에서 따로진행하게되며 캐릭터의 캡슐은 레그돌이 켜진 그 자리 그대로 있느나 각 클라에서 계산한 물리에 따라 레그돌이 굴러다니게 된다. 그렇기에 일단 나는 캐릭터 매쉬가 레그돌이 되면 이 연산을 서버 자체에서 진행하고 각 클라이언트에 뿌리면 레그돌이 클라이언트 마다 화면에 다른 위치로 보이는 현상을 고칠 수 있을 것이라 생각 했다.
레그돌을 서버에 레플리케이션 시키자
일단 서버에서 레그돌의 물리를 연산하게 하려면 관련 변수들을 레플리케이션에 등록해서 서버가 알게 끔 해야한다 그렇기에 bIsStunned의 경우처럼 관련 변수들을 레플리케이션에 등록해 줘야 하는데 어떤 변수가 필요할까? 멀티플레이 환경에서 내가 보는 화면과 상대방이 보는 화면에서의 기절한 캐릭터의 위치와 모양은 동일할 수록 좋다. 하지만 현재 레그돌된 매시의 물리 시뮬레이션은 각 클라이언트에서 돌리는 상황, 그렇기에 우리는 기절한 캐릭터의 플레이어 클라이언트에서 계산한 물리 값을 서버로 보내고 다시 서버에서 각 클라로 전송해주는 방식으로 해결하려 하고 있다. 이때 사용되는 값으로는 위치(Location), 속력(Velocity), 회전방향(Rotation), 회전력(AngularVelocity) 정도가 되겠다. 이에 해당하는 값을 담을 변수들을 선언해주자 특히 제일 중요한 위치 값은 매쉬의 루트인 골반(pelvis)의 위치를 가져오게 한다.

이제 준비는 끝났다. 이제 저 변수에다 실제로 매 Tick마다 값을 받아와 갱신해주면 사전 작업은 끝이 나게 된다.
가장 중요한 OnRep_ServerRagdollLocation
이제 ServerRagdollLocation의 값이 갱신될 때 마다 OnRep_ServerRagdollLocation함수가 각 클라이언트에서 호출되게 된다. 기절중이 아니거나(= bIsStunned가 false) HasAuthority가 true이면, 즉 호출된 곳이 서버이면 바로 함수가 종료되게 한다.(서버 Tick에서 해당 변수들을 갱신하기에 굳이 덮어 쓸 필요는 없다.) 이 때 클라이언트는 각자 물리 엔진을 이용해서 레그돌 된 매쉬를 굴리고 있다. 이제 서버에서 갖는 위치 값과 클라이언트가 계산한 매쉬의 위치 값의 차이가 일정 크기를 넘어서면 강제로 클라이언트의 매쉬 위치를 서버에서의 위치로 옮길 것이다.
void ATTPlayerCharacter::OnRep_ServerRagdollLocation ()
{
if (!bIsStunned || HasAuthority ()) return; //기절이 아니거나 호출된 곳이 서버이면 종료
FVector CurrentLoc = GetMesh ()->GetSocketLocation ( TEXT ( "pelvis" ) ); //현재 위치를 가져옴
float Dist = FVector::Dist ( CurrentLoc , ServerRagdollLocation ); //위치 차이를 계산
if (Dist > 100.0f)
{
GetMesh ()->SetWorldLocation ( ServerRagdollLocation , false , nullptr , ETeleportType::TeleportPhysics );
GetMesh ()->SetWorldRotation ( ServerRagdollRotation , false , nullptr , ETeleportType::TeleportPhysics );
GetMesh ()->SetPhysicsLinearVelocity ( ServerRagdollVelocity );
GetMesh ()->SetPhysicsAngularVelocityInDegrees ( ServerRagdollAngularVelocity );
}
else if (Dist > 10.0f)
{
FVector FixDirection = (ServerRagdollLocation - CurrentLoc);
float CorrectionPower = 10.0f;
FVector NewVelocity = ServerRagdollVelocity + (FixDirection * CorrectionPower);
GetMesh ()->SetPhysicsLinearVelocity ( NewVelocity );
}
}
처음에는 단순하게 Dist 값이 15보다 크면 강제로 텔레포트 시키는 방식으로 구현했었는데 이렇게 했더니 캐릭터가 덜덜덜덜 떨리는 현상이 발생했다. 그래서 차이가 엄청나게 큰 경우만 강제로 텔레포트 시키고 적당히 차이나는 경우에는 위치의 오차 만큼 선형적으로 움직이게끔 하여 부드럽게 동기화 되도록 하였다. 그렇기에 SetLocation이 아닌 SetPhysicsLinearVelocity의 값만 NewVelocity(== 차이로 인한 방향 * 적당한 속력)을 넣었다. 이로 인해 각 클라이언트의 화면에서도 동일한 위치에 매쉬가 존재하게끔 되어서 문제가 해결되는 듯 했으나...
기절이 풀리니 다시 제자리로?
이번에는 기절에서 풀리게 되면 기절했던 자리로 슝 하고 돌아오게 되는 것이 아닌가? 이유는 당연하게도 레그돌된 그 순간 캡슐 컴포넌트는 그 자리에 있고 매쉬만 별도의 시뮬레이션을 통해서 움직이기 때문이다. 그렇기에 별도로 WakeUp함수가 호출되면 Mesh의 pelvis의 위치를 변수에 담아두고 해당 위치로 캡슐 컴포넌트를 이동시키는 방식을 추가해주기로 했다. 이렇게 해서 캐릭터를 공격해서 기절시키고, 기절되면 레그돌이 되어 굴러다니고 기절이 풀리면 다시 캐릭터를 움직일 수 있게 끔 하나의 기절 사이클이 완성 되었다.
기타 발생했던 문제
이 외에도 spring arm은 캡슐에 어태치 되어 있기에 기절 순간에만 mesh에 어태치 시키는 등의 작업으로 매쉬가 굴러다닐때 카메라 또한 따라가게끔 하는 등의 자잘한 문제도 있었지만 이번 래그돌을 구현하면서 정말 많은 것을 배울 수 있었던 것 같다.
'unreal 5기' 카테고리의 다른 글
| 260107 언리얼엔진 본캠프 103일차 GASP(1) (0) | 2026.01.07 |
|---|---|
| 251226 언리얼엔진 본캠프 96일차 멀티플레이 팀프로젝트8 (0) | 2025.12.26 |
| 251218 언리얼엔진 본캠프 91일차 멀티플레이 팀프로젝트6 (1) | 2025.12.18 |
| 251217 언리얼엔진 본캠프 90일차 멀티플레이 팀프로젝트5 (0) | 2025.12.17 |
| 251215 언리얼엔진 본캠프 88일차 멀티플레이 팀프로젝트4 (1) | 2025.12.15 |