「自身が所有権を持つプレイヤー」だけを動かすようにする

目次

  概要
  プレイヤーキャラクタを操作している箇所を探す
  PlayerにmonobitView.isMineを適用する
  Recorderにも同様の処置を適用する
  Actionにも同様の処置を適用する
  DamageReceiverにも同様の処置を適用する
  Player_Shootにも同様の処置を適用する
  Karmaにも同様の処置を適用する
  もう一度、複数クライアントで動作させてみる


概要

なぜ「複数クライアントの動作」に不具合が出てしまったのか?

  ここが「オフラインゲーム開発」から「オンラインゲーム開発」に移行する上での大きな障壁の1つなのですが。
  前節までの流れで、単体テストで上手く動いていましたが、複数クライアントでのテストでは不具合が発生してしまいました。
  なぜでしょう?

  この原因については、以下の囲みに記述します。
  (難しい理屈ですので、ここは理解できなくても構いません。)
単純に理由を言えば、「全員のプレイヤーキャラクタを、プレイヤー全員で動かそうとしているから」です。

例えば、オフラインゲームを開発していて、「上を押したら前進する」という処理をプレイヤーキャラクタに適用させるとします。
それが「オフラインゲーム」のままであれば、そのまま実行すれば動きます。

しかし「オンラインゲームに移行する」のであれば、自分のプレイヤーキャラクタも、相手のプレイヤーキャラクタも、誰かが「上を押したら前進」します。
結果、ネットワーク上の全ての「プレイヤーキャラクタ」が、各々のクライアントにより「同じ動き」をするようになってしまうのです。

ここまでが基本的な理由ですが、実際のところ、先のサンプルを見ればわかる通り、「同じ動き」にはなっていません。

それは後述の「オブジェクトの所有権」にも関連することなのですが、MUNで提供されている
   ・MonobitTransformView - 位置・姿勢・倍率の同期
   ・MonobitAnimatorView - アニメーションの同期
では、内部的に「自身が所有権を持つキャラクタには同期を適用させない」という処置を適用しているからです。

結果として
   ・自身が所有権を持つプレイヤーは、他クライアントの影響を受けずに動かすことが出来る
   ・他クライアントが所有権を持つプレイヤーは、同期を適用しようとするが、プレイヤーのキー入力操作の影響を受ける。
     → 結果、位置はそのまま同期される(が、プレイヤー操作により微妙にズレる)。
       アニメーションは自身のキー操作に依存するため、自身のクライアント操作の影響を受けてしまう。
という動作をしてしまっています。

ネットワーク上のGameObjectは、MUNサーバから「オブジェクトの所有権」が与えられる

  理由は分からなくとも構いませんが、対策方法は知っておく必要があります。

  ネットワーク上の GameObject には、(厳密にいえば MonobitNetwork.Instantiate() を実行した瞬間に)
  MUNサーバから「オブジェクトの所有権」が与えられます。
  簡単に言えば「そのオブジェクトをプレイヤー自身が動かして良いかどうかの権限(フラグ)」です。

  現在プレイヤーキャラクタを動かしている箇所では、まだ、その所有権(フラグ)をもとにした「操作の可否」を設定していません。
  この「操作の可否」をプレイヤー制御のループ系メソッド(Update() や OnGUI()など)に組み込めば、原則的に今回の不具合は解決します!

  実際にどう対策するのか、具体的に触れていきましょう。


プレイヤーキャラクタを操作している箇所を探す

プレイヤーキャラクタのプレハブから探る

  まずは、プレイヤーキャラクタのプレハブを見てみましょう。
  Assets/Resources フォルダを開き、 Player.prefab の右端にある「>」のマークをクリックします。
  すると、player.prefab の中身が展開されます。
  展開されたデータのうち、「Dude」と書かれたオブジェクトを選択します。

プレハブに登録されているコンポーネントから探る

  「Dude」に登録されているコンポーネントが Inspector に表示されます。
  色々なプレイヤー制御スクリプトが用意されているようです。


PlayerにmonobitView.isMineを適用する

「Dude」のInspectorから「Player」を開く

  まずは Player のスクリプトをダブルクリックして開いてみましょう。

該当するクラスについて、MonobitEngine.MonoBehaviour を継承するように変更する

  では実際の対策方法を試してみましょう。
  まずは、該当するクラスについて、MonobitEngine.MonoBehaviour を継承するように変更します。

  Player.cs の 10 行目を、以下のように書き換えます。
public class Player : MonobitEngine.MonoBehaviour {
  オブジェクト所有権のフラグ判定を行なうためには、以下の条件を満たす必要があります。
1) そのスクリプトが登録されているオブジェクトに、MonobitView がアタッチされていること。
2) そのスクリプト自身が MonobitEngine.MonoBehaviour を継承していること。
  1) の条件は既に満たしています。2) の条件は上述による満たしました。これで準備完了です。

