This document is about: PUN 2
SWITCH TO

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

7 - Player Networking

このセクションでは、「プレイヤー」のプレハブを修正します。
最初に作成したプレイヤーはそのままでも動作しますが、PUN環境内で問題なく動作するように修正を加えます。
行う修正はわずかですか、非常に重要です。
したがって、このセクションは非常に重要です。

Transformの同期

キャラクターの位置と回転を同期し、他のコンピューター上でそのプレイヤーが同様に動作するようにします。

Transcriptコンポーネントは独自のスクリプトで直接監視できますが、ネットワークレイテンシーやデータの有効性が原因で多くの問題が発生する可能性があります。
この作業を簡単にするために、TransformコンポーネントとPhotonViewを仲介する[Photon Transform View]コンポーネントを使用します。
このコンポーネントが難しい作業を行ってくれます。

  1. PhotonTransformViewを'My Robot Kyle'プレハブに追加します。
  2. PhotonTransformViewをヘッダータイトルからPhotonViewコンポーネントの最初のObservableコンポーネントエントリにドラッグしてください。
  3. PhotonTransformViewSynchronize Positionにチェックを入れます。
  4. SynchronizeRotationにチェックを入れます。

Animatorの同期

PhotonAnimatorViewを使用するとネットワーク設定が簡単に行えます。
どのレイヤーウェイトかパラメーターを同期するか定義できるようになります。
レイヤーウェイトはゲーム中に変更された場合、同期される必要があり、場合によっては全く同期する必要がありません。
パラメータも同様です。
場合によっては他の要因からアニメーター値を引き出すことも可能です。
これを示すいい例はスピード値です。必ずしもこの値を正確に同期する必要はありませんが、同期した位置アップデートからその値を見積もることができます。
できる限り、同期するパラメータは最小限にしてください。

  1. My Robot KyleプレハブにPhotonAnimatorViewを追加します。
  2. PhotonAnimatorViewをヘッダータイトルから、PhotonViewコンポーネントの新しいObservableコンポーネントエントリへドラッグします。
  3. Synchronized Parametersで、SpeedDiscreteに設定します。
  4. DirectionDiscreteに設定します。
  5. JumpDiscreteに設定します。
  6. HiDisabledに設定します。
Observe PhotonAnimatorView
Observe PhotonAnimatorView

それぞれの値は無効化もできますし、個別的または連続的に同期させることもできます
今回のケースではHiパラメータを使用していないので、これを無効化してトラフィックを減少させます。

個別同期は値が一秒間に10回送られることを意味します。(OnPhotonSerializeView内で)
受信側のクライアントは、ローカルのAnimatorに値を渡します。

連続同期はPhotonAnimatorViewが全てのフレームを実行することを意味します。
OnPhotonSerializeViewが呼び出されると(毎秒10回)、最後の呼び出し以降に記録された値が一緒に送信されます。
受信側のクライアントはスムーズな移行を維持するために値を順番に適用します。
このモードはスムーズですが、効果を実現するためにより多くのデータを送信します。

ユーザー入力の管理

ネットワーク上のユーザー管理の重要な側面は、全てのプレイヤーに対して同じプレハブが初期化され、そのうちの1つだけがコンピューターの前でプレイしているユーザーを表し、
他の全てのインスタンスは他のコンピューターでプレイしている他のユーザーを表すという点です。
この点を念頭においた上で、まず最初の関門は「入力の管理」です。
どうすれば入力を1つのインスタンスにのみ有効にし、そしてそれが正しいインスタンスだと判断できるのでしょうか。
ここでisMineコンセプトが必要になります。

少し前に作成したPlayerAnimatorManagerスクリプトを編集しましょう。
現在の形式では、このスクリプトは区別を認識できないため実装します。

  1. スクリプトPlayerAnimatorManagerを開きます。

  2. PlayerAnimatorManagerクラスをMonoBehaviourからMonoBehaviourPunに変更します。これによってphotonViewコンポーネントが公開されます。

  3. Update()コールの先頭に、以下を挿入します。

    C#

    if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
    {
        return;
    }
    
  4. スクリプトPlayerAnimatorManagerを保存します。

インスタンスが「クライアント」アプリケーションによって制御されている場合、photonView.IsMineはtrueになり、このインスタンスはこのコンピュータのアプリケーション内でプレイしているユーザーを表します。
falseの場合、何もせず、ただPhotonViewコンポーネントに依存し、以前に設定したtransformとanimatorコンポーネントを同期します。

それにしても、どうしてif文の中でPhotonNetwork.IsConnected == trueを強制するのでしょうか?それは、接続していない状態の開発段階でプレハブをテストできるようにするためです。
例えばダミーシーンで、ネットワーキング機能そのものとは関係のないコードを作成し検証する場合などです。
この追加の数式があれば、接続していなくても入力が使用できるようになります。とても単純なトリックですが、開発段階でのワークフローを格段に改善するでしょう。

カメラの制御

これは入力と同様ですが、プレイヤーには一つのビューしかないので、CameraWorkスクリプトは他のプレイヤーではなく、ローカルプレイヤーのみを追従する必要があります。
このためCameraWorkスクリプトはいつ追従するかタイミングを定義する機能が備えられています。

CameraWorkコンポーネントを制御するため、PlayerManagerスクリプトを修正しましょう。

  1. PlayerManagerスクリプトを開きます。
  2. 以下のコードをAwake()Update()の間に挿入します。

