지연 보상
물리 객체를 위한 지연 보상
게임에 물리 객체가있을 때, 특히 두 개 이상의 게임 윈도우가 서로 나란히 있을 때 이러한 객체가 동기화되지 않을 수 있습니다. 이로 인해 게임에 심각한 문제가 발생할 수 있으며 결국 플레이어 경험을 저하시킬 수 있습니다.
이러한 종류의 동기화 문제는 메시지가 한 클라이언트에서 다른 클라이언트로 ‘이동’하는 데 걸리는 시간 때문에 발생합니다. 예: 클라이언트 A가 캐릭터를 앞으로 이동시키고 현재 위치를 보냅니다. 이 메시지는 클라이언트 A가 보낸 직후 100ms 후에 클라이언트 B가 수신합니다. 클라이언트 B는 이 정보를 사용하여 클라이언트 A 캐릭터를 정확한 위치에 놓아 게임을 최신 상태로 유지합니다. 클라이언트 A는 지난 100ms 동안 자신의 캐릭터를 앞으로 이동시키지 않았으므로 그의 캐릭터는 세계에서 새로운 위치로 이동했습니다. 이 순간에 오브젝트는 클라이언트 A와 클라이언트 B의 게임에서 다른 위치를 가지므로 완전히 동기화되지 않은 상태입니다. 캐릭터의 이동 속도에 따라 두 위치의 차이가 달라집니다: 움직임이 다소 느린 경우 눈에 띄지 않을 수도 있습니다. 그러나 이동 속도가 매우 빠르면 두 게임 창에 차이가 명확하게 표시됩니다. 우리는 Photon Quantum과 같은 다른 기술을 사용하지 않는 한 이 문제를 완전히 해결할 수 없으므로 가능한한 이 문제를 제거하려고 하고 있으며 이를 ‘지연 보상’이라고 합니다.
지연 보상은 어떤 의미이며 어떻게 동작하나요?
물리 객체에 지연 보상을 적용할 때, 우리는 객체의 소유자에게 객체의 위치와 회전 정보 이외에 추가 데이터를 보내도록 요청하며 이러한 경우에 객체의 속도를 찾고 있습니다. 단순히 원격 클라이언트의 객체에서 받은 정보를 적용하는 대신 객체의 최신 정보와 보다 정확한 동작을 계산하기 위해 속도 정보를 사용하고 있습니다. 따라서 메시지를 보내고 받는 사이에서 정확한 시간이 더 필요합니다. 이 예제에서는 사용자 정의 OnPhotonSerializeView 솔루션 (아래 참조)을 사용하므로 이 함수를 기반으로 전달 된 시간을 계산하는 방법을 보여줍니다.
먼저, 우리는 빈 OnPhotonSerializeView 구현이 필요합니다:
C#
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { }
이 함수의 송신측 구현은 비교적 직관적이며 나중에 보여줄 것이므로 여기서는 대부분의 작업이 수행되므로 수신측을 먼저 살펴 보도록 하겠습니다. 수신 클라이언트가 해야 할 한 가지는 현재 메시지를 보내고 받는 사이에 경과 한 이전에 언급된 시간을 계산하는 것입니다. 따라서 우리는 메시지가 전송 된 순간을 나타내는 타임스탬프가 포함 된 PhotonMessageInfo를 사용하고 있습니다. 또한 PhotonNetwork.time을 사용하여 현재 시간과 이전에 언급 된 시간과의 차이를 계산합니다. 결과는 그 사이에 경과된 시간입니다.
C#
float lag = Mathf.Abs((float) (PhotonNetwork.time - info.timestamp));
이 값을 통해 소유자로부터 받은 정보에 따라 객체가 어떻게 움직였는지 계산할 수 있습니다. 그렇게 하기 위해 두 가지의 옵션을 아래에 설명하였습니다.
객체 갱신을 위해 OnPhotonSerializeView 사용
첫 번째 옵션은 단순히 OnPhotonSerializeView 함수를 이용하여 객체를 업데이트 하는 것 입니다. 빈 OnPhotonSerializeView 함수를 기반으로 보낸 사람은 다른 모든 클라이언트와 필요한 모든 정보를 공유합니다. 이 경우 우리는 Rigidbody의 위치, 회전 및 속도를 전송합니다. 수신자는 수신 된 정보를 객체의 Rigidbody 컴포넌트에 직접 저장 한 다음, 앞서 설명한대로 경과 시간을 계산합니다. 그 후에 속도를 이전에 계산 한 경과 시간으로 곱합니다. 그런 다음이 계산 결과가 Rigidbody의 컴포넌트 위치에 추가합니다. 이제 우리는 원격 클라이언트를 더 정확하게 설명하는 객체가 있습니다. OnPhotonSerializeView 함수의 전체 구현이 아래에 나와 있습니다.
C#
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.isWriting)
{
stream.SendNext(rigidbody.position);
stream.SendNext(rigidbody.rotation);
stream.SendNext(rigidbody.velocity);
}
else
{
rigidbody.position = (Vector3) stream.ReceiveNext();
rigidbody.rotation = (Quaternion) stream.ReceiveNext();
rigidbody.velocity = (Vector3) stream.ReceiveNext();
float lag = Mathf.Abs((float) (PhotonNetwork.time - info.timestamp));
rigidbody.position += rigidbody.velocity * lag;
}
}
객체 갱신을 위해 OnPhotonSerializeView와 FixedUpdate 사용
두 번째 옵션은 OnPhotonSerializeView 구현 외에 Unity의 FixedUpdate 함수를 사용하는 것 입니다. 다시 빈 OnPhotonSerializeView 함수로 시작합니다. 이 접근법에서 보낸 사람은 동일한 작업을 수행합니다. 즉, Rigidbody의 위치, 회전 및 속도를 공유하는 것입니다. 수신자의 작업은 이전의 접근법과 다릅니다. 이 시간에는 현재 메시지를 보내고 받는 사이에 경과 한 시간을 계산하기 전에 받은 속도 정보만 개체의 Rigidbody 컴포넌트에 저장합니다. 다른 정보(위치 및 회전)는 이 시점에서 로컬 변수에 저장됩니다. 이 예제에서 지역 변수는 networkPosition (Vector3 유형) 및 networkRotation (Quaternion 유형)입니다. 그 다음 수신자는 Rigidbody의 속도에 전달 된 시간을 곱하고 이 계산의 결과를 로컬에 저장된 networkPosition 변수에 더합니다. OnPhotonSerializeView 함수의 전체 구현은 아래와 같습니다.
C#
public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
{
if (stream.isWriting)
{
stream.SendNext(this.m_Body.position);
stream.SendNext(this.m_Body.rotation);
stream.SendNext(this.m_Body.velocity);
}
else
{
networkPosition = (Vector3) stream.ReceiveNext();
networkRotation = (Quaternion) stream.ReceiveNext();
rigidbody.velocity = (Vector3) stream.ReceiveNext();
float lag = Mathf.Abs((float) (PhotonNetwork.time - info.timestamp));
networkPosition += (this.m_Body.velocity * lag);
}
}
지금까지 객체에 위치 또는 회전 업데이트를 적용하지 않았음을 분명히 알았습니다. 이것은 우리가 목표 위치로 목표물을 옮기고 다음 목표 회전점으로 회전시킬 때 다음 단계에서 수행됩니다. 아래와 같이 Unity의 FixedUpdate 함수 안에서 단계적으로 이 작업을 수행하고 있습니다.
C#
public void FixedUpdate()
{
if (!photonView.IsMine)
{
rigidbody.position = Vector3.MoveTowards(rigidbody.position, networkPosition, Time.fixedDeltaTime);
rigidbody.rotation = Quaternion.RotateTowards(rigidbody.rotation, networkRotation, Time.fixedDeltaTime * 100.0f);
}
}
물리 객체가 아닌 것의 지연 보상
때로는 게임에서 필요로 하지 않기 때문에 Rigidbody 컴포넌트가 없는 개체를 가지고 있는 경우가 있습니다. 이 경우 Transform 컴포넌트를 사용하여 동기화합니다. 이 시점에서 두 개의 (또는 그 이상) 다른 화면에서 동일한 객체간에 약간의 지연과 약간의 차이가 있음을 발견 할 수 있습니다. 특히 게임 창을 서로 옆에 배치하는 경우 특히 그렇습니다. 좋은 소식은 약간의 조정을 통해 이전에 배운 것을 사용할 수 있다는 것입니다.
먼저, 객체의 움직임을 설명하는 옵션이 필요합니다. Rigidbody 구성 요소의 velocity 속성이 없으므로 사용자지정 솔루션을 사용해야합니다. 쉬운 접근법은 객체의 마지막 두 위치 사이의 차이를 사용하는 것입니다.
C#
Vector3 oldPosition = transform.position;
// Handling position updates related to the given input
movement = transform.position - oldPosition;
먼저 현재 위치를 oldPosition이라는 임시 변수에 저장합니다. 그 후에 우리는 모든 입력을 처리하고 이에 따라 객체의 위치를 업데이트합니다. 결국 우리는 객체의 움직임 (Vector3 유형)을 설명하는 로컬에 저장된 위치와 업데이트된 위치 차이를 계산하고 Rigidbody 컴포넌트의 velocity 속성에 대한 ‘대체’ 를 수행합니다. 이 코드 스니펫은 Update 함수의 일부입니다.
나머지는 기본적으로 이전 접근 방식과 동일하지만 FixedUpdate 함수를 사용하는 대신 처음에 다음 코드 스니펫을 추가 할 수 있는 Update 함수를 사용합니다.
C#
if (!pView.isMine)
{
transform.position = Vector3.MoveTowards(transform.position, networkPosition, Time.deltaTime * movementSpeed);
transform.rotation = Quaternion.RotateTowards(transform.rotation, networkRotation, Time.deltaTime * 100);
return;
}
이 방식으로 객체에 Rigidbody 컴포넌트가 없는 경우에도 지연 보상을 사용할 수 있습니다.
결론
지연 보상은 게임에서 발생할 수 있는 모든 종류의 동기화 문제를 제거하는 데 도움이 되지않지만 전반적으로 훨씬 적은 동기화 문제로 플레이어 경험에 영향을 주는 안정적인 게임과 안정적인 시뮬레이션을 얻는 데 도움이됩니다.
Back to top