Unityでオンラインマルチプレイなゲームを作りたい その7 ゲーム開始前のルーム待機

[前回の記事](https://qiita.com/koyuki2007/items/2fc0bfaf040589ee4ce8)でルームに入室する処理を作りました。
今回は、ゲーム開始前のルーム待機画面のようなものを作っていきます。

# 今回の目標
![scene_diagram_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469983/00d42d3a-e142-5709-7e31-478cba32e4f0.png)
赤〇で囲っている部分辺りを作っていきます。

イメージ図
※プレイヤー名は気にしないでください。
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469983/2beb1c2d-0d20-8c1b-5ae8-e57e3799b00b.jpeg)
実装するものの概要としては、

1. ルーム情報の表示(ルーム名, 入室者数, 入室可能な人数)
2. ルームメンバーの情報を表示
3. ホスト(ルーム作成者)のみ、ルームメンバーを強制退室させることが可能なようにする
4. ホスト(ルーム作成者)のみ、ゲームを開始するボタンを表示
5. ゲスト(ルーム入室者)のみ、参加準備完了のボタンを表示
6. ホスト(ルーム作成者)が抜けた際の処理

の6つになります。

それでは、1から順に実装していきます。

## 1.ルーム情報の表示(ルーム名, 入室者数, 入室可能な人数)
http://www.monobitengine.com/doc/mun/contents/FeatureClient/RoomParameter.htm

```c#
MonobitEngine.MonobitNetwork.room
```
ルームの情報はこれで取得できます。

では、実際に処理を書いてみます。

```c#:SceneRoomWait.cs
/// <summary>ルーム待機画面</summary>
public class SceneRoomWait : MonobitEngine.MonoBehaviour
{
    /// <summary>ルーム参加数のオブジェクト</summary>
    private GameObject m_RoomInfoTextObj;

    // Start is called before the first frame update
    private void Start()
    {
        if (!MonobitNetwork.inRoom) { return; }

        m_RoomMemberCountTextObj = Instantiate(Resources.Load("uGUI_Text")) as GameObject;
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }
}
```
<code>Start()</code>関数の最初に<code>MonobitNetwork.inRoom</code>を使ってルームに入室されている状態かどうかだけ確認させています。
後はuGUIのTextを生成し、そこに<code>MonobitNetwork.room</code>で取得できるルーム名/入室者数/入室可能な人数を入れてるだけです。

後は、参加者が入室/退室した際に、入室者数の更新が必要なので、そこを実装します。
まずは、他の参加者が入室/退室した際に呼ばれるコールバックを作成します。

######同室している他のプレイヤーが入室した際に呼ばれる関数
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnOtherPlayerConnected%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89

```c#
public void OnOtherPlayerConnected(MonobitEngine.MonobitPlayer newPlayer)
```
引数には新しく入室してきたプレイヤーの情報が入っています。

######同室している他のプレイヤーが退室した際に呼ばれる関数
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnOtherPlayerDisconnected%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89

```c#
public void OnOtherPlayerDisconnected(MonobitEngine.MonobitPlayer otherPlayer)
```
引数には退室したプレイヤーの情報が入っています。

では、この二つが呼ばれた際に、表示されている参加人数を変動させるようにしていきます。

```c#:SceneRoomWait.cs
/// <summary>ルーム待機画面</summary>
public class SceneRoomWait : MonobitEngine.MonoBehaviour
{
    /// <summary>ルーム参加数のオブジェクト</summary>
    private GameObject m_RoomInfoTextObj;

    // Start is called before the first frame update
    private void Start()
    {
        if (!MonobitNetwork.inRoom) { return; }

        m_RoomMemberCountTextObj = Instantiate(Resources.Load("uGUI_Text")) as GameObject;
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }

    /// <summary>他プレイヤーが入室してきた際に呼ばれるコールバック</summary>
    /// <param name="newPlayer">入室してきたプレイヤーの情報</param>
    private void OnOtherPlayerConnected(MonobitEngine.MonobitPlayer newPlayer)
    {
        UpdatePlayersCount();
    }

    /// <summary>他プレイヤーが退室した際に呼ばれるコールバック</summary>
    /// <param name="otherPlayer">退室したプレイヤーの情報</param>
    private void OnOtherPlayerDisconnected(MonobitEngine.MonobitPlayer otherPlayer)
    {
        UpdatePlayersCount();
    }

    /// <summary>ルーム入室者数に変動があった際に表示を書き換える</summary>
    private void UpdatePlayersCount()
    {
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }
}
```
コールバックが呼ばれる毎に<code>MonobitNetwork.room.playerCount</code>を入れなおしているだけですね。
これで、人数に変化があった際に表示にも反映されるようになります。

では、次にルームメンバーの情報を表示する処理を作っていきます。

