This document is about: PUN 2
SWITCH TO

PUN Classic (v1)、PUN 2、Boltはメンテナンスモードとなっております。Unity2022についてはPUN 2でサポートいたしますが、新機能が追加されることはありません。お客様のPUNプロジェクトおよびBoltプロジェクトが停止することはなく、将来にわたってパフォーマンス性能が落ちることはありません。 今後の新しいプロジェクトについては、Photon FusionまたはQuantumへ切り替えていただくようよろしくお願いいたします。

ラグ補正

物理オブジェクトのためのラグ補正

ゲームに物理オブジェクトがある場合、これらのオブジェクトの同期が少し遅れている点に気づくかもしれませんー特に、2つ以上のゲームウィンドウが隣にある場合です。この点は、ゲームに重大な問題点を招く原因となり、結果的にプレイヤーの体験を低下させる可能性があります。

こういった同期に関する問題は、クライアントから別のクライアントにメッセージが ‘送信’される際の所要時間が原因です。たとえば、クライアントAが自分のキャラクターを前方に動かし、現在の位置を送信したとします。このメッセージは、クライアントAが送信してからわずか100ミリ秒後にクライアントBによって受信されます。クライアントBはこの情報を使って、クライアントAのキャラクタ0を最新の位置に設置し、ゲームを最新の状態に保持します。クライアントAが直近の100ミリ秒間、自分のキャラクターを前方に動かし続けたため、このキャラクターはワールドの中で新たな位置に到達しました。この瞬間には、オブジェクトは完全には同期されていません。これは、クライアントAとクライアントBで異なる位置を保持しているためです。 キャラクターの移動速度によって、これら2つの位置の違いは異なります。移動が非常に遅い場合、違いはまったく気づかないほどです。しかし、移動が非常に高速な場合には、2つのゲームウィンドウ違いは明らかです。この問題は完全に取り除くことはできないため(Photon Quantumのような別の技術を使用していない限り)、この問題の表出をできる限り抑えるため、「ラグ補正」という技術を実装しています。

ラグ補正の定義と機能

物理オブジェクトにラグ補正を適用する場合、オブジェクトの位置と回転に加えて補足情報を送信するよう、オブジェクトのオーナーに依頼します。 この場合、私達はオブジェクトの速度が必要です。受信した情報はリモートクライアント上のオブジェクトに単純に適用するのではなく、オブジェクトのより最新で正確な挙動を算出するために使用されます。 このため、メッセージを送信した時点から受信した時点への、正確な経過時間を把握する必要があります。この例では、カスタムのOnPhotonSerializeViewソリューション(下記を参照してください)を使用するため、この機能にもとづいて経過時間を算出する方法を示します。

まず、空のOnPhotonSerializeView実装が必要です:

C#

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) { }
注意:この機能を活用するには、スクリプトに `IPunObservable`インターフェースを実装する必要があります。

この機能を実装する際の、送信者の処理は比較的単純ですので後に説明します。ここではまず、作業のほとんどがおこなわれる受信者の処理をみてみましょう。受信者がおこなう設定の1つとして、先に述べた最新のメッセージが送信され受信されるまでの経過時間の計算があります。このため、メッセージが送信された時刻を表すタイムスタンプを含むPhotonMessageInfoを使用します。また、現在時刻とさきに述べたタイムスタンプ間の差異を計算するには、PhotonNetwork.Timeを使用します。結果的に、送信されてから受信されるまでの時間間隔が算出されます。

C#

    float lag = Mathf.Abs((float) (PhotonNetwork.Time - info.timestamp));

この値を得たことで、オーナーから得る情報にもとづいてオブジェクトがどのように移動したかを計算できます。この計算を実施するには、以下で説明する2つのオプションがあります。

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を使用してオブジェクトをアップデート

2番目のオプションでは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コンポーネントを使用してこれらのオブジェクトを同期します。この時点では、2つ(または複数)の異なる画面上で同じオブジェクト間に遅延や、若干のオフセットがあることに気づいたかもしれません。これは、特にゲームウィンドウが隣り合っている場合に顕著です。幸いなことに、これらのオブジェクトを調整するにはさきに習得した事項を使用できます。

まず、オブジェクトの動きを表すオプションが必要です。Rigidbodyコンポーネントに速度プロパティがないため、カスタムソリューションを使用する必要があります。簡単なアプローチとしては、オブジェクトの直近の2つの位置間の差異を使用する方法があります。

C#

    Vector3 oldPosition = transform.position;

    // Handling position updates related to the given input

    movement = transform.position - oldPosition;

まず、現在の位置をoldPositionと呼ばれるテンポラリ変数に保存します。その後これに応じて、そのオブジェクトの位置のすべての入力とアップデートを処理します。最後に、ローカルに保存された位置とアップデートされた位置の差異を計算して、オブジェクトの移動(Vector3型)を把握し、Rigidbodyコンポーネントの速度プロパティを‘置換’します。以下のコードスニペットは、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