該当するクラスの Update() について、monobitView.isMineによる実行可否判定を入れる

  更に、Player.cs の 28 行目から、以下のコードを記述します。
		// オブジェクト所有権を所持しなければ実行しない
		if( !monobitView.isMine )
		{
			return;
		}
  monobitView.isMine が、「自身が所有権を持つオブジェクトか?」を示す、オブジェクト所有権のフラグです。
monobitView は MonobitEngine.MonoBehaviour に登録されたプロパティで、
スクリプト自身が登録されているオブジェクトに追加されている MonobitView コンポーネントを取得します。
  単純に、「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。これだけです。


Recorderにも同様の処置を適用する

「Dude」のInspector から「Recorder」を選択する

  同様の処置を他のスクリプトにも適用させます。
  「Dude」のInspector から、「Recorder」をダブルクリックしましょう。

該当するクラスについて、MonobitEngine.MonoBehaviour を継承するように変更する

  Recorder.cs の 8 行目を、以下のように書き換えます。
public class Recorder : MonobitEngine.MonoBehaviour {
  Player.csと同様、MonobitEngine.MonoBehaviour を継承するようにします。

該当するクラスの OnGUI() について、monobitView.isMineによる実行可否判定を入れる

  更に、Recorder.cs の 35 行目から、以下のコードを記述します。
		// オブジェクト所有権を所持しなければ実行しない
		if( !monobitView.isMine )
		{
			return;
		}
  ここも「所有権を持たないキャラクタは、OnGUI() を処理させない」というロジックを適用させます。

該当するクラスの Update() について、monobitView.isMineによる実行可否判定を入れる

  Recorder.cs の 113 行目についても、以下のコードを記述します。
		// オブジェクト所有権を所持しなければ実行しない
		if( !monobitView.isMine )
		{
			return;
		}
  ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。


Actionにも同様の処置を適用する

「Dude」のInspector から「Action」を選択する

  「Dude」のInspector から、「Action」をダブルクリックしましょう。

該当するクラスについて、MonobitEngine.MonoBehaviour を継承するように変更する

  Action.cs の 12 行目を、以下のように書き換えます。
public class Action : MonobitEngine.MonoBehaviour {
  MonobitEngine.MonoBehaviour を継承するようにします。

該当するクラスの Update() について、monobitView.isMineによる実行可否判定を入れる

  Action.cs の 37 行目についても、以下のコードを記述します。
		// オブジェクト所有権を所持しなければ実行しない
		if( !monobitView.isMine )
		{
			return;
		}
  ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。


DamageReceiverにも同様の処置を適用する

「Dude」のInspector から「DamageReceiver」を選択する

  「Dude」のInspector から、「DamageReceiver」をダブルクリックしましょう。

該当するクラスについて、MonobitEngine.MonoBehaviour を継承するように変更する

  DamageReceiver.cs の 9 行目を、以下のように書き換えます。
public class DamageReceiver : MonobitEngine.MonoBehaviour
  MonobitEngine.MonoBehaviour を継承するようにします。

該当するクラスの Update() について、monobitView.isMineによる実行可否判定を入れる

  DamageReceiver.cs の 24 行目についても、以下のコードを記述します。
		// オブジェクト所有権を所持しなければ実行しない
		if( !monobitView.isMine )
		{
			return;
		}
  ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。


Player_Shootにも同様の処置を適用する

「Dude」のInspector から「Player_Shoot」を選択する

  「Dude」のInspector から、「Player_Shoot」をダブルクリックしましょう。

該当するクラスについて、MonobitEngine.MonoBehaviour を継承するように変更する

  Player_Shoot.cs の 8 行目を、以下のように書き換えます。
public class Player_Shoot : MonobitEngine.MonoBehaviour {
  MonobitEngine.MonoBehaviour を継承するようにします。

該当するクラスの Update() について、monobitView.isMineによる実行可否判定を入れる

  Player_Shoot.cs の 28 行目についても、以下のコードを記述します。
		// オブジェクト所有権を所持しなければ実行しない
		if( !monobitView.isMine )
		{
			return;
		}
  ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。


Karmaにも同様の処置を適用する

「Dude」のInspector から「Karma」を選択する

  「Dude」のInspector から、「Karma」をダブルクリックしましょう。

該当するクラスについて、MonobitEngine.MonoBehaviour を継承するように変更する

  Karma.cs の 8 行目を、以下のように書き換えます。
public class Karma : MonobitEngine.MonoBehaviour
  MonobitEngine.MonoBehaviour を継承するようにします。

該当するクラスの Update() について、monobitView.isMineによる実行可否判定を入れる

  Karma.cs の 12 行目についても、以下のコードを記述します。
		// オブジェクト所有権を所持しなければ実行しない
		if( !monobitView.isMine )
		{
			return;
		}
  ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。


複数クライアントで動作させてみる

改めて、実行バイナリとUnityエディタの両方を使って、マルチプレイ動作確認を行なう

  改めて 複数クライアントの実行 で説明している手順で、もう一度実行処理をしてみましょう。
  見事、クライアント間での同期処理を実装することが出来ましたね!