C#

/// <summary>
/// MonoBehaviour method called on GameObject by Unity during initialization phase.
/// </summary>
void Start()
{
  CameraWork _cameraWork = this.gameObject.GetComponent<CameraWork>();

  if (_cameraWork != null)
  {
    if (photonView.IsMine)
    {
      _cameraWork.OnStartFollowing();
    }
  }
  else
  {
    Debug.LogError("<Color=Red><a>Missing</a></Color> CameraWork Component on playerPrefab.", this);
  }
}
  1. スクリプトPlayerManagerを保存します。

まず、CameraWorkコンポーネントを取得します。もし取得しない場合にはエラーが記録されます。
そして、photonView.IsMinetrueの場合にはこのインスタンスを追従する必要があるので_cameraWork.OnStartFollowing()を呼び出し、シーンでカメラがそのインスタンスを効果的に追従させます。

その他全てのプレイヤーインスタンスのphotonView.IsMinefalseに設定されるので、各々の_cameraWorkは何もしません。

作動させるために、最後に以下の変更を加えます。

  1. プレハブ My Robot KyleCameraWorkコンポーネントでFollow on Startを無効化します。
CameraWork FollowOnStart Off
CameraWork FollowOnStartをオフ

これで効果的に上記の_cameraWork.OnStartFollowing()を呼ぶスクリプトPlayerManagerへプレイヤーを追従するロジックが効果的に引き継がれます。

ビーム射撃の制御

射撃もまた、上記で公開された入力の原理に従うのでphotonView.IsMinetrueである場合のみ動作する必要があります。

  1. スクリプトPlayerManagerを開きます。

  2. 入力処理呼出をif文で囲みます。

    C#

    if (photonView.IsMine)
    {
        ProcessInputs ();
    }
    
  3. スクリプトPlayerManagerを保存します。

しかし、これをテストする際には、ローカルプレイヤーの射撃しか確認できません。
他のインスタンスがいつ射撃するのか確認するべきです。ネットワーク上で射撃を同期するメカニズムが必要です。
これを行うには、IsFiringのブーリアン値を手動で同期します。これまでは、PhotonTransformViewPhotonAnimatorViewを使って変数を内部的に同期することができました。
Unity Inspectorで公開されていたもののみの調整で済みましたが、今回必要なのはこのゲーム特有のものなので、手動でおこなう必要があります。

  1. スクリプトPlayerManagerを開きます。
  2. IPunObservableを実装します。

C#

public class PlayerManager : MonoBehaviourPunCallbacks, IPunObservable
{
    #region IPunObservable implementation

    public void OnPhotonSerializeView(PhotonStream stream, PhotonMessageInfo info)
    {
    }

    #endregion
}
  1. IPunObservable.OnPhotonSerializeViewの中に以下のコードを追加します。

    C#

    if (stream.IsWriting)
    {
        // このプレイヤーを所有しています。データをほかのものに送ります。
        stream.SendNext(IsFiring);
    }
    else
    {
        // ネットワークプレイヤー。データ受信
        this.IsFiring = (bool)stream.ReceiveNext();
    }
    
  2. スクリプトPlayerManagerを保存します。

  3. Unityエディタに戻り、アセット内のMy Robot Kyleプレハブを選択して、 PhotonViewコンポーネントに監視エントリを追加し、PlayerManagerコンポーネントをそのエントリにドラッグします。

Observe PhotonAnimatorView
Observe PlayerManager

上記の最後の設定を行わないと、PhotonViewに監視されないためIPunObservable.OnPhotonSerializeViewは呼ばれません。

このIPunObservable.OnPhotonSerializeViewでは変数streamが手渡されます。この変数がネットワーク上で送信され、またデータを読み書きする場合に呼び出します。
データはローカルプレイヤーであるとき(photonView.IsMine == true)のみ書き込みできます。その他の場合は、読み取りを行います。

ストリームクラスには何を行うべきか把握しているヘルパーがあるので、stream.isWritingに依存すれば現在のインスタンスケースの内容を予測できます。

データの書き込みを行うのであれば、stream.SendNext()を使用してIsFiring値を追加します。このメソッドはデータのシリアライゼーションの難しさを軽減する非常に便利なメソッドです。
データの読み込みを行うのであれば、stream.ReceiveNext()を使用します。

体力の同期

ネットワークのためのプレイヤー機能のアップデートを完了するには、体力値を同期してプレイヤーの各インスタンスが適正な体力値となるようにします。
上記で説明したIsFiring値と全く同じ原理です。

  1. スクリプトPlayerManagerを開きます。

  2. IPunObservable.OnPhotonSerializeView内で、IsFiring 変数をSendNextおよびReceiveNextした後に、Healthにも同じ処理を行います。

    C#

    if (stream.IsWriting)
    {
        //このプレイヤーを所有しています。データをほかのものに送ります。
        stream.SendNext(IsFiring);
        stream.SendNext(Health);
    }
    else
    {
        // ネットワークプレイヤー。データ受信
        this.IsFiring = (bool)stream.ReceiveNext();
        this.Health = (float)stream.ReceiveNext();
    }
    
  3. スクリプトPlayerManagerを保存します。

このHealth変数の同期を行うシナリオで必要な処理は、上記で完了します。

次に進む
前に戻る

Back to top