## 2.ルームメンバーの情報を表示
###### ルームメンバーの情報取得
http://www.monobitengine.com/doc/mun/contents/FeatureClient/PlayerParameter.htm#MonobitEngine.MonobitNetwork.playerList%20%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3

```c#
onobitEngine.MonobitNetwork.playerList
```
このリストで他ルームメンバーの情報を取得できます。

まずはプレイヤー情報を表示/更新させるための処理を作っていきます。
※uGUIの表示設定等の処理は省いています。

```c#:PlayerInfoGUI.cs
/// <summary>ルームメンバーの情報を表示する</summary>
public class PlayerInfoGUI : UnityEngine.MonoBehaviour
{
    /// <summary>プレイヤー情報を表示するためのテキスト</summary>
    private Text m_PlayerInfoText;

    /// <summary>プレイヤー情報</summary>
    private MonobitPlayer m_Player;

    /// <summary>表示するプレイヤーのID</summary>
    public int ID { get; private set; }

    /// <summary>表示済みかどうか</summary>
    public bool IsEntry { get; private set; }

    // 
    private void Awake()
    {
        m_PlayerInfoText.text = "No Entry";

        ID = int.MaxValue;

        IsEntry = false;
    }

    /// <summary>表示させたいプレイヤーの情報を設定する</summary>
    /// <param name="player">表示したいプレイヤーの情報</param>
    public void Set(MonobitPlayer player)
    {
        m_Player = player;

        ID = m_Player.ID;

        IsEntry = true;

        InfoUpdate();
    }

    /// <summary>表示する情報を削除</summary>
    public void Clear()
    {
        m_Player = null;

        m_PlayerInfoText.text = "No Entry";

         ID =int.MaxValue;

        IsEntry = false;
    }

    /// <summary>表示情報の更新</summary>
    public void InfoUpdate()
    {
        if (!IsEntry) { return; }

        string attribute = m_Player.isHost ? "Host": "Guest";

        m_PlayerInfoText.text = m_Player.ID + ":" + m_Player.name + "  " + attribute;
    }
}
```
<code>public void Set(MonobitPlayer player)</code>でプレイヤー情報を受け取り<code>public void InfoUpdate()</code>で情報を元に表示するテキストを作成しています。
このスクリプトを空のオブジェクトに追加し、Prefab化しておくと扱いやすいです。

では、SceneRoomWait.csでこの処理にプレイヤー情報を渡してあげます。

```c#:SceneRoomWait.cs
/// <summary>ルーム待機画面</summary>
public class SceneRoomWait : MonobitEngine.MonoBehaviour
{
    /// <summary>ルーム情報を表示するオブジェクト</summary>
    private GameObject m_RoomInfoTextObj;

    ///　<summary>プレイヤー情報を表示するオブジェクト</summary>
    private GameObject[] m_PlayerInfos;    

    // Start is called before the first frame update
    private void Start()
    {
        if (!MonobitNetwork.inRoom) { return; }

        m_RoomMemberCountTextObj = Instantiate(Resources.Load("uGUI_Text")) as GameObject;
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";

        // ルームに入れるプレイヤーの最大数分だけ、プレイヤー情報表示を作成する
        m_PlayerInfos = new GameObject[MonobitNetwork.room.maxPlayers];
        for(int i = 0; i < MonobitNetwork.room.maxPlayers; ++i)
        {
             // PlayerInfoGUI.csが追加されたPrefabをインスタンス化
             m_PlayerInfos[i] = Instantiate(Resource.Load("PlayerInfoGUI")) as GameObject;
        }

        // 作成したプレイヤー情報表示に既に参加しているプレイヤーの情報を設定していく
        int length = MonobitNetwork.playerList.Length;
        for (int i = 0; i < length; ++i)
        {
            m_PlayerInfos[i].GetComponent<PlayerInfoGUI>().Set(MonobitNetwork.playerList[i]);
            m_PlayerInfos[i].SetActive(true);
        }
    }

    /// <summary>他プレイヤーが入室してきた際に呼ばれるコールバック</summary>
    /// <param name="newPlayer">入室してきたプレイヤーの情報</param>
    private void OnOtherPlayerConnected(MonobitEngine.MonobitPlayer newPlayer)
    {
        // プレイヤーが新たに入室してきたので、プレイヤー情報表示に追加する
        int length = m_PlayerInfos.Length;
        for(int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();
            
            if (plyaerInfo.IsEntry){ continue; }

            plyaerInfo.Set(newPlayer);

            m_PlayerInfos[i].SetActive(true);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>他プレイヤーが退室した際に呼ばれるコールバック</summary>
    /// <param name="otherPlayer">退室したプレイヤーの情報</param>
    private void OnOtherPlayerDisconnected(MonobitEngine.MonobitPlayer otherPlayer)
    {
        // プレイヤーが退室したので、該当プレイヤーの情報を非表示にする
        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != otherPlayer.ID) { continue; }

            plyaerInfo.Clear();

            m_PlayerInfos[i].SetActive(false);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>ルーム入室者数に変動があった際に表示を書き換える</summary>
    private void UpdatePlayersCount()
    {
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }
}
```
<code>Start()</code>はルーム入室に成功した直後なので、既にルームに入室されているプレイヤーの情報をここで取得します。
後は、別プレイヤーが入室/退室した場合に呼ばれるコールバック内で、該当するプレイヤーの情報を設定/削除するだけです。

