소켓에서 생성되는 발사체 리플리케이션 문제

무엇이 문제일까?

두 플레이어가 마주보고 폭탄을 던지는 상황을 생각해보자. RPC와 리플리케이트는 정상적으로 작동하는 모습이다. 각자가 폭탄을 던지는 몽타주가 재생되고, 폭탄도 제대로 생성되고, 날아가는 궤적도 양쪽에서 모두 동일하게 확인된다.

자, 이제 상황이 달라졌다. 리슨서버의 플레이어는 클라이언트 플레이어를 시야에 담지 않는다. 다만 폭탄을 던지는 방향만은 서로 교차되기 때문에 폭탄이 정상적으로 리플리케이트 된다면 양 쪽 모두 동일한 궤적을 확인할 수 있을 것이다.

그리고 이 때 문제가 발생한다. 이전까지는 잘 작동하던 리플리케이션이 갑자기 말썽을 일으킨다. 클라이언트 플레이어가 던진 폭탄은 이상한 곳에서 생성되며, 깔끔하게 날아가지 않고 기묘한 움직임을 보인다. 각자의 화면을 보면 같은 폭탄이 다른 위치에서 폭발하는 모습이 보인다.

왜 이럴까?

원인이 무엇일까?

우선 리플리케이트 옵션을 먼저 확인해보았다.

  • 캐릭터의 움직임은 CharacterMovementComponent에서 관리한다. 매우 정상적으로 작동.
  • 캐릭터의 몽타주는 RPC를 활용한다. 이 또한 매우 정상적으로 작동.
  • 폭탄 액터는 BeginPlay()에서 SetReplicates(true), SetReplicateMovement(true) 설정을 해두었다.

    위의 설정들이 있기에 마주본 상태에서는 정상적으로 리플리케이트가 진행된다. 혹시 폭탄 액터가 리플리케이트 대상이 아니라고 판단하는건가 싶어서 관련성 옵션도 확인해봤다.

  • bAlwaysRelevant = true

거리나 시야에 관계 없이 리플리케이트가 진행되어야했다. 하지만 그렇지 않았다. 혹시나 전송량이 많아서 폭탄 정보 전송에 문제가 생겼다던가..?

  • 우선순위를 조정해보거나 프로파일러로 확인해봤음에도 문제가 없었다.

물리 설정 관련해서 문제가 생겼나..? 리플리케이션…? 서버쪽…? 아니면 클라이언트…? 어디의 무엇이 문제인지 찾기가 어려웠다.

해결법을 찾다 - VisibilityBasedAnimTickOption

해결 방법을 탐색하던 중, 레딧에서 해당 글을 발견했다. 혹시나 싶어서 이 사람이 올린 영상까지 확인해보니 나와 증상이 같은 사람이었다!!

문제의 원인은 VisibilityBasedAnimTickOption 이었다. 이는 보이는지 여부에 따라서 애니메이션 재생 여부를 결정하는 옵션이다. 최적화를 생각한다면 눈에 보이지 않는 애니메이션은 재생하지 않는 것이 좋다. 하지만 그 애니메이션 재생 여부에 따라서 소켓의 위치가 달라지고, 소켓에서 발사되는 발사체의 위치 리플리케이션이 꼬이게 된 것.

/** Skinned Mesh Animation Tick option based on rendered or not. This dictates "TickPose and RefreshBoneTransforms" */
UENUM(BlueprintType)
enum class EVisibilityBasedAnimTickOption : uint8
{
	/** Always Tick and Refresh BoneTransforms whether rendered or not. */
	AlwaysTickPoseAndRefreshBones,
	/** Always Tick, but Refresh BoneTransforms only when rendered. */
	AlwaysTickPose,
	/**
		When rendered Tick Pose and Refresh Bone Transforms,
		otherwise, just update montages and skip everything else.
		(AnimBP graph will not be updated).
	*/
	OnlyTickMontagesWhenNotRendered,
	/** Tick only when rendered, and it will only RefreshBoneTransforms when rendered. */
	OnlyTickPoseWhenRendered,
};

캐릭터 매쉬의 해당 옵션을 EVisibilityBasedAnimTickOption::AlwaysTickPoseAndRefreshBones로 설정하여 시야 범위 바깥에 있더라도 애니메이션을 재생하도록 하였고, 정상적으로 리플리케이션이 진행되며 문제가 해결되었다.