멀티플레이 구현에 앞서, 지금까지 구현했던 것들을 멀티플레이에서 멀쩡히 동작시키기 위해 어떤식으로 바꾸면 될 지 리스트를 작성할 필요가 있었다. 리슨서버를 열고, 두 명의 플레이어로 플레이해보면서 어떤 기능에 문제가 있는지를 파악해 보았다.
우선은 세 가지 문제점이 있었다.
첫 번째 문제의 경우, 언리얼의 오류메세지를 보고 쉽게 고칠 수 있었다. FindMagneticPillar()
함수의 GetController()->GetPlayerViewPoint(Location, Rotation);
이 구문에서 문제가 생겼었다.
맨 앞줄에 !IsLocallyControlled()
일 시 return 하는 구문을 추가해 해결해 주었다. !IsLocallyControlled()
함수는 현재 액터가 로컬 플레이어에 의해 제어되는지 여부를 나타내는데, 아마 다른 플레이어에 의해 제어되는 액터의 시점을 가져오려고 해서 문제가 생긴 것 같다.
사진을 보면 알 수 있듯, 클라이언트에서는 서버 캐릭터와 마주보고 쳐다보고 있지만 서버에서는 회전이 반영이 안되어 등을 돌리고 있는 모습이다.
캐릭터 무브먼트 컴포넌트를 사용하고 있기 때문에 당연히 잘 동작할 것이라고 생각했고, Third Person으로 변경해서 동작해보면 메시의 회전이 잘 리플리케이트 되는 것을 확인할 수 있었기에 도대체 왜 반영이 안되는건지 고민이 많았다.
이것저것 디버깅 하던 와중, 생성자 단계에서 bUseControllerRotationYaw = false;
를 적용할 경우 메시의 회전값이 잘 들어온다는 것을 확인했다. 그래서 컨트롤 옵션이 리플리케이트 되지 않는거 아닐까? 라는 의문을 가지고 구글링 해보았다.
나랑 똑같은 문제를 가진 사람을 발견.. 당연히 리플리케이션 되지 않는다는 답변을 볼 수 있었다.
클라이언트쪽에서 컨트롤 옵션이 변경될 때마다 서버 RPC를 보내주고, 멀티캐스트 RPC를 통해 클라이언트로 복제해 해결하기로 했다.
// ATrapperPlayer.h
UFUNCTION(Server, Reliable)
void ServerRPCChangeControlOptions(ECharacterControlType type);
UFUNCTION(NetMulticast, Reliable)
void MulticastChangeControlOptions(ECharacterControlType type);
두 개의 RPC 함수를 선언해주고, 기존의 SetCharacterControlData가 매개변수로 컨트롤 타입을 받아 변경하도록 변경해주었다.
void ATrapperPlayer::ServerRPCChangeControlOptions_Implementation(ECharacterControlType type)
{
MulticastChangeControlOptions(type);
}
void ATrapperPlayer::MulticastChangeControlOptions_Implementation(ECharacterControlType type)
{
SetCharacterControlData(type);
}
구현부는 이렇다.
void ATrapperPlayer::CharacterControlTypeCheck()
{
ECharacterControlType TempType = ControlState;
if (GetVelocity().IsNearlyZero())
{
bIsAltPressed ? ControlState = ECharacterControlType::IdleAlt : ControlState = ECharacterControlType::Idle;
}
else
{
bIsAltPressed ? ControlState = ECharacterControlType::MovingAlt : ControlState = ECharacterControlType::Moving;
}
if(BowMechanics->IsDrawingBow())
{
ControlState = ECharacterControlType::Drawing;
}
// 만약 상태가 달라진다면 함수 호출
if (TempType == ControlState) return;
if (HasAuthority())
{
SetCharacterControlData(ControlState);
}
else if(IsLocallyControlled())
{
ServerRPCChangeControlOptions(ControlState);
}
}
서버 RPC를 호출해주는 쪽은 CharacterControlTypeCheck()
함수이다. 서버일 경우 바로 캐릭터 컨트롤 옵션을 적용해주고, 로컬 컨트롤러일 경우 서버 RPC를 호출해준다.
서버쪽에서 정상적으로 움직이는 클라이언트를 확인할 수 있다! 다음 포스팅 땐 자성이동을 개선할 예정이다 :)