取りあえずこれでルームメンバーの情報の表示が出来るようになりました。
次は、ホストのみ、ルームメンバーを強制退室させることが出来るようにする処理を作っていきます。

## 3.ホスト(ルーム作成者)のみ、ルームメンバーを強制退室させることが可能なようにする
http://www.monobitengine.com/doc/mun/contents/FeatureClient/Kick.htm

```c#
 MonobitEngine.MonobitNetwork.Kick( player );
```
この関数を強制退室させたいプレイヤー情報渡して呼んであげればいいですね。
では、イメージ図のようにプレイヤー情報の横に、プレイヤーに対応した強制退室ボタンを表示/クリック時に退室させる処理を追加します。

先に書いたPlayerInfoGUI.csに追加すると良さそうです。

```c#:PlayerInfoGUI.cs
/// <summary>ルームメンバーの情報を表示する</summary>
public class PlayerInfoGUI : UnityEngine.MonoBehaviour
{
    /// <summary>プレイヤー情報を表示するためのテキスト</summary>
    private Text m_PlayerInfoText;

    /// <summary></summary>
    private GameObject m_KickButtonObj;

    /// <summary></summary>
    private Button m_KickButton;

    /// <summary>プレイヤー情報</summary>
    private MonobitPlayer m_Player;

    /// <summary>表示するプレイヤーのID</summary>
    public int ID { get; private set; }

    /// <summary>表示済みかどうか</summary>
    public bool IsEntry { get; private set; }

    // 
    private void Awake()
    {
        m_PlayerInfoText.text = "No Entry";

        m_KickButtonObj = GameObject.Instantiate(Resources.Load("Button")) as GameObject;
        m_KickButtonObj.GetComponent<RectTransform>().sizeDelta = m_ButtonSize;
        m_KickButtonObj.SetActive(false);
        
        Text text = m_KickButtonObj.GetComponentInChildren<Text>();
        text.fontSize = m_ButtonFontSize;
        text.text = "Kick";

        m_KickButton = m_KickButtonObj.GetComponent<Button>();
        m_KickButton.onClick.AddListener(OnClickKick);

        ID = int.MaxValue;

        IsEntry = false;
    }

    /// <summary>強制退室ボタンが押されたときに呼ばれる</summary>
    private void OnClickKick()
    {
        if (!IsEntry) { return; }

        // 現在表示しているプレイヤー情報に該当するプレイヤーを強制退室させる
        MonobitNetwork.Kick(m_Player);
    }

    /// <summary>表示させたいプレイヤーの情報を設定する</summary>
    /// <param name="player">表示したいプレイヤーの情報</param>
    public void Set(MonobitPlayer player)
    {
        m_Player = player;

        ID = m_Player.ID;

        IsEntry = true;

        InfoUpdate();
    }

    /// <summary>表示する情報を削除</summary>
    public void Clear()
    {
        m_Player = null;

        m_PlayerInfoText.text = "No Entry";

         ID =int.MaxValue;

        IsEntry = false;
    }

    /// <summary>表示情報の更新</summary>
    public void InfoUpdate()
    {
        if (!IsEntry) { return; }

        string attribute = m_Player.isHost ? "Host": "Guest";

        m_PlayerInfoText.text = m_Player.ID + ":" + m_Player.name + "  " + attribute;

        //  MonobitNetwork.Kick()の仕様上、自分自身も強制退室できるため、ボタンの処理は行えないようにする。
        if (ID == MonobitNetwork.player.ID) { return; }

        // ホストのみが強制退室ボタンを使えるようにしたいので、ホストでない場合はボタンは非表示にさせる。
        m_KickButtonObj.SetActive(MonobitNetwork.isHost);
    }
}
```
ホストのみが強制退室できるようにしたいので、強制退室ボタンそのものをホストのみが表示するようにしています。
更に、自分自身も強制退室できてしまうので、自分自身に該当する強制退室ボタンも非表示にしています。
これで表示している各プレイヤーに対応した強制退室ボタンが表示/利用できるようになります。

## 4.ホスト(ルーム作成者)のみ、ゲームを開始するボタンを表示
次に、ゲームを開始するボタンを作成します。これはホストのみで操作したいので、ホスト以外は表示しないようにしたいと思います。

