7-プレイヤーネットワーキング
このセクションでは「プレイヤー」のプレハブを修正します。
最初に作成したプレイヤーはそのままの状態でも動作しますが、PUN環境内で問題なく動作するように修正を加えます。
行う修正はわずかですが、非常に重要です。
したがって、このセクションは非常に重要です。
Transformの同期
キャラクターの位置と回転を同期し、プレイヤーが移動する際に他のコンピューター上でそのプレイヤーが同様に動作するようにします。
Transcriptコンポーネントは独自のスクリプトで直接監視できますが、ネットワークレイテンシーやデータの有効性が原因で多くの問題が発生する可能性があります。
この作業を簡単にするために、TransformコンポーネントとPhotonViewを仲介する[Photon Transform View]コンポーネントを使用します。
このコンポーネントが難しい作業を行ってくれます。
- PhotonTransformViewを'My Robot Kyle'プレハブに追加します。
- PhotonTransformViewをヘッダータイトルから PhotonViewコンポーネントの最初のObservableコンポーネントエントリーにドラッグします。
- PhotonTransformViewの
Synchronize Position
にチェックを入れます。 Synchronize Position
で「Lerp Value for Interpolation Option」を選択します。Lerp Speed
を10に設定します(大きくなるにつれて、より速く追いつきます)。SynchronizeRotation
にチェックを入れます。
Animatorの同期
PhotonAnimatorView を使用するとネットワークの設定が簡単におこなえます。
どのレイヤーウェイトやパラメーターを同期するか定義することができます。
レイヤーウェイトは、ゲーム中に変更された場合にのみ同期する必要があり、場合によっては全く同期する必要がありません。
パラメーターも同様です。
場合によっては他の要因からアニメーター値を引き出すことも可能です。
これを示す良い例はスピード値です。必ずしもこの値を正確に同期する必要はありませんが、同期した位置アップデートからその値を見積もることができます。
できる限り、同期するパラメータ数は最小限にしてください。
- PhotonAnimatorViewを
My Robot Kyle
プレハブに追加します。 - PhotonAnimatorViewをヘッダータイトルから、PhotonViewコンポーネントの監視可能なコンポーネントエントリーにドラッグします。
- Synchronized Parametersで、
Speed
をDiscrete
に設定します。 Direction
をDiscrete
に設定します。Jump
をDiscrete
に設定します。Hi
をDisabled
に設定します。
それぞれの値は無効化もできますし、個別的または連続的に同期させることもできます。
今回はHi
パラメータを使用していないので、無効にしてトラフィックを減少させます。
個別同期
とは、値が1秒間に10回送信されることを意味します(OnPhotonSerializeView
内で)。
受信側のクライアントは、その値をローカルのAnimatorに渡します。
'連続同期'は、PhotonAnimatorView
がすべてのフレームを実行することを意味します。
OnPhotonSerializeView
が呼び出されると(毎秒10回)、最後の呼び出し以降に記録された値が一緒に送信されます。
受信側のクライアントは、スムーズな遷移を保持するために値を順番に適用します。
このモードはよりスムーズですが、効果を実現するためにより多くのデータを送信します。
ユーザー入力の管理
ネットワーク上でのユーザー管理の重要な側面は、すべてのプレイヤーに対して同じプレハブがインスタンス化され、
そのうちの1つだけがコンピューターの前でプレイしているユーザを表し、他のインスタンスは他のコンピューターでプレイしているユーザーを表すという点です。
この点を念頭に置いたうえで最初に取り組む課題は「入力の管理」です。
どうすれば1つのインスタンスのみで入力を有効にし、またどれが適切なインスタンスかを把握できるでしょうか。
ここでisMine
コンセプトが必要になります。
作成済みのPlayerAnimatorManager
スクリプトを編集しましょう。
現時点の形式では、このスクリプトは区別を認識できないため、実装してみましょう。
スクリプト
PlayerAnimatorManager
を開きます。PlayerAnimatorManager
クラスをMonoBehaviourからPhoton.MonoBehaviourに変換します。これによって、photonView
コンポーネントが公開されます。Update()
コールの先頭に以下を挿入します。C#
if (photonView.isMine == false && PhotonNetwork.connected == true) { return; }
スクリプト
PlayerAnimatorManager
を保存します。
インスタンスが'クライアント'アプリケーションによって管理されている場合にはPhotonView.isMineはtrueになり、このインスタンスはこのコンピュータのこのアプリケーション内でプレイしているユーザーを表します。
falseの場合は何もせずにPhotonViewコンポーネントに依存し、以前に設定したTransformとComponentを同期します。
ではなぜ、if文でPhotonNetwork.connected == trueを強制するのでしょうか?それは、接続していない状態で開発中にこのプレハブをテストできるようにするためです。
たとえば、ダミーシーンでネットワーク機能とは関係のないコードを作成し、検証する場合などです。
したがって、この追加の式を使用すると、接続されていない場合でも入力を使用できます。 これは非常に単純なトリックですが、開発中のワークフローを大幅に改善します。
カメラの管理
入力と同様、プレイヤーはゲームに対してビューを1つしか持たないので、CameraWork
スクリプトは他のプレイヤーではなくローカルプレイヤーのみを追従する必要があります。
そのため CameraWork
スクリプトには、いつ追従するかを定義する機能を備えています。
CameraWork
コンポーネントを制御するために PlayerManager
スクリプトを修正します。
PlayerManager
スクリプトを開きます。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); } }
スクリプト
PlayerManager
を保存します。
まず、 CameraWork
コンポーネントを取得します。 見つからない場合、エラーが記録されます。
次に photonView.isMine
がtrueの場合は、このインスタンスに追従する必要があるため _cameraWork.OnStartFollowing()
を呼び出し、シーン内のそのインスタンスをカメラに効果的に追従させます。
他のすべてのプレイヤーインスタンスは、photonView.isMine
がfalseに設定されているため、それぞれの_cameraWork
は何もおこないません。
作動させるには、最後に以下の変更をおこないます:
プレハブMy Robot Kyle
のCameraWork
コンポーネントで、Follow on Start
を非有効化します。
これによって、上記の_cameraWork.OnStartFollowing()
を呼ぶスクリプトPlayerManager
へプレイヤーを追従するロジックが効果的に引き継がれます。
ビーム射撃の管理
射撃も上記で公開された入力の原理に従うので、 photonView.isMine
がtrue
である場合のみ動作する必要があります。
スクリプト
PlayerManager
を開きます。入力処理呼び出しをif文で囲む。
C#
if (photonView.isMine) { ProcessInputs (); }
スクリプト
PlayerManager
を保存します。
しかし、これをテストする際にはローカルプレイヤーの射撃しか確認できません。
他のインスタンスがいつ射撃するかを確認する必要があります。
ネットワーク上で射撃を同期させるメカニズムが必要です。これを行うには、IsFiringブール値を手動で同期させます。
これまでは、PhotonTransformViewとPhotonAnimatorViewを使って変数を内部的に同期させることができました。Unity Inspectorで公開されていたもののみの調整で済みましたが、今回必要なのはこのゲーム特有のものなので、手動でおこなう必要があります。
スクリプト
PlayerManager
を開きます。IPunObservable
を実装します。Inside
IPunObservable.OnPhotonSerializeView
add the following codeIPunObservable.OnPhotonSerializeView
内で以下のコードを追加しますC#
if (stream.isWriting) { // We own this player: send the others our data stream.SendNext(IsFiring); } else { // Network player, receive data this.IsFiring = (bool)stream.ReceiveNext(); }
スクリプト
PlayerManager
を保存します。Unityエディターに戻り、アセット内の
My Robot Kyle
プレハブを選択してPhotonViewコンポーネントに監視エントリーを追加します。その後、そのエントリーにPlayerManager
をドラッグします。
上記の最後の設定をおこなわないと、PhotonViewに監視されないためIPunObservable.OnPhotonSerializeView
は呼ばれません。
このIPunObservable.OnPhotonSerializeView
メソッドでは、変数stream
が渡されます。この変数はネットワーク上で送信され、またデータを読み書きする場合に呼び出します。
localPlayer (PhotonView.isMine == true
)の場合のみ書き込み可能で、その他の場合は読み込みをおこないます。
ストリームクラスには何をおこなうべきか把握しているヘルパーがあるため、stream.isWriting
に依存すれば現在のインスタンスケースの内容を予測できます。
データの書き込みを予定している場合、 stream.SendNext()
を使用して、データのストリームにIIsFiring
値を追加します。このメソッドは、データのシリアル化の難しさを軽減する非常に便利なメソッドです。
読み込みをおこなう場合は、stream.ReceiveNext()
を使用します。
体力の同期
ネットワーキングのためのプレイヤー機能のアップデートを完了するには、体力値を同期してプレイヤーの各インスタンスが適切な値となるようにします。
これは、上記で説明したIsFiring
値と全く同じ原理です。
スクリプト
PlayerManager
を開きます。IPunObservable.OnPhotonSerializeView
でIsFiring
変数をSendNext
およびReceiveNext
した後に、Health
にも同じ処理をおこないます。C#
if (stream.isWriting) { // We own this player: send the others our data stream.SendNext(IsFiring); stream.SendNext(Health); } else { // Network player, receive data this.IsFiring = (bool)stream.ReceiveNext(); this.Health = (float)stream.ReceiveNext(); }
スクリプト
PlayerManager
を保存します。
Health
変数の同期をおこなうシナリオで必要な処理は、上記で完了します。