Unityでオンラインマルチプレイなゲームを作りたい 終 今までのまとめ

# 今回の内容
[前回の記事](https://qiita.com/koyuki2007/items/79888c27688d7e52a530)で実装について一通り書き終えたため、今までに使用したMUNの機能とそれを利用したネットワークの実装法についてを纏めたものを書こうかなと思います。

ということで書いていきます。

# サーバー接続/切断と関連のコールバック
[MUN技術情報ページ/サーバへの接続](http://www.monobitengine.com/doc/mun/contents/FeatureClient/ConnectToServer.htm)
サーバーへ接続するための関数を呼ぶだけで接続してくれます。
接続時に呼んでくれるコールバックも用意されていて、MonobitEngine.MonoBehaviorを継承していると呼んでくれるようになります。

例)ボタンを押したらサーバーへ接続する

```c#
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

using MonobitEngine;

 /// <summaryサーバーへ接続するためのクラス</summary>
public class ServerConnecter : MonobitEngine.MonoBehavior
{
    /// <summary>接続ボタンのオブジェクト</summary>
    private GameObject m_ConnectButton;

    // Start is called before the first frame update
    private void Start()
    {
        m_ConnectButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_ConnectButton.GetComponent<Button>().onClick.AddListener(OnClickConnect);
    }

    // Update is called once per frame
    private void Update()
    {

    }

    /// <summary>接続ボタンを押した際に呼ばれる</summary>
    public void OnClickConnect()
    {
        // サーバーへ接続する
        MonobitEngine.MonobitNetwork.ConnectServer("v1.0");
    }

    /// <summary>サーバー接続後に呼んでくれる、この関数が呼ばれた時点ではまだ通信は行えないらしい</summary>
    public void OnConnectedToMonobit()
    {
        Debug.Log("接続に成功しました");
    }

    /// <summary>サーバーの接続に失敗したときに呼んでくれる</summary>
    /// <param name="cause">失敗した際の情報</param>
    public void OnConnectToServerFailed(MonobitEngine.DisconnectCause cause)
    {  
        Debug.Log("OnConnectToServerFailed : cause = " + cause.ToString());
    }

    /// <summary>何らかの原因によりサーバ接続確立後に接続失敗したときに呼んでくれる</summary>
    /// <param name="cause">失敗した際の情報</param>
    public void OnConnectionFail (MonobitEngine.DisconnectCause cause)
    {
        Debug.Log("OnConnectionFail  : cause = " + cause.ToString());
    }

    /// <summary>同時接続数制限に達したときに呼んでくれる</summary>
    public void OnMonobitMaxConnectionReached()
    {
        Debug.Log("接続数が上限に達しています");
    }

    /// <summary>サーバに接続＆認証完了したときに呼んでくれる</summary>
    public void OnConnectedToServer()
    {
        // サーバーの接続に成功したので次の画面へ移動
        SceneManager.LoadScene("SceneLobby");
    }
}
```
[MUN技術情報ページ/サーバへの切断](http://www.monobitengine.com/doc/mun/contents/FeatureClient/DisconnectFromServer.htm)
接続と同じく関数を呼び、切断時のコールバックを書いてあげるだけですね。

例)先ほどの例のサーバー接続クラスに切断用のボタンを追加する。

```c#
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

using MonobitEngine;

 /// <summaryサーバーへ接続するためのクラス</summary>
public class ServerConnecter : MonobitEngine.MonoBehavior
{
    /// <summary>接続ボタンのオブジェクト</summary>
    private GameObject m_ConnectButton;

    /// <summary>切断ボタンのオブジェクト</summary>
    private GameObject m_DisconnectButton;

    // Start is called before the first frame update
    private void Start()
    {
        m_ConnectButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_ConnectButton.GetComponent<Button>().onClick.AddListener(OnClickConnect);

        m_DisconnectButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_DisconnectButton.GetComponent<Button>().onClick.AddListener(OnClickDisconnect);
    }

    // Update is called once per frame
    private void Update()
    {

    }

    /// <summary>接続ボタンを押した際に呼ばれる</summary>
    public void OnClickConnect()
    {
        // サーバーへ接続する
        MonobitNetwork.ConnectServer("v1.0");
    }

    /// <summary>切断ボタンを押した際に呼ばれる</summary>
    public void OnClickDisconnect()
    {
        if (!MonobitNetwork.isConnect){ return; }

       // MUNサーバから切断する
       MonobitNetwork.DisconnectServer();
    }

/****************************諸々省略***************************/

    /// <summary>サーバから切断したときに呼んでくれる</summary>
    public void OnDisconnectedFromServer()
    {
         Debug.Log("サーバーとの接続を切りました。");
    }
}
```
# ロビー入室/退室と関連のコールバック
[MUN技術情報ページ/用語集/ロビーについて](http://www.monobitengine.com/doc/mun/contents/Reference/Glossary.htm#%E3%83%AD%E3%83%93%E3%83%BC)
ルームへ入室する前の準備を行うような機能です。
ルームに対して複雑な条件での入室等が無い場合はデフォルトのロビーで実装します。

例)サーバー接続と共に自動でデフォルトのロビーへ入室する

```c#
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

using MonobitEngine;

 /// <summaryサーバーへ接続するためのクラス</summary>
public class ServerConnecter : MonobitEngine.MonoBehavior
{
    /// <summary>接続ボタンのオブジェクト</summary>
    private GameObject m_ConnectButton;

    /// <summary>切断ボタンのオブジェクト</summary>
    private GameObject m_DisconnectButton;

    // Start is called before the first frame update
    private void Start()
    {
        m_ConnectButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_ConnectButton.GetComponent<Button>().onClick.AddListener(OnClickConnect);

        m_DisconnectButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_DisconnectButton.GetComponent<Button>().onClick.AddListener(OnClickDisconnect);

        // 自動的にロビーに入室するように設定します。
        MonobitNetwork.autoJoinLobby = true;
    }

    // Update is called once per frame
    private void Update()
    {

    }

    /// <summary>接続ボタンを押した際に呼ばれる</summary>
    public void OnClickConnect()
    {
        // サーバーへ接続する
        MonobitNetwork.ConnectServer("v1.0");
    }

    /// <summary>切断ボタンを押した際に呼ばれる</summary>
    public void OnClickDisconnect()
    {
       // MUNサーバから切断する
       MonobitNetwork.DisconnectServer();
    }

    /****************************諸々省略***************************/

    /// <summary>サーバに接続＆認証完了したときに呼んでくれる</summary>
    //public void OnConnectedToServer()
    //{
    //    // サーバーの接続に成功したので次の画面へ移動
    //    SceneManager.LoadScene("SceneLobby");
    //}

    // ※自動ロビー入室をオンにした場合OnConnectedToServer()の代わりにOnJoinedLobby()が呼ばれるようになる
    /// <summary>ロビー入室に成功した場合に呼ばれる</summary>
    public void OnJoinedLobby()
    {
        // サーバーの接続に成功したので次の画面へ移動
        SceneManager.LoadScene("SceneLobby");
    }
}
```
<code>MonobitNetwork.autoJoinLobby</code>をtrueにすれば、後は<code>MonobitNetwork.ConnectServer</code>をするだけで自動でデフォルトのロビーへ入室されます。
ロビー退室処理についてはサーバー切断時に自動で退室されるため、ロビー退室時に何か処理が必要な場合に<code>public void OnLeftLobby()</code>を実装してあげてください。

# ルームの作成/入室/退室と関連のコールバック
[MUN技術情報ページ/ルームの作成](http://www.monobitengine.com/doc/mun/contents/FeatureClient/CreateRoom.htm)
ルームに入室することで、同室しているプレイヤー間でゲームの通信が本格的に行えるようになります。

例)ボタンを押すとルームが作成される

```c#
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

using MonobitEngine;

 /// <summarルーム作成を行うクラス</summary>
public class RoomCreater : MonobitEngine.MonoBehavior
{
    /// <summary>作成ボタンのオブジェクト</summary>
    private GameObject m_CreateButton;

    // Start is called before the first frame update
    private void Start()
    {
        // サーバーに接続できていないか、ロビーに入室できてない場合は処理させないようにする。
        if (!MonobitNetwork.isConnect || !MonobitNetwork.inLobby){ return; }

        m_CreateButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_CreateButton.GetComponent<Button>().onClick.AddListener(OnClickCreate);
    }

    // Update is called once per frame
    private void Update()
    {

    }

    /// <summary>作成ボタンを押した際に呼ばれる</summary>
    public void OnClickCreate()
    {
        RoomSettings settings = new MonobitEngine.RoomSettings();
        settings.maxPlayers = 4;
        settings.isVisible = true;
        settings.isOpen = true;

        MonobitNetwork.CreateRoom("TestRoom", settings, MonobitNetwork.lobby);
    }

    /// <summary>ルームを生成したときに呼んでくれる</summary>
    public void OnCreatedRoom()
    {
         SceneManager.LoadScene("SeceneGame");
    }

    /// <summary>ルームの作成に失敗した時に呼んでくれる</summary>
    public void OnCreateRoomFailed(object[] codeAndMsg)
    {
        Debug.Log("OnCreateRoomFailed : errorCode = " + codeAndMsg[0] + ", message = " + codeAndMsg[1]);
    }

    /// <summary>ルームに入室したときにに呼んでくれる、OnCreatedRoomが呼ばれれば十分な気がします</summary>
    public void OnJoinedRoom()
    {
        Debug.Log("OnJoinedRoom");
    }
}
```
<code>RoomSettings</code>で作成するルームの情報を作成し、<code>MonobitNetwork.CreateRoom</code>を呼ぶだけです。
後は作成の成功/失敗時に呼んでくれるコールバックを実装してあげます。

[MUN技術情報ページ/ルームの入室](http://www.monobitengine.com/doc/mun/contents/FeatureClient/JoinRoom.htm)
作成されたルームへ入室することができます。

例)ボタンを押すとルームへ入室する。

```c#
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

using MonobitEngine;

 /// <summarルーム入室を行うクラス</summary>
public class RoomEnter : MonobitEngine.MonoBehavior
{
    /// <summary>入室ボタンのオブジェクト</summary>
    private GameObject m_JoinButton;

    // Start is called before the first frame update
    private void Start()
    {
        // サーバーに接続できていないか、ロビーに入室できてない場合は処理させないようにする。
        if (!MonobitNetwork.isConnect || !MonobitNetwork.inLobby){ return; }

        m_JoinButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_JoinButton.GetComponent<Button>().onClick.AddListener(OnClickJoin);
    }

    // Update is called once per frame
    private void Update()
    {

    }

    /// <summary>入室ボタンを押した際に呼ばれる</summary>
    public void OnClickJoin()
    {
        MonobitNetwork.JoinRoom("TestRoom");
    }

    /// <summary>ルームに入室したときにに呼んでくれる</summary>
    public void OnJoinedRoom()
    {
        SceneManager.LoadScene("SeceneGame");
    }

    /// <summary>ルーム入室に失敗したときにに呼んでくれる</summary>
    /// <param name="codeAndMsg">失敗した際の情報</param>
    public void OnJoinRoomFailed(object[] codeAndMsg)
    {
        Debug.Log("OnJoinRoomFailed : errorCode = " + codeAndMsg[0] + ", message = " + codeAndMsg[1]);
    }
}
```
Createとほぼ同じで入室する関数を呼び、入室後に呼んでくれるコールバックを書くだけです。

[MUN技術情報ページ/ルームからの退室](http://www.monobitengine.com/doc/mun/contents/FeatureClient/LeaveRoom.htm)
現在入室しているルームから退室できます。

例)ボタンを押すとルームから退室する

```c#
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

using MonobitEngine;

 /// <summarゲームシーン</summary>
public class SceneGame: MonobitEngine.MonoBehavior
{
    /// <summary>退室ボタンのオブジェクト</summary>
    private GameObject m_LeftButton;

    // Start is called before the first frame update
    private void Start()
    {
        // サーバーに接続できていないか、ロビーに入室できてない、ルームに入室できていない場合は処理させないようにする。
        if (!MonobitNetwork.isConnect || !MonobitNetwork.inLobby || !MonobitNetwork.inRoom) { return; }

        m_LeftButton = Instantiate(Resources.Load("Button")) as GameObject;
        m_LeftButton.GetComponent<Button>().onClick.AddListener(OnClickLeft);
    }

    // Update is called once per frame
    private void Update()
    {

    }

    /// <summary>退室ボタンを押した際に呼ばれる</summary>
    public void OnClickLeft()
    {
        MonobitNetwork.LeftRoom();
    }

    /// <summary>ルームから退室したときに呼んでくれる</summary>
    public void OnLeftRoom()
    {
       SceneManager.LoadScene("SceneLobby");
    }
}
```

# 同期させたいオブジェクトの作成
[MUN技術情報ページ/ネットワーク越しのオブジェクトの生成](http://www.monobitengine.com/doc/mun/contents/FeatureClient/Instantiate.htm)
この機能を使い、自身と、それ以外のルームメンバーのゲーム内にオブジェクトを出現させることが出来ます。

例)ゲームシーン内にプレイヤーを出現させる

```c#
using UnityEngine;

using MonobitEngine;

public class SceneGame
{
    private GameObject m_OwnPlayerObj;

    private void Start()
    {
        m_OwnPlayerObj = MonobitEngine.MonobitNetwork.Instantiate("Player", Vector3.zero, Quaternion.identity, 0);
    }
}
```

## ネットワーク越しに作成されたオブジェクトの保持
<code>MonobitEngine.MonobitNetwork.Instantiate</code>はネットワーク越しに、すなわち自身以外のルームメンバーにもオブジェクトが自動で作成されます。
その際に作成されたオブジェクトを各ルームメンバーでも保持したいというときが出てくることがあると思います。

[MUN技術情報ページ/接続コールバック/OnMonobitInstantiate メソッド](http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnMonobitInstantiate%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)
この機能は、ネットワーク越しに作成された際に作成されたオブジェクトで呼ばれるコールバックです。
これを実装するこで、ネットワーク越しに自動で生成されたオブジェクトを取得することが簡単になります。

例)ホストが作成した敵のオブジェクトを、ホスト以外でも保持しておきたい。