```c#:SceneRoomWait.cs
/// <summary>ルーム待機画面</summary>
public class SceneRoomWait : MonobitEngine.MonoBehaviour
{
    /// <summary>ゲームスタートボタンのオブジェクト</summary>
    private GameObject m_InGameButtonObj;

    /// <summary>ゲームスタートボタン</summary>
    private Button m_InGameButton;

    /// <summary>ゲームスタートボタンのテキスト</summary>
    private Text m_InGameButtonText;

    /// <summary>ルーム情報を表示するオブジェクト</summary>
    private GameObject m_RoomInfoTextObj;

    ///　<summary>プレイヤー情報を表示するオブジェクト</summary>
    private GameObject[] m_PlayerInfos;    

    // Start is called before the first frame update
    private void Start()
    {
        if (!MonobitNetwork.inRoom) { return; }
          
        if (MonobitNetwork.isHost)
        {
            m_InGameButtonObj.GetComponentInChildren<Button>().onClick.AddListener(OnClickInGame());
            m_InGameButtonText = m_InGameButtonObj.GetComponentInChildren<Text>();
            m_InGameButtonText.text = "Start";
        }   
        
        m_RoomMemberCountTextObj = Instantiate(Resources.Load("uGUI_Text")) as GameObject;
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";

        // ルームに入れるプレイヤーの最大数分だけ、プレイヤー情報表示を作成する
        m_PlayerInfos = new GameObject[MonobitNetwork.room.maxPlayers];
        for(int i = 0; i < MonobitNetwork.room.maxPlayers; ++i)
        {
             // PlayerInfoGUI.csが追加されたPrefabをインスタンス化
             m_PlayerInfos[i] = Instantiate(Resource.Load("PlayerInfoGUI")) as GameObject;
        }

        // 作成したプレイヤー情報表示に既に参加しているプレイヤーの情報を設定していく
        int length = MonobitNetwork.playerList.Length;
        for (int i = 0; i < length; ++i)
        {
            m_PlayerInfos[i].GetComponent<PlayerInfoGUI>().Set(MonobitNetwork.playerList[i]);
            m_PlayerInfos[i].SetActive(true);
        }
    }

    /// <summary>他プレイヤーが入室してきた際に呼ばれるコールバック</summary>
    /// <param name="newPlayer">入室してきたプレイヤーの情報</param>
    private void OnOtherPlayerConnected(MonobitEngine.MonobitPlayer newPlayer)
    {
        // プレイヤーが新たに入室してきたので、プレイヤー情報表示に追加する
        int length = m_PlayerInfos.Length;
        for(int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.IsEntry){ continue; }

            plyaerInfo.Set(newPlayer);

            m_PlayerInfos[i].SetActive(true);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>他プレイヤーが退室した際に呼ばれるコールバック</summary>
    /// <param name="otherPlayer">退室したプレイヤーの情報</param>
    private void OnOtherPlayerDisconnected(MonobitEngine.MonobitPlayer otherPlayer)
    {
        // プレイヤーが退室したので、該当プレイヤーの情報を非表示にする
        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != otherPlayer.ID) { continue; }

            plyaerInfo.Clear();

            m_PlayerInfos[i].SetActive(false);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>スタートボタンが押された際に呼ばれる</summary>
    public void OnClickInGame()
    {
         Debug.Log("Game Start!");
    }

    /// <summary>ルーム入室者数に変動があった際に表示を書き換える</summary>
    private void UpdatePlayersCount()
    {
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }
}
```
単にホストの場合のみ、<code>Start()</code>関数内でボタンを生成するようにしているだけですね。

## 5.ゲスト(ルーム入室者)のみ、参加準備完了のボタンを表示
次にゲストプレイヤーが、ホストプレイヤーに対してゲーム開始の準備が出来たことを伝えるReadyボタンを作ります。
先ほどホスト用につくったゲームを開始するボタンを、ゲストの場合はホストに対して準備完了を伝えるボタンになるように処理を変更します。

ホストに対して、準備完了を伝える方法は、
###### プレイヤーカスタムパラメータ
http://www.monobitengine.com/doc/mun/contents/FeatureClient/PlayerCustomParameter.htm
こちらのプレイヤーカスタムパラメータという機能を使って実装していきます。
プレイヤーカスタムパラメータは、同室しているルームメンバーに共有することが出来るプレイヤー固有の情報を設定できます。
これに、ゲーム開始の準備ができたかどうかを伝えるためのReadyパラメータを追加します。

###### プレイヤーカスタムパラメータ変更時に呼ばれるコールバック
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnMonobitPlayerParametersChanged%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89

```c#
public void OnMonobitPlayerParametersChanged(object[] playerAndUpdateProps)
```
プレイヤーカスタムパラメータ変更時に呼ばれるコールバックです。
プレイヤーの表示情報の更新等に使えます。

