「自身が所有権を持つプレイヤー」だけを動かすようにする
目次
概要
プレイヤーキャラクタを操作している箇所を探す
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エディタの両方を使って、マルチプレイ動作確認を行なう
改めて 複数クライアントの実行 で説明している手順で、もう一度実行処理をしてみましょう。
見事、クライアント間での同期処理を実装することが出来ましたね!