UE - 네트워크 팀플 회고록
UnrealEngine
목표와 동기
캐릭터 파트를 다시 맡으면서 이번엔 더 빠르게 많은 기능들을 구현해보는 것을 목표로 설정했다. 특히 파티 애니멀즈같이 흐느적 거리는 물리기반 캐릭터를 만드는 것 자체가 너무 궁금했다. 어떻게 캐릭터가 래그돌같이 움직이는지, 충돌 설정은 어떻게 처리해야 하는지 등등
시도한 것들
물리기반 캐릭터 구현
물리기반 캐릭터를 구현하기 위해 캐릭터의 메시에 피직스 애니메이션 컴포넌트를 적용했다. 래그돌같이 움직이기가 가능하면서도 애니메이션을 적용할 수 있는 컴포넌트라고 해서 좋은 선택이라고 생각했다.
잡기 시스템
정말 고생을 많이 했던 시스템이다.
어떻게 잡을 물체를 찾고, 잡고 놓을 것인지 구조를 짜는 데 고민을 많이 했었다.
이건 Grabbable
, GrabComp
, GrabHandler
로 구조를 나누어 시스템을 구현했다.
어빌리티 매니저와 상태 매니저
저번 프로젝트에서 GAS를 사용 했었던 것 처럼, 어빌리티를 관리하는 컴포넌트를 만들어봤다.
아직 개선할 부분이 많지만 Start, Activate, End 3 단계로 이루어지며 전체적인 구조는 전략 패턴을 이용해서 구현했다. 또한 캐릭터 상태를 결정하는 StateManager도 만들었다. 이건 상태 패턴을 이용한 구조다.
작업 중 발생한 문제들
문제점 1.
이 캐릭터는 ACharacter
를 상속받아 만들었고, 때문에 루트는 항상 캡슐 고정이다. 이 고정된 캡슐이 래그돌 캐릭터 메시가 넘어지지 않게 잘 잡아줬지만, 이게 오히려 캐릭터의 자유도를 떨어뜨리게 된 것이다.
기획 상 캐릭터에게는 날아차기 기능이 있었다. 하지만 점프 후 다리 바디 파츠에 힘을 보내줘도 결국 메시는 캡슐 안에 갇혀있어 극적인 모습을 보여주지 못했다.
심지어 다리도 짧아서 거의 점프하는 모션과 비슷했다.
지금 작성하면서 생각난건데, 날아차기를 할 때만 캡슐에 피직스를 활성화 시켜도 됐지 않았을까 생각이 든다. 이런..
문제점 2.
두 번째로 고민한 건 잡기 후 끌기 기능이였다. 이 캐릭터의 기본 이동은 CharacterMovementComp
인데 이건 물리적으로 연결된 Constraint
와 상성이 좋지 않았다. 두 캐릭터가 서로 잡힌 상태일 때, 캐릭터 무브먼트로 이동을 하게되면 컨스트레인트로 인해 다시 끌려오지 않고, 메시가 찢어지게 되는 문제가 발생했다.
그렇다고 CharacterMovement를 버릴 수 도 없던게 AI에서 Move To 함수가 캐릭터 무브먼트 컴포넌트가 있어야지만 작동하기에 마감 일주일 전 갑자기 구조를 바꿔버릴 수 도 없었다.
때문에 서로 잡고 잡힌 대상끼리 끌리는 효과를 연출하기 위해 가짜 물리력을 더해주는 MovementModifier
컴포넌트를 만들었다.
해결방안 이 Movement Modifier
는 서로 연결된 액터끼리 서로의 저항력을 자신의 CharacterMovementComp
의 Velocity
에 더해주는 방식으로 끌림 효과를 흉내낸다.
저항력은 클래스 타입마다 다르게 구할 수 있게 IGrabbable
인터페이스에 저항력을 구하는 함수를 넣어줬다. (잡기 가능한 액터는 모두 이 인터페이스를 상속해야한다.)
이 컴포넌트로 인해 어느정도 물리효과를 흉내낼 수 있었다.
문제점 3.
피직스 시뮬레이트는 서버와 클라이언트의 차이가 많이 날 수 있다는 문제다.
여기서 시간을 정말 많이 소모했다.
아직 바디파츠를 만들기 전, 래그돌을 검사하다가 발견한 문제였는데, 각 PC마다 시뮬레이션의 결과 차이가 나는것이 육안으로 보였다.
이걸 해결하기 위해 시도한 방법들은 3가지가 있다.
- 바디파츠 지금 이 캐릭터의 메인이 되는 클래스. 캐릭터의 각 본과 컨스트레인트로 연결되어 있으니, 이걸 복제하고 연결하면 캐릭터 메시가 서버측과 비슷하게 모습을 보여주겠지? 라는 생각으로 도전했다. 물론 서버의 위치를 복제하여 강제로 이동하다보니 가끔씩 캐릭터가 덜덜거리며 떨기도 한다.
- 캐릭터 메시의 주요 본 트랜스폼 복제 이건 캐릭터 메시에서 중요한 부위들의 본 위치를 구조체에 넣어 클라이언트가 자신의 메시 본의 위치와 회전을 수정하는 방식이다. 하지만 왠지 모르게 T 포즈만 나와서 실패했다. 아마 내가 가져온 본 구조가 애니메이션과 물리가 적용되기 전의 정보였다고 의심하고 있다.
- Poseable Mesh 위의 방식과 비슷하지만 메시 그 자체에 복제된 값을 넣는 것이 아닌 Poseable Mesh를 이용한 방식이다. Poseable Mesh의 함수 중엔 CopyPoseFromSkeletalComponent(SkeletalMeshComp) 가 있는데, 이 함수는 애니메이션이 적용된 스켈레탈 메시의 본 정보를 복사해 반영하는 함수다. 그렇기에 매 틱마다 멀티캐스트로 모든 클라이언트에게 Poseable Mesh의 결과만 보여주는 방식이다. 이러면 클라이언트는 캐릭터 메시를 시뮬레이션 할 필요도 없어지고, 자연스럽게 오차도 사라지기에 좋은 생각이라고 생각했다. 하지만 결국 이 방법도 실패했는데, Poseable Mesh는 사용하기 전에 SetSkinnedAsset으로 자신의 메시를 먼저 설정해야한다. 하지만 메시는 복제가 되지 않아 서버 메시와 동등한 조건이 아니게 되버리는 것이다. 때문에 서버에서 Physics Anim Comp로 메시에 래그돌 상태를 부여했지만, 클라이언트는 아니기에 차이가 발생하게 되는 것이다. 때문에 이 방법도 실패했다. 3번째 방법만 성공했어도 클라이언트는 계산 없이 정말 렌더링만 해도 될 정도였을텐데 정말 아쉽다.
물론 여기에 시간을 많이 사용해 개선할 부분들이 많다. 네트워크 복제 문제로 GrabSystem
구조에 급하게 변화가 생겨 내가 원하던 이상적인 구조가 아니게 된 점.
어빌리티 매니저에 컨디션 이라는 기능을 추가해 추가적으로 검사할 조건들을 생성하는 기능. (예를 들면 캐릭터 상태)
어빌리티 호출 방식 중 키 값이 리터럴이라 나중엔 동적인 값으로 바꿀 수 있도록 개선할 수 있을 것 같다.
또한 캐릭터 무브먼트와 AI에 대한 지식이 부족해 완전한 물리 캐릭터를 만들지 못한 점도 아쉽게 느껴진다.
때문에 최종 프로젝트를 시작하기 전 AI
와 CharacterMovement
에 대해 공부해볼 계획이다.