```c#:SceneRoomWait.cs
/// <summary>ルーム待機画面</summary>
public class SceneRoomWait : MonobitEngine.MonoBehaviour
{
    /// <summary>ゲームスタートボタンのオブジェクト</summary>
    private GameObject m_InGameButtonObj;

    /// <summary>ゲームスタートボタン</summary>
    private Button m_InGameButton;

    /// <summary>ゲームスタートボタンのテキスト</summary>
    private Text m_InGameButtonText;

    /// <summary>ルーム情報を表示するオブジェクト</summary>
    private GameObject m_RoomInfoTextObj;

    ///　<summary>プレイヤー情報を表示するオブジェクト</summary>
    private GameObject[] m_PlayerInfos;    

    // Start is called before the first frame update
    private void Start()
    {
        if (!MonobitNetwork.inRoom) { return; }

        Hashtable playerCustomParams = new Hashtable();
        playerCustomParams["ready"] = false;
        MonobitNetwork.SetPlayerCustomParameters(playerCustomParams);

        if (MonobitNetwork.isHost)
        {
            m_InGameButtonObj.GetComponentInChildren<Button>().onClick.AddListener(OnClickInGame());
            m_InGameButtonText = m_InGameButtonObj.GetComponentInChildren<Text>();
            m_InGameButtonText.text = "Start";
        }   

        m_RoomMemberCountTextObj = Instantiate(Resources.Load("uGUI_Text")) as GameObject;
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";

        // ルームに入れるプレイヤーの最大数分だけ、プレイヤー情報表示を作成する
        m_PlayerInfos = new GameObject[MonobitNetwork.room.maxPlayers];
        for(int i = 0; i < MonobitNetwork.room.maxPlayers; ++i)
        {
             // PlayerInfoGUI.csが追加されたPrefabをインスタンス化
             m_PlayerInfos[i] = Instantiate(Resource.Load("PlayerInfoGUI")) as GameObject;
        }

        // 作成したプレイヤー情報表示に既に参加しているプレイヤーの情報を設定していく
        int length = MonobitNetwork.playerList.Length;
        for (int i = 0; i < length; ++i)
        {
            m_PlayerInfos[i].GetComponent<PlayerInfoGUI>().Set(MonobitNetwork.playerList[i]);
            m_PlayerInfos[i].SetActive(true);
        }
    }

    /// <summary>他プレイヤーが入室してきた際に呼ばれるコールバック</summary>
    /// <param name="newPlayer">入室してきたプレイヤーの情報</param>
    private void OnOtherPlayerConnected(MonobitEngine.MonobitPlayer newPlayer)
    {
        // プレイヤーが新たに入室してきたので、プレイヤー情報表示に追加する
        int length = m_PlayerInfos.Length;
        for(int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.IsEntry){ continue; }

            plyaerInfo.Set(newPlayer);

            m_PlayerInfos[i].SetActive(true);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>他プレイヤーが退室した際に呼ばれるコールバック</summary>
    /// <param name="otherPlayer">退室したプレイヤーの情報</param>
    private void OnOtherPlayerDisconnected(MonobitEngine.MonobitPlayer otherPlayer)
    {
        // プレイヤーが退室したので、該当プレイヤーの情報を非表示にする
        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != otherPlayer.ID) { continue; }

            plyaerInfo.Clear();

            m_PlayerInfos[i].SetActive(false);

            break;
        }

        UpdatePlayersCount();
    }
    /// <summary>MonobitNetwork.SetPlayerCustomParametersによりプレイヤーカスタムパラメータが変更された際に呼ばれるコールバック</summary>
    /// <param name="playerAndUpdatedParameters">プレイヤーカスタムパラメータに変更があったMonobitPlayer及びHashtable</param>
    public void OnMonobitPlayerParametersChanged(object[] playerAndUpdatedParameters)
    {         
        MonobitPlayer monobitPlayer = (MonobitPlayer)playerAndUpdatedParameters[0];
        Hashtable playerParams = (Hashtable)playerAndUpdatedParameters[1];

        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != monobitPlayer.ID) { continue; }

             plyaerInfo.InfoUpdate();

            break;
        }
    }

    /// <summary>スタートボタンが押された際に呼ばれる</summary>
    public void OnClickInGame()
    {
         Debug.Log("Game Start!");
    }

    /// <summary>ルーム入室者数に変動があった際に表示を書き換える</summary>
    private void UpdatePlayersCount()
    {
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }
}
```
<code>Start()</code>で"ready"というパラメータをfalseに設定して追加しているだけです。
<code>MonobitNetwork.SetPlayerCustomParameters(playerCustomParams);</code>を呼ぶことで、各ルームメンバーにパラメータを共有することが出来ます。
<code>OnMonobitPlayerParametersChanged</code>内では、更新があったプレイヤーをIDから探し出し、表示の更新を行っているだけです。

