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

目次

  概要
  プレイヤーキャラクタを操作している箇所を探す
  PlayerにMonobitEngine.MonobitView.isMineを適用する
  Recorderにも同様の処置を適用する
  Actionにも同様の処置を適用する
  DamageReceiverにも同様の処置を適用する
  Player_Shootにも同様の処置を適用する
  Karmaにも同様の処置を適用する
  変更したプログラムの保存


概要

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

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

  この原因については、以下の折り畳み箇所に記述します。
  (難しい理屈ですので、ここは理解できなくても構いません。)

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

  しかし「オンラインゲームに移行した」このサンプルゲーム上においては、
  「誰かが上を押した」場合、自分のプレイヤーキャラクタも、相手のプレイヤーキャラクタも、すべてのキャラクタが前進します。
  結果、ネットワーク上の全ての「プレイヤーキャラクタ」が、各々のクライアントにより釣られて動くようになってしまうのです。

  ただし、MUNでは内部的に、一定のフレームレートにより
     ・MonobitTransformView - 位置・姿勢・倍率の同期
     ・MonobitAnimatorView - アニメーションの同期
  で「自身のオブジェクトの同期」を行なっているため、
  他クライアントのキャラクタを動かしてしまっても、そのオブジェクトを生成した他クライアントからの受信によりリセットされるため、
・ 位置は同期しているが、アニメーションが同期しない。
・ 自分のキャラクタを動かそうとすると、相手のアニメーションが動こうと頑張っているように見える。
  という奇妙な現象が発生することになります。

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

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

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

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

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


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

プレイヤーキャラクタのプレハブから「Dude」を選択する

  まずは、プレイヤーキャラクタのプレハブから、
  プレイヤーキャラクタの操作メソッドが含まれている「Dude」オブジェクトを選択しましょう。

  この箇所は Unity 2018.2 以前と Unity2018.3 以降でインタフェースが異なりますので、
  お使いの Unity のバージョン に合わせて、以下の項目を選択して進めてください。

 Assets/Resources フォルダを開き、 Player.prefab の右端にある「>」のマークをクリックします。
  すると、player.prefab の中身が展開されます。
  展開されたデータのうち、「Dude」と書かれたオブジェクトを選択します。



  まず Assets/Resources フォルダーをクリックして選択し、その中にある Player.prefab を選択します。
  Player の Inspector にある「Open Prefab」のボタンを押します。
  すると Hierarchy の表示箇所に Player.prefab がリスト化されて表示されますので、
  親オブジェクト「Player」の左隣にある三角マークをクリックし、子オブジェクトの「Dude」を選択します。


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 のメニューから [ファイル] > [すべて保存] を選択し、保存してください。