```c#
using System.Collections;
using System.Collections.Generic;

using UnityEngine;

/// <summary>敵オブジェクトを保持するクラス</summary>
public static class EnemyCash
{
    /// <summary>敵を保持する本体</summary>
    private static List<GameObject> m_Enemies = List<GameObject>();

    /// <summary>M敵を保持する</summary>
    /// <param name="enemyObj">保持したい敵のオブジェクト</param>
    public static void Cash(GameObject enemyObj)
    {
        m_Enemies.Add(enemyObj);
    }

    /// <summary>保持している敵を削除する</summary>
    /// <param name="enemyObj">削除したい敵</param>
    public static void Release(GameObject enemyObj)
    {
         m_Enemies.Remove(enemyObj);
    }

    /// <summary>保持している敵をすべて削除</summary>
    public static void AllClear()
    {
        int length = m_Enemies.Count;
        for (int i = 0; i < lengtj; ++i)
        {
            m_Enemies.Remove(m_Enemies[i]);
        }
    }
}
```
こんな感じで敵を保持するクラスを用意したとします。
このクラスに敵を保持するために、ネットワーク越しで作成される敵の処理にコールバックを追加します。

```c#
using UnityEngine;

using MonobitEngine;

/// <summary>敵クラス</summary>
public class Enemy : MonobitEngine.MonoBehavior
{
    // Start is called before the first frame update
    private void Start()
    {

    }

    // Update is called once per frame
    private void Update()
    {

    }

    /// <summary>MonobitNetwork.Instantiateによってインスタンス化された際に呼ばれるコールバック</summary>
    /// <param name="info">インスタンス化を行ったクライアントの情報</param>
    public void OnMonobitInstantiate(MonobitEngine.MonobitMessageInfo info)
    {
        // 今回の例だと、ホストが作成元
        // ホストは既にMonobitNetwork.Instantiateの戻り値で敵を保持していることが多いため、
        // ホスト以外で保持するようにしている。
        if (MonobitNetwork.isHost) { return; }

        // ネットワーク越しに作成された敵のgameObjectを保持する
        EnemyCash.Cash(gameObject);
    }
}
```
こんな感じでネットワーク越しに作成するオブジェクトにOnMonobitInstantiateコールバックを書き、後はその中でgameObjectを取得することで、
ネットワーク越しに作られたオブジェクトを作成元以外でも保持することが出来ます。
主にホストマイグレーション的な機能を実装した際に、ホスト以外のメンバーもオブジェクトを保持していないと不都合が出る場合などでこういった方法を取りました。