では、ホストはStartボタン、ゲストはReadyボタンになるように処理を更に変更していきます。

```c#:SceneRoomWait.cs
/// <summary>ルーム待機画面</summary>
public class SceneRoomWait : MonobitEngine.MonoBehaviour
{
    /// <summary>ゲームスタートボタンのオブジェクト</summary>
    private GameObject m_InGameButtonObj;

    /// <summary>ゲームスタートボタン</summary>
    private Button m_InGameButton;

    /// <summary>ゲームスタートボタンのテキスト</summary>
    private Text m_InGameButtonText;

    /// <summary>ルーム情報を表示するオブジェクト</summary>
    private GameObject m_RoomInfoTextObj;

    ///　<summary>プレイヤー情報を表示するオブジェクト</summary>
    private GameObject[] m_PlayerInfos;    

    // Start is called before the first frame update
    private void Start()
    {
        if (!MonobitNetwork.inRoom) { return; }

        Hashtable playerCustomParams = new Hashtable();
        playerCustomParams["ready"] = MonobitNetwork.isHost;
        MonobitNetwork.SetPlayerCustomParameters(playerCustomParams);

        m_InGameButtonObj.GetComponentInChildren<Button>().onClick.AddListener(OnClickInGame());
        m_InGameButtonText = m_InGameButtonObj.GetComponentInChildren<Text>();
        m_InGameButtonText.text = MonobitNetwork.isHost ? "Start" : "Ready";

        m_RoomMemberCountTextObj = Instantiate(Resources.Load("uGUI_Text")) as GameObject;
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";

        // ルームに入れるプレイヤーの最大数分だけ、プレイヤー情報表示を作成する
        m_PlayerInfos = new GameObject[MonobitNetwork.room.maxPlayers];
        for(int i = 0; i < MonobitNetwork.room.maxPlayers; ++i)
        {
             // PlayerInfoGUI.csが追加されたPrefabをインスタンス化
             m_PlayerInfos[i] = Instantiate(Resource.Load("PlayerInfoGUI")) as GameObject;
        }

        // 作成したプレイヤー情報表示に既に参加しているプレイヤーの情報を設定していく
        int length = MonobitNetwork.playerList.Length;
        for (int i = 0; i < length; ++i)
        {
            m_PlayerInfos[i].GetComponent<PlayerInfoGUI>().Set(MonobitNetwork.playerList[i]);
            m_PlayerInfos[i].SetActive(true);
        }
    }

    /// <summary>他プレイヤーが入室してきた際に呼ばれるコールバック</summary>
    /// <param name="newPlayer">入室してきたプレイヤーの情報</param>
    private void OnOtherPlayerConnected(MonobitEngine.MonobitPlayer newPlayer)
    {
        // プレイヤーが新たに入室してきたので、プレイヤー情報表示に追加する
        int length = m_PlayerInfos.Length;
        for(int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.IsEntry){ continue; }

            plyaerInfo.Set(newPlayer);

            m_PlayerInfos[i].SetActive(true);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>他プレイヤーが退室した際に呼ばれるコールバック</summary>
    /// <param name="otherPlayer">退室したプレイヤーの情報</param>
    private void OnOtherPlayerDisconnected(MonobitEngine.MonobitPlayer otherPlayer)
    {
        // プレイヤーが退室したので、該当プレイヤーの情報を非表示にする
        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != otherPlayer.ID) { continue; }

            plyaerInfo.Clear();

            m_PlayerInfos[i].SetActive(false);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>MonobitNetwork.SetPlayerCustomParametersによりプレイヤーカスタムパラメータが変更された際に呼ばれるコールバック</summary>
    /// <param name="playerAndUpdatedParameters">プレイヤーカスタムパラメータに変更があったMonobitPlayer及びHashtable</param>
    public void OnMonobitPlayerParametersChanged(object[] playerAndUpdatedParameters)
    {         
        MonobitPlayer monobitPlayer = (MonobitPlayer)playerAndUpdatedParameters[0];
        Hashtable playerParams = (Hashtable)playerAndUpdatedParameters[1];

        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != monobitPlayer.ID) { continue; }

             plyaerInfo.InfoUpdate();

            break;
        }
    }

    /// <summary>スタートボタンが押された際に呼ばれる</summary>
    public void OnClickInGame()
    {
       if (MonobitNetwork.isHost) // ホスト側なので、ゲームスタートボタンの処理
        {
            int length = MonobitNetwork.playerList.Length;
            for (int i = 0; i < MonobitNetwork.playerList.Length; ++i)
            {
                if ((bool)MonobitNetwork.playerList[i].customParameters["ready"] == false)
                {
                    Debug.Log(MonobitNetwork.playerList[i].name + "is not ready.");

                    return;
                }
            }

             Debug.Log("Game Start!");
        }
        else // ゲスト側なので、Readyボタンの処理
        {
            Hashtable playerCustomParam = MonobitNetwork.player.customParameters;

            playerCustomParam["ready"] = ((bool)playerCustomParam["ready"] == true) ? false : true;

            MonobitNetwork.SetPlayerCustomParameters(playerCustomParam);
        }
    }

    /// <summary>ルーム入室者数に変動があった際に表示を書き換える</summary>
    private void UpdatePlayersCount()
    {
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }
}
```
ホストは常にtrue、ゲストはreadyボタンが押されるまでfalseにしたかったので、<code>Start()</code>関数でプレイヤーカスタムパラメータの設定を行っているところの値が変わるように変更しています。(ついでにボタンのテキストも"Start"と"Ready"で変えています。)
<code>OnClickInGame()</code>内で、ホスト時とゲスト時で処理を分岐させています。
ホストではゲーム開始の為に、ルームメンバーが全員準備できているかを<code>MonobitNetwork.otherPlayerList</code>で各ゲストのプレイヤーカスタムパラメータを確認しています。
ゲストではボタンを押すたびにプレイヤーカスタムパラメータの"ready"の値を変えるようにしてるだけです。

