참고: UNet은 지원이 중단되었으며 향후 Unity에서 삭제될 예정입니다. 현재 새로운 시스템이 개발 중입니다. 자세한 내용과 다음 단계는 이 블로그 포스트를 참조하십시오. |
대부분의 경우 SyncVar만 사용해도 게임 스크립트가 자체 상태를 클라이언트에 직렬화할 수 있습니다. 하지만 좀 더 복잡한 직렬화 코드가 필요한 경우도 있습니다. 이 페이지는 Unity의 일반 SyncVar 기능 이외에 커스터마이즈된 동기화 솔루션이 필요한 숙련된 개발자를 위해 작성되었습니다.
자체 커스텀 직렬화를 수행하기 위해 NetworkBehaviour에서 가상 함수를 구현하여 SyncVar 직렬화에 사용할 수 있습니다. 다음과 같은 함수를 예로 들 수 있습니다.
public virtual bool OnSerialize(NetworkWriter writer, bool initialState);
public virtual void OnDeSerialize(NetworkReader reader, bool initialState);
initialState
플래그를 사용하면 게임 오브젝트가 처음으로 직렬화된 시점과 점진적 업데이트를 보낼 수 있는 시점을 구분할 수 있습니다. 게임 오브젝트가 처음으로 클라이언트에 전송되는 시점에서는 전체 상태의 스냅샷을 포함해야 하지만, 그 이후 발생하는 업데이트의 경우 점진적 변화만을 전송하여 대역폭을 절약할 수 있습니다. SyncVar 후크 함수는 initialState
가 true인 경우 호출되지 않으며, 점진적 업데이트의 경우에만 호출됩니다.
한 클래스에 SyncVar이 있는 경우, 이러한 함수의 구현이 클래스에 자동으로 추가됩니다. 즉, SyncVar가 있는 클래스는 커스텀 직렬화 함수를 가질 수 없습니다.
OnSerialize
함수는 업데이트가 보내져야 한다는 점을 표시하기 위해 true를 반환해야 합니다. true를 반환하게 되면 스크립트의 잔류 부분이 0으로 설정되며, false를 반환하면 이는 변경되지 않습니다. 따라서 이 기능을 통해 스크립트에 대한 여러 변경점을 프레임마다 보내는 대신 시스템이 준비될 때까지 축적한 다음 한 번에 보낼 수 있습니다.
Network Identity 컴포넌트가 있는 게임 오브젝트는 NetworkBehaviour
에서 파생된 다수의 스크립트를 가질 수 있습니다. 이 게임 오브젝트를 직렬화하는 과정은 아래와 같습니다.
서버 측:
각 NetworkBehaviour
는 더티 마스크가 있습니다. 이 마스크는 OnSerialize
에서 syncVarDirtyBits
으로서 사용할 수 있습니다.
NetworkBehaviour
스크립트의 각 SyncVar에 더티 마스크 비트가 하나씩 할당됩니다.
SyncVar의 값이 변경되면 더티 마스크에서 이 SyncVar에 해당하는 비트가 설정됩니다.
또는 SetDirtyBit()
를 호출하면 더티 마스크에 직접 작성할 수 있습니다.
서버 업데이트 루프의 일부로서 NetworkIdentity 게임 오브젝트가 서버에서 검사됩니다.
NetworkIdentity
에 더티로 설정된 NetworkBehaviours
가 있는 경우 해당 게임 오브젝트에 대해 UpdateVars
패킷이 생성됩니다.
UpdateVars
패킷은 해당 게임 오브젝트의 각 NetworkBehaviour
에 OnSerialize
를 호출함으로써 채워집니다.
더티로 설정되지 않은 NetworkBehaviours
는 자신의 더티 비트 패킷에 0을 작성합니다.
더티로 설정된 NetworkBehaviours
는 자신의 더티 마스크를 쓴 후, 변경된 SyncVars에 대한 값을 씁니다.
OnSerialize
가 NetworkBehaviour
에 대해 true를 반환하면 더티 마스크는 해당 NetworkBehaviour
에 대해 초기화되며, 이 값이 변경될 때까지 다시 전송되지 않습니다.
게임 오브젝트를 찾고 있는 준비된 클라이언트들에 UpdateVars
패킷이 전송됩니다.
클라이언트 측:
어떤 게임 오브젝트에 대한 UpdateVars 패킷
을 수신합니다.
해당 게임 오브젝트의 각 NetworkBehaviour
스크립트에 대해 OnDeserialize
함수가 호출됩니다.
해당 게임 오브젝트의 각 NetworkBehaviour
스크립트는 더티 마스크를 읽습니다.
NetworkBehaviour
의 더티 마스크가 0 이면 OnDeserialize
함수는 더 이상 읽지 않고 반환합니다.
더티 마스크가 0이 아닌 값이면 OnDeserialize
함수는 설정된 더티 비트에 대응하는 SyncVar 값을 읽습니다.
SyncVar 후크 함수가 있을 경우, 스트림으로부터 읽은 값을 가지고 이 함수를 호출합니다.
따라서 이 스크립트의 경우에는 다음과 같게 됩니다.
public class data : NetworkBehaviour
{
[SyncVar]
public int int1 = 66;
[SyncVar]
public int int2 = 23487;
[SyncVar]
public string MyString = "Example string";
}
다음 코드 예제는 생성되는 OnSerialize
함수를 보여줍니다.
public override bool OnSerialize(NetworkWriter writer, bool forceAll)
{
if (forceAll)
{
// The first time a GameObject is sent to a client, send all the data (and no dirty bits)
writer.WritePackedUInt32((uint)this.int1);
writer.WritePackedUInt32((uint)this.int2);
writer.Write(this.MyString);
return true;
}
bool wroteSyncVar = false;
if ((base.get_syncVarDirtyBits() & 1u) != 0u)
{
if (!wroteSyncVar)
{
// Write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.WritePackedUInt32((uint)this.int1);
}
if ((base.get_syncVarDirtyBits() & 2u) != 0u)
{
if (!wroteSyncVar)
{
// Write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.WritePackedUInt32((uint)this.int2);
}
if ((base.get_syncVarDirtyBits() & 4u) != 0u)
{
if (!wroteSyncVar)
{
// Write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.Write(this.MyString);
}
if (!wroteSyncVar)
{
// Write zero dirty bits if no SyncVars were written
writer.WritePackedUInt32(0);
}
return wroteSyncVar;
}
다음 코드 예제는 OnDeserialize
함수를 보여줍니다.
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
if (initialState)
{
this.int1 = (int)reader.ReadPackedUInt32();
this.int2 = (int)reader.ReadPackedUInt32();
this.MyString = reader.ReadString();
return;
}
int num = (int)reader.ReadPackedUInt32();
if ((num & 1) != 0)
{
this.int1 = (int)reader.ReadPackedUInt32();
}
if ((num & 2) != 0)
{
this.int2 = (int)reader.ReadPackedUInt32();
}
if ((num & 4) != 0)
{
this.MyString = reader.ReadString();
}
}
NetworkBehaviour
에 마찬가지로 직렬화 함수가 있는 베이스 클래스가 있다면 베이스 클래스 함수 역시 호출되어야 합니다.
게임 오브젝트 상태 업데이트를 위해 생성된 UpdateVar
패킷은 클라이언트로 전송되기 이전 버퍼에 축적될 수 있다는 점을 상기해야 합니다. 따라서 한 개의 전송 레이어 패킷에는 여러 게임 오브젝트에 대한 업데이트를 포함할 수 있습니다.