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