「自身が所有権を持つプレイヤー」だけを動かすようにする
目次
概要
プレイヤーキャラクタを操作している箇所を探す
PlayerにMonobitEngine.MonobitView.isMineを適用する
Recorderにも同様の処置を適用する
Actionにも同様の処置を適用する
DamageReceiverにも同様の処置を適用する
Player_Shootにも同様の処置を適用する
Karmaにも同様の処置を適用する
変更したプログラムの保存
なぜ「複数クライアントの動作」に不具合が出てしまったのか?
ここが「オフラインゲーム開発」から「オンラインゲーム開発」に移行する上での大きな障壁の1つなのですが。
前節までの流れで、単体テストで上手く動いていましたが、複数クライアントでのテストでは不具合が発生してしまいました。
なぜでしょう?
この原因については、以下の折り畳み箇所に記述します。
(難しい理屈ですので、ここは理解できなくても構いません。)
ネットワーク上のGameObjectは、MUNサーバから「オブジェクトの所有権」が与えられる
理由は分からなくとも構いませんが、対策方法は知っておく必要があります。
ネットワーク上の GameObject には、(厳密にいえば MonobitNetwork.Instantiate() を実行した瞬間に)
MUNサーバから「オブジェクトの所有権」が与えられます。
簡単に言えば「そのオブジェクトをプレイヤー自身が動かして良いかどうかの権限(フラグ)」です。
現在プレイヤーキャラクタを動かしている箇所では、まだ、その所有権(フラグ)をもとにした「操作の可否」を設定していません。
この「操作の可否判定」をプレイヤー制御のループ系メソッド(Update や OnGUI など)に組み込むことで、今回の不具合は解決します。
実際にどう対策するのか、具体的に触れていきましょう。
プレイヤーキャラクタを操作している箇所を探す▲
プレイヤーキャラクタのプレハブから「Dude」を選択する
まずは、プレイヤーキャラクタのプレハブから、
プレイヤーキャラクタの操作メソッドが含まれている「Dude」オブジェクトを選択しましょう。
この箇所は Unity 2018.2 以前と Unity2018.3 以降でインタフェースが異なりますので、
お使いの Unity のバージョン に合わせて、以下の項目を選択して進めてください。
PlayerにMonobitEngine.MonobitView.isMineを適用する▲
「Dude」のInspectorから「Player」を開く
Dude の Inspector には、プレイヤー制御に関するたくさんのコンポーネントが登録されています。
まずはこの中の、プレイヤーキャラクタの操作処理が含まれている「Player」コンポーネントの
スクリプトファイルをダブルクリックして開いてみましょう。

該当するクラス内に、MonobitView コンポーネント用のフィールドを用意する
Player.cs を開き、「自身が所有権を持つプレイヤー」だけを動かすように、対策を施しましょう。
まず、該当するクラス内に、MonobitView コンポーネントを管理するための変数を用意します。
Player.cs の 12 行目付近に、以下のフィールドを追加してください。
// MonobitView コンポーネント
MonobitEngine.MonobitView m_MonobitView = null;

オブジェクト所有権のフラグ判定を行なうためには、MonobitView コンポーネント本体の参照が必要です。
そのため、事前に変数を用意します。
該当するクラスの Awake() で、MonobitView コンポーネントの情報を取得する
更に、Player.cs の 23 行目付近に、以下の Awake() メソッドのコードを追加します。
void Awake()
{
// すべての親オブジェクトに対して MonobitView コンポーネントを検索する
if (GetComponentInParent<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>();
}
// 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する
else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>();
}
// 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する
else
{
m_MonobitView = GetComponent<MonobitEngine.MonobitView>();
}
}

前述のMonobitTransformView のカスタマイズ でも説明いたしましたが、親オブジェクトに存在する MonobitView を検索し、
そのコンポーネントのデータを m_MonobitView に格納します。
該当するクラスの Update() について、MonobitView.isMineによる実行可否判定を入れる
更に、Player.cs の 50 行目付近に、以下のコードを書き加えます。
// オブジェクト所有権を所持しなければ実行しない
if( !m_MonobitView.isMine )
{
return;
}

