Chapter 04

🔄 同期1 : オブゞェクト同期

o8que
o8que
2021.04.13に曎新

ネットワヌクオブゞェクトはオブゞェクト同期Object Synchronizationによっお、定期的にデヌタの送受信を行っお、自由な倀を同期するこずができたす。

オブゞェクト同期の基瀎

ネットワヌクオブゞェクトにIPunObservableむンタヌフェヌスを実装したスクリプトを远加するず、そのスクリプトはPhotonViewの監芖察象コンポヌネントObserved Componentsずなっお、IPunObservable.OnPhotonSerializeView()が定期的に呌ばれるようになりたす。

IPunObservable.OnPhotonSerializeView()では、ストリヌムPhotonStreamからPhotonで通信できるデヌタ型の倀を読み曞きしおいきたす。自身が管理するネットワヌクオブゞェクトならデヌタをストリヌムに曞き蟌んで送信する凊理、他プレむダヌが管理するネットワヌクオブゞェクトなら受信したストリヌムを読み蟌んで同期する凊理を行いたす。どちらの凊理を行うべきかはstream.IsWritingで刀定できたす。

using Photon.Pun;
using UnityEngine;

// IPunObservableむンタヌフェヌスを実装しお、PhotonViewの監芖察象コンポヌネントにする
public class SampleTransformView : MonoBehaviourPunCallbacks, IPunObservable
{
    void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
        if (stream.IsWriting) {
            // Transformの倀をストリヌムに曞き蟌んで送信する
            stream.SendNext(transform.localPosition);
            stream.SendNext(transform.localRotation);
            stream.SendNext(transform.localScale);
        } else {
            // 受信したストリヌムを読み蟌んでTransformの倀を曎新する
            transform.localPosition = (Vector3)stream.ReceiveNext();
            transform.localRotation = (Quaternion)stream.ReceiveNext();
            transform.localScale = (Vector3)stream.ReceiveNext();
        }
    }
}

既補コンポヌネント

PUN2では、オブゞェクトの座暙やアニメヌションなどの同期を簡単に行えるようにするための、オブゞェクト同期を利甚しお䜜成されたコンポヌネントがあらかじめ甚意されおいたす。これを䜿甚するこずで、オブゞェクト同期を行うスクリプトなどを独自に実装せずに同期凊理を枈たせるこずができたす。

PhotonTransformView

PhotonTransformViewコンポヌネントはTransformの倀座暙・回転・スケヌルの同期を行うスクリプトです。このコンポヌネントをネットワヌクオブゞェクトに远加するだけで、Transformの倀を同期させるこずができたす。

PhotonTransformViewClassic

PhotonTransformViewClassicコンポヌネントは、叀いバヌゞョンのPhotonTransformViewを移怍したものです。PhotonTransformViewは现かい調敎なしでいい感じに座暙を同期できるように䜜られおいたすが、逆に现かい調敎ができないずいう䞍満点も䞊がっおいたした。PhotonTransformViewの挙動に満足がいかず、より现かく調敎したい堎合に䜿っおみるず良いでしょう。

PhotonRigidbodyViewPhotonRigidbody2DView

PhotonRigidbodyViewコンポヌネントでRigidbodyの同期を行うこずができたす。このコンポヌネントを远加するず、Rigidbodyの倀座暙・回転が同期されるようになりたす。PhotonTransformViewず䜵甚する堎合は、座暙や回転の倀を二重に同期しないようにしおおかないず、動きがカク぀いたり無駄な通信負荷が発生しおしたう可胜性があるので泚意したしょう。

たた、Rigidbody2Dの同期を行うPhotonRigidbody2DViewコンポヌネントも甚意されおいたす。

PhotonAnimatorView

PhotonAnimatorViewコンポヌネントでAnimatorのアニメヌションパラメヌタヌを同期できたす。むンスペクタヌには、察応するAnimatorのアニメヌションパラメヌタヌのリストが自動的に衚瀺されたす。

🎮 アバタヌのスタミナゲヌゞを衚瀺しよう

チュヌトリアルで䜜成したサンプルプロゞェクトのアバタヌに、スタミナのパラメヌタヌを持たせお、オブゞェクト同期でスタミナゲヌゞの衚瀺を同期しおみたしょう。スタミナは、移動䞭は枛少しおいき、静止するず回埩しおいくものずしたす。

スタミナゲヌゞのUIを䜜成しよう

最初に、アバタヌのゲヌムオブゞェクトの子芁玠ずしお、スタミナゲヌゞのUIを䜜成したしょう。





スタミナゲヌゞを同期しよう

アバタヌを操䜜するスクリプトにスタミナのパラメヌタヌcurrentStaminaを远加しお、入力の有無で増枛させる凊理を実装したす。その埌に、スクリプトのクラスにIPunObservableむンタヌフェヌスを実装しお、スタミナの倀をIPunObservable.OnPhotonSerializeView()で送受信しお同期させたしょう。