## 6.ホスト(ルーム作成者)が抜けた際の処理
では、最後にホストが抜けた際の処理を追加します。
http://www.monobitengine.com/doc/mun/contents/FeatureClient/ChangeHost.htm
ホストがルームを抜けた際は、抜けたホスト以外で、最も古いプレイヤーに自動でホストが委譲されるようです。

今回は、ホストが抜け別のルームメンバーがホストになった場合、ホストが担当していたゲームスタートボタンの処理など切り替えないといけないので実装していきます。

http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnHostChanged%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89

```c#
public void OnHostChanged(MonobitEngine.MonobitPlayer newHost)
```
ホストが切り替わった際に呼ばれるコールバックがあるので、こちらを使います。

```c#
/// <summary>ルーム待機画面</summary>
public class SceneRoomWait : MonobitEngine.MonoBehaviour
{
    /// <summary>ゲームスタートボタンのオブジェクト</summary>
    private GameObject m_InGameButtonObj;

    /// <summary>ゲームスタートボタン</summary>
    private Button m_InGameButton;

    /// <summary>ゲームスタートボタンのテキスト</summary>
    private Text m_InGameButtonText;

    /// <summary>ルーム情報を表示するオブジェクト</summary>
    private GameObject m_RoomInfoTextObj;

    ///　<summary>プレイヤー情報を表示するオブジェクト</summary>
    private GameObject[] m_PlayerInfos;    

    // Start is called before the first frame update
    private void Start()
    {
        if (!MonobitNetwork.inRoom) { return; }

        Hashtable playerCustomParams = new Hashtable();
        playerCustomParams"ready"] = MonobitNetwork.isHost);
        MonobitNetwork.SetPlayerCustomParameters(playerCustomParams;

        m_InGameButtonObj.GetComponentInChildren<Button>().onClick.AddListener(OnClickInGame());
        m_InGameButtonText = m_InGameButtonObj.GetComponentInChildren<Text>();
        m_InGameButtonText.text = MonobitNetwork.isHost ? "Start" : "Ready";

        m_RoomMemberCountTextObj = Instantiate(Resources.Load("uGUI_Text")) as GameObject;
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";

        // ルームに入れるプレイヤーの最大数分だけ、プレイヤー情報表示を作成する
        m_PlayerInfos = new GameObject[MonobitNetwork.room.maxPlayers];
        for(int i = 0; i < MonobitNetwork.room.maxPlayers; ++i)
        {
             // PlayerInfoGUI.csが追加されたPrefabをインスタンス化
             m_PlayerInfos[i] = Instantiate(Resource.Load("PlayerInfoGUI")) as GameObject;
        }

        // 作成したプレイヤー情報表示に既に参加しているプレイヤーの情報を設定していく
        int length = MonobitNetwork.playerList.Length;
        for (int i = 0; i < length; ++i)
        {
            m_PlayerInfos[i].GetComponent<PlayerInfoGUI>().Set(MonobitNetwork.playerList[i]);
            m_PlayerInfos[i].SetActive(true);
        }
    }

    /// <summary>他プレイヤーが入室してきた際に呼ばれるコールバック</summary>
    /// <param name="newPlayer">入室してきたプレイヤーの情報</param>
    private void OnOtherPlayerConnected(MonobitEngine.MonobitPlayer newPlayer)
    {
        // プレイヤーが新たに入室してきたので、プレイヤー情報表示に追加する
        int length = m_PlayerInfos.Length;
        for(int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.IsEntry){ continue; }

            plyaerInfo.Set(newPlayer);

            m_PlayerInfos[i].SetActive(true);

            break;
        }

        UpdatePlayersCount();
    }

    /// <summary>他プレイヤーが退室した際に呼ばれるコールバック</summary>
    /// <param name="otherPlayer">退室したプレイヤーの情報</param>
    private void OnOtherPlayerDisconnected(MonobitEngine.MonobitPlayer otherPlayer)
    {
        // プレイヤーが退室したので、該当プレイヤーの情報を非表示にする
        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != otherPlayer.ID) { continue; }

            plyaerInfo.Clear();

            m_PlayerInfos[i].SetActive(false);

            break;
        }

        UpdatePlayersCount();
    }
    /// <summary>ホストプレイヤーが退室した際に呼ばれるコールバック</summary>
    /// <param name="newHost">新しく設定されたホストプレイヤーの情報</param>
    public void OnHostChanged(MonobitPlayer newHost)
    {
        // 新しくホストになったプレイヤーが自分で無ければ処理は不要
        if (!MonobitNetwork.isHost) { return; }

        // ホストに変更があったのでプレイヤーの情報を更新する
        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
             playerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            plyaerInfo.InfoUpdate();
        }

        Hashtable playerCustomParam = MonobitNetwork.player.customParameters;
        playerCustomParam["ready"] = true.ToString();
        MonobitNetwork.SetPlayerCustomParameters(playerCustomParam);

        m_InGameButtonText.text = "Start";
    }

    /// <summary>MonobitNetwork.SetPlayerCustomParametersによりプレイヤーカスタムパラメータが変更された際に呼ばれるコールバック</summary>
    /// <param name="playerAndUpdatedParameters">プレイヤーカスタムパラメータに変更があったMonobitPlayer及びHashtable</param>
    public void OnMonobitPlayerParametersChanged(object[] playerAndUpdatedParameters)
    {         
        MonobitPlayer monobitPlayer = (MonobitPlayer)playerAndUpdatedParameters[0];
        Hashtable playerParams = (Hashtable)playerAndUpdatedParameters[1];

        int length = m_PlayerInfos.Length;
        for (int i = 0; i < length; ++i)
        {
            PlayerInfoGUI plyaerInfo = m_PlayerInfos[i].GetComponent<PlayerInfoGUI>();

            if (plyaerInfo.ID != monobitPlayer.ID) { continue; }

             plyaerInfo.InfoUpdate();

            break;
        }
    }

    /// <summary>スタートボタンが押された際に呼ばれる</summary>
    public void OnClickInGame()
    {
       if (MonobitNetwork.isHost) // ホスト側なので、ゲームスタートボタンの処理
        {
            int length = MonobitNetwork.playerList.Length;
            for (int i = 0; i < MonobitNetwork.playerList.Length; ++i)
            {
                if ((bool)MonobitNetwork.playerList[i].customParameters["ready"] == false)
                {
                    Debug.Log(MonobitNetwork.playerList[i].name + "is not ready.");

                    return;
                }
            }

             Debug.Log("Game Start!");
        }
        else // ゲスト側なので、Readyボタンの処理
        {
            Hashtable playerCustomParam = MonobitNetwork.player.customParameters;

            playerCustomParam["ready"] = ((bool)playerCustomParam["ready"] == true) ? false : true;

            MonobitNetwork.SetPlayerCustomParameters(playerCustomParam);
        }
    }

    /// <summary>ルーム入室者数に変動があった際に表示を書き換える</summary>
    private void UpdatePlayersCount()
    {
        m_RoomMemberCountTextObj.GetComponent<Text>().text = MonobitNetwork.room.name + "(" + MonobitNetwork.room.playerCount + "/" + MonobitNetwork.room.maxPlayers + ")";
    }
}
```
<code>OnHostChanged</code>内で、ホストが行う処理に切り替えるようにしています。

これでルーム待機画面の処理が一通り作成できました。

次回からは、ゲーム部分の同期について書いていきたいと思います。

# 参考
http://www.monobitengine.com/doc/mun/contents/FeatureClient/RoomParameter.htm
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnOtherPlayerConnected%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnOtherPlayerDisconnected%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89
http://www.monobitengine.com/doc/mun/contents/FeatureClient/PlayerParameter.htm#MonobitEngine.MonobitNetwork.playerList%20%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3
http://www.monobitengine.com/doc/mun/contents/FeatureClient/Kick.htm
http://www.monobitengine.com/doc/mun/contents/FeatureClient/PlayerCustomParameter.htm
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnMonobitPlayerParametersChanged%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89
http://www.monobitengine.com/doc/mun/contents/FeatureClient/ChangeHost.htm
http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnHostChanged%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89