MonobitEngine.MonobitView.isMine が、「自身のクライアントはそのオブジェクトの所有権を持つか?」を示す、オブジェクト所有権のフラグです。
単純に、「自分が操作しているクライアントが所有権を持たない場合、そのキャラクタの Update() を処理させない」というロジックを適用させます。
これだけです。
Recorderにも同様の処置を適用する▲
「Dude」のInspector から「Recorder」を選択する
同様の処置を他のスクリプトにも適用させます。
続けて、プレイヤーキャラクタの行動記録制御処理が含まれている「Recorder」コンポーネントの
スクリプトファイルをダブルクリックしましょう。

該当するクラス内に、MonobitView コンポーネント用のフィールドを用意する
Recorder.cs の 10 行目付近に、MonobitView コンポーネントの変数を宣言します。
// MonobitView コンポーネント
MonobitEngine.MonobitView m_MonobitView = null;

Player.csと同様、オブジェクト所有権のフラグ判定を行なうために
MonobitView コンポーネント本体の参照用変数を用意します。
該当するクラスの Awake() で、MonobitView コンポーネントの情報を取得する
更に、Recorder.cs の 23 行目付近に、以下の Awake() メソッドのコードを追加します。
void Awake()
{
// すべての親オブジェクトに対して MonobitView コンポーネントを検索する
if (GetComponentInParent<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>();
}
// 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する
else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>();
}
// 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する
else
{
m_MonobitView = GetComponent<MonobitEngine.MonobitView>();
}
}

これも前述同様、親オブジェクトに存在する MonobitView を検索し、
そのコンポーネントのデータを m_MonobitView に格納します。
該当するクラスの OnGUI() について、実行しないようにする。
更に、Recorder.cs の 57 行目付近に、以下のコードを追記します。
// このUIは必要ないので実行しない
return;

UIで処理できる内容を同期させないように変更を加えます。
該当するクラスの Update() について、MonobitView.isMineによる実行可否判定を入れる
Recorder.cs の 135 行目付近にも、以下のコードを追加しましょう。
// オブジェクト所有権を所持しなければ実行しない
if( !m_MonobitView.isMine )
{
return;
}

ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。
Actionにも同様の処置を適用する▲
「Dude」のInspector から「Action」を選択する
同様に、プレイヤーキャラクタのアニメーション切り替え処理が含まれている「Action」コンポーネントの
スクリプトファイルをダブルクリックしましょう。

該当するクラス内に、MonobitView コンポーネント用のフィールドを用意する
Action.cs の 14 行目付近に、MonobitView コンポーネントの変数を宣言します。
// MonobitView コンポーネント
MonobitEngine.MonobitView m_MonobitView = null;

前述と同様、オブジェクト所有権のフラグ判定を行なうために、MonobitView コンポーネント本体の参照用変数を用意します。
該当するクラスの Awake() で、MonobitView コンポーネントの情報を取得する
更に、Action.cs の 32 行目付近に、以下の Awake() メソッドのコードを追加します。
void Awake()
{
// すべての親オブジェクトに対して MonobitView コンポーネントを検索する
if (GetComponentInParent<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>();
}
// 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する
else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>();
}
// 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する
else
{
m_MonobitView = GetComponent<MonobitEngine.MonobitView>();
}
}

これも前述同様、親オブジェクトに存在する MonobitView を検索し、そのコンポーネントのデータを m_MonobitView に格納します。
該当するクラスの Update() について、MonobitView.isMineによる実行可否判定を入れる
Action.cs の 59 行目についても、以下のコードを記述します。
// オブジェクト所有権を所持しなければ実行しない
if( !m_MonobitView.isMine )
{
return;
}

ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。
DamageReceiverにも同様の処置を適用する▲
「Dude」のInspector から「DamageReceiver」を選択する
同様に、プレイヤーキャラクタのダメージアクション制御が含まれている「DamageReceiver」コンポーネントの
スクリプトファイルをダブルクリックしましょう。

該当するクラス内に、MonobitView コンポーネント用のフィールドを用意する
DamageReceiver.cs の 11 行目付近に、MonobitView コンポーネントの変数を宣言します。
// MonobitView コンポーネント
MonobitEngine.MonobitView m_MonobitView = null;

前述と同様、オブジェクト所有権のフラグ判定を行なうために、MonobitView コンポーネント本体の参照用変数を用意します。
該当するクラスの Awake() で、MonobitView コンポーネントの情報を取得する
更に、DamageReceiver.cs の 32 行目付近に、以下の Awake() メソッドのコードを追加します。
void Awake()
{
// すべての親オブジェクトに対して MonobitView コンポーネントを検索する
if (GetComponentInParent<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>();
}
// 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する
else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>();
}
// 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する
else
{
m_MonobitView = GetComponent<MonobitEngine.MonobitView>();
}
}