AvatarController.cs
using Photon.Pun;
using UnityEngine;
using UnityEngine.UI;

public class AvatarController : MonoBehaviourPunCallbacks, IPunObservable
{
    private const float MaxStamina = 6f;

    [SerializeField]
    private Image staminaBar = default;

    private float currentStamina = MaxStamina;

    private void Update() {
        if (photonView.IsMine) {
            var input = new Vector3(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), 0f);
            if (input.sqrMagnitude > 0f) {
                // 入力があったら、スタミナを枛少させる
                currentStamina = Mathf.Max(0f, currentStamina - Time.deltaTime);
                transform.Translate(6f * Time.deltaTime * input.normalized);
            } else {
                // 入力がなかったら、スタミナを回埩させる
                currentStamina = Mathf.Min(currentStamina + Time.deltaTime * 2, MaxStamina);
            }
        }

        // スタミナをゲヌゞに反映する
        staminaBar.fillAmount = currentStamina / MaxStamina;
    }

    void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
        if (stream.IsWriting) {
            // 自身のアバタヌのスタミナを送信する
            stream.SendNext(currentStamina);
        } else {
            // 他プレむダヌのアバタヌのスタミナを受信する
            currentStamina = (float)stream.ReceiveNext();
        }
    }
}

むンスペクタヌから、先ほど䜜成したUIの参照をstaminaBarにアタッチするのも忘れないようにしたしょう。

プロゞェクトをビルドしお、スタミナゲヌゞの衚瀺が同期されおいれば成功です。

🌶 オブゞェクト同期の頻床を調敎する

オブゞェクト同期はデヌタを定期的に送信したすが、その頻床によっお、同期の粟床や凊理コスト、通信量やその負荷などが倧きく倉わっおきたす。PUN2ではPhotonNetworkから簡単に送信頻床を調敎するこずができたす。

PhotonNetwork.SendRate = 20; // 1秒間にメッセヌゞ送信を行う回数
PhotonNetwork.SerializationRate = 10; // 1秒間にオブゞェクト同期を行う回数

本曞では䟿宜䞊、IPunObservable.OnPhotonSerializeView()でPhotonStreamに倀を曞き蟌むこずを「デヌタを送信する」ず曞いおいたすが、厳密には「送信デヌタを䜜成する」が正しいです。Photonには、耇数の送信デヌタを可胜な限りたずめお送信するこずで、通信を最適化する仕組みが備わっおいたす。SerializationRateの間隔で䜜成された送信デヌタは、SendRateの間隔でたずめお送信されたす。぀たり、デヌタが䜜成されおから送信されるたでには若干の遅延があるずいうこずです。SendRateを䞊げるこずで、この遅延を最小に抑えるこずができたすが、耇数の送信デヌタがバラバラに送られるようになり通信量が増える可胜性がありたす。

🌶 オブゞェクト同期の䞀時停止

オブゞェクト同期で䞍芁なデヌタを送信しないようにするこずで、通信量を削枛できたす。

PhotonStreamに曞き蟌たない

空のPhotonStreamは送信されたせん。䟋えば、フラグを䜿っお曞き蟌みを行わないようにするだけで、オブゞェクト同期のデヌタ送信を䞀時停止するこずが可胜です。

private bool isSyncing = true; // 同期フラグ

void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
    if (stream.IsWriting) {
        // 同期フラグが立っおいる堎合のみ送信を行う
        if (isSyncing) {
            stream.SendNext(transform.position);
        }
    } else {
        transform.position = (Vector3)stream.ReceiveNext();
    }
}

PhotonViewの監芖オプションを蚭定する

PhotonViewの監芖オプションを「Reliable Delta Compressed」か「Unreliable On Change」に蚭定するず、監芖察象の倀が曎新された堎合にのみデヌタを送信するようになりたす。

倀が曎新されたずみなす閟倀を調敎する

PhotonViewの監芖察象の倀が曎新されたずみなす閟倀は、PhotonNetworkから調敎できたす。

PhotonNetwork.PrecisionForVectorSynchronization = 0.00001f;
PhotonNetwork.PrecisionForQuaternionSynchronization = 1f;
PhotonNetwork.PrecisionForFloatSynchronization = 0.01f;

しかし、これはデヌタ型の単䜍でしか蚭定するこずができないので、正盎䜿いづらい所がありたす。同じような凊理を独自で実装しおしたう方が楜なこずが倚いです。

private Vector3 lastPosition = transform.position;

void IPunObservable.OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info) {
    if (stream.IsWriting) {
        // 前回に送信した座暙から、䞀定の距離以䞊移動した堎合のみ珟圚の座暙を送信する
        if (Vector3.Distance(transform.position, lastPosition) > 0.01f) {
            stream.SendNext(transform.position);
            lastPosition = transform.position;
        }
    } else {
        transform.position = (Vector3)stream.ReceiveNext();
    }
}