7-プレイヤーネットワーキング
このセクションでは「プレイヤー」のプレハブを修正します。
最初に作成したプレイヤーはそのままの状態でも動作しますが、PUN環境内で問題なく動作するように修正を加えます。
行う修正はわずかですが、非常に重要です。
したがって、このセクションは非常に重要です。
Transformの同期
キャラクターの位置と回転を同期し、プレイヤーが移動する際に他のコンピューター上でそのプレイヤーが同様に動作するようにします。
Transcriptコンポーネントは独自のスクリプトで直接監視できますが、ネットワークレイテンシーやデータの有効性が原因で多くの問題が発生する可能性があります。
この作業を簡単にするために、TransformコンポーネントとPhotonViewを仲介する[Photon Transform View]コンポーネントを使用します。
このコンポーネントが難しい作業を行ってくれます。
- PhotonTransformViewを'My Robot Kyle'プレハブに追加します。
- PhotonTransformViewをヘッダータイトルから PhotonViewコンポーネントの最初のObservableコンポーネントエントリーにドラッグします。
PhotonTransformViewをドラッグアンドドロップ - 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を実装します。
IPunObservableの実装 Inside
IPunObservable.OnPhotonSerializeViewadd 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をドラッグします。
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変数の同期をおこなうシナリオで必要な処理は、上記で完了します。