これも前述同様、親オブジェクトに存在する MonobitView を検索し、そのコンポーネントのデータを m_MonobitView に格納します。
該当するクラスの Update() について、MonobitView.isMineによる実行可否判定を入れる
さらに、DamageReceiver.cs の 47 行目付近にも、以下のコードを追記します。
// オブジェクト所有権を所持しなければ実行しない
if( !m_MonobitView.isMine )
{
return;
}

ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。
Player_Shootにも同様の処置を適用する▲
「Dude」のInspector から「Player_Shoot」を選択する
同様に、プレイヤーキャラクタの武器であるショット弾の発射制御が含まれている
「Player_Shoot」コンポーネントのスクリプトファイルをダブルクリックしましょう。

該当するクラス内に、MonobitView コンポーネント用のフィールドを用意する
Player_Shoot.cs の 10 行目付近に、MonobitView コンポーネントの変数を宣言します。
// MonobitView コンポーネント
MonobitEngine.MonobitView m_MonobitView = null;

前述と同様、オブジェクト所有権のフラグ判定を行なうために、MonobitView コンポーネント本体の参照用変数を用意します。
該当するクラスの Awake() で、MonobitView コンポーネントの情報を取得する
更に、Player_Shoot.cs の 23 行目付近に、以下の Awake() メソッドのコードを追加します。
void Awake()
{
// すべての親オブジェクトに対して MonobitView コンポーネントを検索する
if (GetComponentInParent<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>();
}
// 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する
else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>();
}
// 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する
else
{
m_MonobitView = GetComponent<MonobitEngine.MonobitView>();
}
}

これも前述同様、親オブジェクトに存在する MonobitView を検索し、そのコンポーネントのデータを m_MonobitView に格納します。
該当するクラスの Update() について、MonobitView.isMineによる実行可否判定を入れる
さらに Player_Shoot.cs の 50 行目付近に、以下のコードを追記します。
// オブジェクト所有権を所持しなければ実行しない
if( !m_MonobitView.isMine )
{
return;
}

ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。
Karmaにも同様の処置を適用する▲
「Dude」のInspector から「Karma」を選択する
同様に、プレイヤーキャラクタが地面から落下したあとの復帰処理が含まれている
「Karma」コンポーネントのスクリプトファイルをダブルクリックしましょう。

該当するクラス内に、MonobitView コンポーネント用のフィールドを用意する
Karma.cs の 10 行目付近に、MonobitView コンポーネントの変数を宣言します。
// MonobitView コンポーネント
MonobitEngine.MonobitView m_MonobitView = null;

前述と同様、オブジェクト所有権のフラグ判定を行なうために、MonobitView コンポーネント本体の参照用変数を用意します。
該当するクラスの Awake() で、MonobitView コンポーネントの情報を取得する
更に、Karma.cs の 13 行目付近に、以下の Awake() メソッドのコードを追加します。
void Awake()
{
// すべての親オブジェクトに対して MonobitView コンポーネントを検索する
if (GetComponentInParent<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInParent<MonobitEngine.MonobitView>();
}
// 親オブジェクトに存在しない場合、すべての子オブジェクトに対して MonobitView コンポーネントを検索する
else if (GetComponentInChildren<MonobitEngine.MonobitView>() != null)
{
m_MonobitView = GetComponentInChildren<MonobitEngine.MonobitView>();
}
// 親子オブジェクトに存在しない場合、自身のオブジェクトに対して MonobitView コンポーネントを検索して設定する
else
{
m_MonobitView = GetComponent<MonobitEngine.MonobitView>();
}
}

これも前述同様、親オブジェクトに存在する MonobitView を検索し、そのコンポーネントのデータを m_MonobitView に格納します。
該当するクラスの Update() について、MonobitView.isMineによる実行可否判定を入れる
更に Karma.cs の 34 行目付近に、以下のコードを追記します。
// オブジェクト所有権を所持しなければ実行しない
if( !m_MonobitView.isMine )
{
return;
}

ここも「所有権を持たないキャラクタは、Update() を処理させない」というロジックを適用させます。
変更したプログラムの保存▲
ここまで変更を加えた全スクリプトを保存する
変更したスクリプトファイルを、すべて保存しましょう。
Visual Studio のメニューから [ファイル] > [すべて保存] を選択し、保存してください。
