Version: 2021.1
언어: 한국어
상태 동기화
네트워크 가시성

고급 상태 동기화

중요: UNet은 지원이 중단된 솔루션이며, 새로운 멀티플레이어 및 네트워킹 솔루션(MLAPI)이 개발 중입니다. 자세한 내용과 다음 단계는 Unity MLAPI 웹사이트에 있는 정보를 참조하십시오.

대부분의 경우 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 패킷은 해당 게임 오브젝트의 각 NetworkBehaviourOnSerialize를 호출함으로써 채워집니다.

  • 더티로 설정되지 않은 NetworkBehaviours는 자신의 더티 비트 패킷에 0을 작성합니다.

  • 더티로 설정된 NetworkBehaviours는 자신의 더티 마스크를 쓴 후, 변경된 SyncVars에 대한 값을 씁니다.

  • OnSerializeNetworkBehaviour에 대해 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 패킷은 클라이언트로 전송되기 이전 버퍼에 축적될 수 있다는 점을 상기해야 합니다. 따라서 한 개의 전송 레이어 패킷에는 여러 게임 오브젝트에 대한 업데이트를 포함할 수 있습니다.

상태 동기화
네트워크 가시성