[UE5] 서버-클라이언트 캐릭터 컨트롤 옵션 동기화

연하·2024년 6월 7일
0

Trapper

목록 보기
8/32

멀티플레이 구현에 앞서, 지금까지 구현했던 것들을 멀티플레이에서 멀쩡히 동작시키기 위해 어떤식으로 바꾸면 될 지 리스트를 작성할 필요가 있었다. 리슨서버를 열고, 두 명의 플레이어로 플레이해보면서 어떤 기능에 문제가 있는지를 파악해 보았다.

우선은 세 가지 문제점이 있었다.

  • 플레이 하자마자 터짐
  • 서버/클라이언트 각각 캐릭터의 움직임이 잘 동작하는 것은 확인했다. 하지만, 서버에서 캐릭터를 움직일 때, 서버 캐릭터의 메시 회전은 클라이언트에 반영되지만 클라이언트에서 움직인 캐릭터의 메시 회전은 서버에서 적용되지 않았다.
  • 솔루나 시프트(자성 이동)가 클라이언트에서는 작동하지 않는다. 목적지까지 날아가는 것까지는 가능하지만, 솔루나 시프트가 종료되면 원래 위치로 되돌아간다.

첫 번째 문제

첫 번째 문제의 경우, 언리얼의 오류메세지를 보고 쉽게 고칠 수 있었다. 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를 호출해준다.

서버쪽에서 정상적으로 움직이는 클라이언트를 확인할 수 있다! 다음 포스팅 땐 자성이동을 개선할 예정이다 :)

0개의 댓글