## オブジェクトの所有権
[MUN技術情報ページ/オブジェクト所有権チェック](http://www.monobitengine.com/doc/mun/contents/FeatureClient/IsMine.htm)
<code>MonobitEngine.MonobitNetwork.Instantiate</code>によって作成されたオブジェクトは所有権というものが割り当てられます。
所有権は呼び出し元のクライアントに自動で割り当てられます。
この所有権を持つものが原則オブジェクトの操作を行うように実装していくのが基本です。

さっき例に使った敵クラスで所有権を持つもののみが操作できるような実装を加えてみます。

```c#
using UnityEngine;

using MonobitEngine;

/// <summary>敵クラス</summary>
public class Enemy : MonobitEngine.MonoBehavior
{
    // Start is called before the first frame update
    private void Start()
    {

    }

    // Update is called once per frame
    private void Update()
    {
        // 所有権が自分にあるものだけ自分で操作させる
        // 自分のものではないもに関しては同期されて動く
        if (!monobitView.isMine){ return; }

        /******************移動や攻撃といった処理*******************/
    }
}
```
<code>monobitView.isMine</code>をUpdateの先頭に書いてるだけですが、これで自分以外は操作できないようにできます。


## オブジェクトの所有権を持つメンバーが居なくなってしまった場合
![キャプチャ.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469983/75b21aa4-0a41-2b4b-1f09-bc9e942f02e6.jpeg)
所有権を持つメンバーがもし、アプリを閉じてしまうなどの不慮の事故に見舞われてしまった場合、所有していたオブジェクトは消えてしまいます。
大体の実装ではホストが敵やアイテムといった同期オブジェクトの所有権を持っているため、もしそのホストが不慮の事故でいなくなってしまうと、ゲームが成り立たなくなります。

そういった状況を防ぐために、所有者が居なくなっていてもオブジェクトが消えないようにする実装が必要です。

```c#:
// string prefabName        :インスタンス化したいプレハブ名
// Vector3 position         :インスタンス化後に置かれる初期位置
// Quaternion rotation      :インスタンス化後に適用される姿勢
// int 	group               :ルーム内で振り分けられているグループ番号。ルーム内で更にグループ分けするときとかに数値を変える。基本は0でいい。
// object[] data            :ネットワーク越しにインスタンス化されたオブジェクトに任意でデータを付属できる。
// bool isDontDestroyOnLoad :シーンの切り替え時にオブジェクトが削除されるかを設定できる。
// bool isMine              :インスタンス化されたオブジェクトの所有権を設定できる。
// bool isDontDestroyOnRoom :作成元がルームから抜けた後オブジェクトが削除されるかどうかを設定できる。
MonobitEngineBase.MonobitNetwork.Instantiate("prefub", Vector3.zero, Quaternion.identity, 0, null, false, false, true);
```
この機能を上記で記述したInstantiateの代わりに使うことで、ルーム内から所有者が居なくなってもオブジェクトが残り続けるようにできます。
最後の引数のisDontDestroyOnRoomをtrueにすることで所有者が居なくなってもオブジェクトは削除されないようになります。
<code>monobitView.isMine</code>で操作を所有権をもつもののみに絞りたい場合は引数のisMineをtrueにしてください。

# RPC
[MUN技術情報ページ/RPC(Remote Procedure Call)](http://www.monobitengine.com/doc/mun/contents/FeatureClient/RPC.htm)
ネットワーク越しに処理を呼び出させる機能です。ゲーム中の値の同期やイベントの共有/通知などに使います。

例)すべてのルームメンバー(自分も含め)に数字と文字列を通知するRPCを飛ばす

```c#
using UnityEngine;

using MonobitEngine;

/// <summary>RPCを使用するクラス</summary>
public class RPCTester : MonobitEngine.MonoBehavior
{
    /// <summary>数字と文字列を送るRPCを送信する</summary>
    private void TestRPC()
    {
        monobitView.RPC("RecvTestRPC", MonobitTargets.All, 8931, "はくさい");
    }

    /// <summary>送られてきたRPCの処理を行う</summary>
    /// <param name="recvVal">TestRPC()のRPCで設定された8931を受け取る引数</param>
    /// <param name="recvStr">TestRPC()のRPCで設定された"はくさい"を受け取るための引数</param>
    [MunRPC]
    private void RecvTestRPC(int recvVal, string recvStr)
    {
        Debug.Log("Receive RPC! " + recvVal + ", " + recvStr);
    }
}
```
注意点として、RPCを使用するオブジェクトは、Instantiate、もしくはMonobitNetwork.Instantiateでインスタンス化しておく必要があります。
プレハブ化し、エディターのinspector上にD&Dするなどで直に作成した場合、RPCが上手く送信されないことがあるようです。

# RPC以外の同期方法
[MUN技術情報ページ/[Tips] RPCメッセージの送信量・頻度の軽減](http://www.monobitengine.com/doc/mun/contents/PerformanceTuning/TipsRPC.htm)
[MUN技術情報ページ/[Tips] オブジェクト同期の送信量・頻度の軽減](http://www.monobitengine.com/doc/mun/contents/PerformanceTuning/TipsObjectStream.htm)
この辺りに書かれている方法になります。主にRPCの代わりに実装しRPCの検索処理を減らす際や、データのサイズ/送信量を減らすなどに役立ちます。

## プレイヤーカスタムパラメーターによる同期
[MUN技術情報ページ/プレイヤーカスタムパラメータ](http://www.monobitengine.com/doc/mun/contents/FeatureClient/PlayerCustomParameter.htm)
各クライアント自身に持たせることができ、ルーム内で他のメンバーに共有することが出来るデータを設定するものです。
これを使いHPや自身の持つスコアといったものの値の同期を実装することも可能です。

例)プレイヤーで体力, 持ち点, 死亡回数を共有したい場合

```c#
using System.Collections;
using System.Collections.Generic;

using UnityEngine;

using MonobitEngine;

/// <summary>プレイヤークラス</summary>
public class Player : MonobitEngine.MonoBehavior
{
    /// <summary>体力</summary>
    private int m_HP = 8;

    /// <summary>死んだ回数</summary>
    private int m_DeadCount = 0;

    /// <summary>持ち点</summary>
    private int m_Score = 0;

    /// <summary>プレイヤーカスタムパラメータ更新用</summary>
    private Hashtable m_PlayerCustomParams = new Hashtable();

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

        m_PlayerCustomParams["hp"] = m_HP;
        m_PlayerCustomParams["deadCount"] = m_DeadCount;
        m_PlayerCustomParams["score"] = m_Score;

        MonobitNetwork.SetPlayerCustomParameters(playerCustomParams);
    }

    // Update is called once per frame
    private void Update()
    {
        if (!monobitView.isMine) { return; }
    }

    /// <summary>ダメージ処理</summary>
    /// <param name="damage">被ダメージ数値</param>
    public void Damage(int damage)
    {
        if (!monobitView.isMine) { return; }

        m_HP -= damage;

        playerCustomParams["hp"] = m_HP;

        MonobitNetwork.SetPlayerCustomParameters(playerCustomParams);
    }

    /// <summary>死亡処理</summary>
    public void Dead()
    {
        if (!monobitView.isMine) { return; }

        ++m_DeadCount;

        playerCustomParams["deadCount"] = m_DeadCount;

        MonobitNetwork.SetPlayerCustomParameters(playerCustomParams);
    }

    /// <summary>スコア加算</summary>
    /// <param name="addScore">加算するスコア</param>
    public void AddScore(int addScore)
    {
        if (!monobitView.isMine) { return; }

        m_Score += addScore

        playerCustomParams["score"] = m_Score;

        MonobitNetwork.SetPlayerCustomParameters(playerCustomParams);
    }
}
```
こんな感じでプレイヤーカスタムパラメータに値を設定し、更新させることでRPCの代わりに値を共有することも可能です。
プレイヤー内でRPCが複数実装されてる場合はこういった実装に変えてみるのも手かなといった感じですね。

## ルームカスタムパラメーターによる同期
[MUN技術情報ページ//ルームカスタムパラメータ](http://www.monobitengine.com/doc/mun/contents/FeatureClient/RoomCustomParameter.htm)
ルーム内のメンバーで共有できるルームの追加情報で、普通はルームの設定で扱うことが多いです。(ルームに入れるレベルの制限等)
このルームカスタムパラメータを使い、ルーム内共通の値の同期を行なうこともできます。

例)ゲーム内で共通のスコア, 経過時間を共有する場合

```c#
using System.Collections;
using System.Collections.Generic;

using UnityEngine;

using MonobitEngine;

public class SceneGame : MonobitEngine.MonoBehavior
{
    private int m_TotalScore;

    private float m_Time;

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

        Hashtable roomCustomParams = MonobitNetwork.room.customParameters;
        
        roomCustomParams["totalScore"] = m_TotalScore;
        
        roomCustomParams["time"] = m_Time;

        MonobitNetwork.room.SetCustomParameters(roomCustomParams);
    }

    // Update is called once per frame
    private void Update()
    {
        if (!MonobitNetwork.isHost) { return; }

        m_Time += Time.deltaTime;

        Hashtable roomCustomParams = MonobitNetwork.room.customParameters;

        roomCustomParams["time"] = m_Time;

        MonobitNetwork.room.SetCustomParameters(roomCustomParams);
    }

    public void AddScore(int addScore)
    {
        if (!MonobitNetwork.isHost) { return; }

        m_TotalScore += addScore;

        Hashtable roomCustomParams = MonobitNetwork.room.customParameters;

        roomCustomParams["totalScore"] = m_TotalScore;
        
        MonobitNetwork.room.SetCustomParameters(roomCustomParams);
    }
}
```
プレイヤーカスタムパラメータと同様でRPCの削減の手の一つですね。

## OnMonobitSerializeView
[MUN技術情報ページ/接続コールバック/OnMonobitSerializeView](http://www.monobitengine.com/doc/mun/contents/FeatureClient/CallbackFunction.htm#OnMonobitSerializeView%20%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)
MonobitViewがアタッチされているオブジェクトの同期毎に呼ばれるコールバックです。
引数のstreamに同期させたい値を書き込むことで、同期を行なわせることが出来ます。

例)プレイヤーの値の同期

```c#
using UnityEngine;

using MonobitEngine;

/// <summary>プレイヤークラス</summary>
public class Player : MonobitEngine.MonoBehavior
{
    /// <summary>状態列挙</summary>
    public enum State
    {
        Deactive = 0,
        Active = 1,
        Dead = 2
    }

    /// <summary>状態</summary>
    private State m_State = State.Deactive;

    /// <summary>体力</summary>
    private int m_HP = 8;

    /// <summary>死んだ回数</summary>
    private int m_DeadCount = 0;

    /// <summary>持ち点</summary>
    private int m_Score = 0;

    /// <summary>プレイヤーカスタムパラメータ更新用</summary>
    private Hashtable m_PlayerCustomParams = new Hashtable();

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

    // Update is called once per frame
    private void Update()
    {
        if (!monobitView.isMine) { return; }

        switch(m_State) 
        {
            case State.Deactive:
                 break;

            case State.Active:
                 break;

            case State.Dead:
                 break;

            default:
                 break;
        }
    }

    /// <summary>ダメージ処理</summary>
    /// <param name="damage">被ダメージ数値</param>
    public void Damage(int damage)
    {
        if (!monobitView.isMine) { return; }

        m_HP -= damage;
    }

    /// <summary>死亡処理</summary>
    public void Dead()
    {
        if (!monobitView.isMine) { return; }

        ++m_DeadCount;

        m_State = State.Dead;
    }

    /// <summary>スコア加算</summary>
    /// <param name="addScore">加算するスコア</param>
    public void AddScore(int addScore)
    {
        if (!monobitView.isMine) { return; }

        m_Score += addScore
    }

    /// <summary></summary>
    /// <param name="stream"></param>
    /// <param name="info"></param>
    public void OnMonobitSerializeView(MonobitEngine.MonobitStream stream, MonobitEngine.MonobitMessageInfo info)
    {
        // 書き込み手の場合
        if (stream.isWriting)
        {
            stream.Enqueue(transform.position.x);
            stream.Enqueue(transform.position.y);
            stream.Enqueue((int)m_State);
            stream.Enqueue(m_HP);
            stream.Enqueue(m_DeadCount);
            stream.Enqueue(m_Score);
        }
        else // 受け取り手の場合
        {
            float x = (int)stream.Dequeue();
            float x = (int)stream.Dequeue();
            transform.position = new Vector3(x, y, 0.0f);

            m_State = (State)stream.Dequeue();

            m_HP = (int)stream.Dequeue();

            m_DeadCount = (int)stream.Dequeue();

            m_Score = (int)stream.Dequeue();
        }
    }
}
```
OnMonobitSerializeViewで共有したい値をstreamに全て書き込んでしまいます。
後は受け取り手で読み込んであげるだけで値の同期が可能です。
MonobitTransformViewを使った位置同期だと無駄な場合(2Dのゲームとか)にこっちを使ったりします。
2Dのアニメーション制御とかもこれでやるといいかもしれせん。

纏めは以上になります。
これでオンラインゲームに関しての記事は多分恐らくきっと最後になります。
また何か作ろうかと思った際は備忘録的に記事にしていこうかなと思います。

# 今までのやつ
細かくは書けてないとおもうので、ちょっと踏み込んだ実装を知りたいときは、今までのを見ていただければ書いているかと思います。

[Unityでオンラインマルチプレイなゲームを作りたい その1 ネットワークエンジンの選択](https://qiita.com/koyuki2007/items/180be375eeca9108c687)
[Unityでオンラインマルチプレイなゲームを作りたい その2 環境導入](https://qiita.com/koyuki2007/items/15bf4cd834722e8b47ab)
[Unityでオンラインマルチプレイなゲームを作りたい その3 サーバー接続](https://qiita.com/koyuki2007/items/247c13603244020f6bb8)
[Unityでオンラインマルチプレイなゲームを作りたい その4 ロビー入室](https://qiita.com/koyuki2007/items/f0e16adc3938081eb06e)
[Unityでオンラインマルチプレイなゲームを作りたい その5 ルーム作成](https://qiita.com/koyuki2007/items/b537549767d003ac96e4)
[Unityでオンラインマルチプレイなゲームを作りたい その6 ルーム入室](https://qiita.com/koyuki2007/items/2fc0bfaf040589ee4ce8)
[Unityでオンラインマルチプレイなゲームを作りたい その7 ゲーム開始前のルーム待機](https://qiita.com/koyuki2007/items/20713d4a50ca31f7ed26)
[Unityでオンラインマルチプレイなゲームを作りたい その8 プレイヤーの同期](https://qiita.com/koyuki2007/items/e7d16e020539f38c6e7c)
[Unityでオンラインマルチプレイなゲームを作りたい その9 弾の同期](https://qiita.com/koyuki2007/items/eb612aa7bde99c014141)
[Unityでオンラインマルチプレイなゲームを作りたい その10 敵の同期](https://qiita.com/koyuki2007/items/1ea6538bc0b0c13fc25e)
[Unityでオンラインマルチプレイなゲームを作りたい その11 敵の弾の同期](https://qiita.com/koyuki2007/items/09e7342badff41fd7b24)
[Unityでオンラインマルチプレイなゲームを作りたい その12 ダンジョンの生成同期](https://qiita.com/koyuki2007/items/5862b36a56d577e4f54c)
[Unityでオンラインマルチプレイなゲームを作りたい その13 当たり判定について](https://qiita.com/koyuki2007/items/5b582cdd807bcaa9fb6b)
[Unityでオンラインマルチプレイなゲームを作りたい その14 リザルト画面](https://qiita.com/koyuki2007/items/79888c27688d7e52a530)

10以上も書いてしまってました...。一通り作り終えてからこの纏めだけ書くだけでよかったような気がしなくもないですね...。

# 参考
http://www.monobitengine.com/doc/mun/contents/Introduction/Top.htm