Unityでオンラインマルチプレイなゲームを作りたい その11 敵の弾の同期

[前回の記事](https://qiita.com/koyuki2007/items/1ea6538bc0b0c13fc25e)で敵の作成を行いました。

今回は敵の弾を実装していこうと思います。
とはいえ、やることは[その9 弾の同期](https://qiita.com/koyuki2007/items/eb612aa7bde99c014141)と同じなので、もうちょっといい感じに実装してみます。

# 今回の目標
敵が発射する弾を作成します。ついでに弾の発射同期についてを少し変えた実装にします。

## 実装方法
[[Tips] RPCメッセージの送信量・頻度の軽減](http://www.monobitengine.com/doc/mun/contents/PerformanceTuning/TipsRPC.htm)
ここを参考に弾の発射同期をパフォーマンス改善も含めて実装していこうと思います。
MonobitEngine.MonoBehaviour及びMonobitViewを持っているオブジェクトが多くインスタンス化されてるとその分だけRPCを検索をするようになっているみたいです。
今回の実装内容的には敵のオブジェクトがこれに該当するのですが、流石に同期を行なうオブジェクト故この二つを外すのは難しいです。

※ドキュメントから一部コード抜粋

```c#
// シーン内の全てのMonoBehaviourから[MunRPC]のアトリビュート付きのメソッドを探索する
var result = MethodsCache.TryGetValue(monoType, out methodList);
if (result != true)
{
    MethodInfo[] methods = monoType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
    foreach (MethodInfo method in methods)
    {
        if (method.IsDefined(typeof(MunRPC), false))
            methodList.Add(method);
    }
    MethodsCache[monoType] = methodList;
}
if (methodList == null || methodList.Count == 0)
            continue;
 
// メソッド名が同一であるものを抽出する
var selectInfo = methodList.Where(m => m.Name.Equals(methodName));
if (selectInfo == null) continue;
```
ドキュメントのソースのここを見る限りだと、何かしらRPCを持っていると呼びたいRPCかどうかの確認が行われるようです。
そして1つのインスタンスに対してRPCが複数あると、その分だけ検索に処理が生じそうです。
一つのインスタンスが所持してるRPCを減らすか無くすだけで多少効果はありそうです。

ということで、敵の持つ弾の発射RPCを個々のインスタンスではなく別の一つのインスタンスで行えるような実装にしていこうと思います。

※[前回の記事](https://qiita.com/koyuki2007/items/1ea6538bc0b0c13fc25e)のEnemyの処理のショット部分のみを抜粋しております

```c#:Enemy.cs
/// <summary>弾の発射</summary>
    private void Shot()
    {
        m_ShotIntervalCount += Time.deltaTime;

        if (m_ShotIntervalCount < m_ShotInterval) { return; }

        m_ShotIntervalCount = 0.0f;

        float baseAngle = m_Angle - (m_ShotAngleRange * 0.5f);

        float angleAmount = (m_ShotWay <= 1) ? m_ShotAngleRange : m_ShotAngleRange / (m_ShotWay - 1);

        for (int i = 0; i < m_ShotWay; ++i)
        {
            monobitView.RPC("RecvShot", MonobitTargets.All, transform.localPosition, baseAngle + (angleAmount * i));
        }      
    }

    /// <summary>弾を発射するRPC</summary>
    /// <param name="pos">弾の発射位置</param>
    /// <param name="angle">弾の発射方向</param>
    [MunRPC]
    private void RecvShot(Vector3 pos, float angle)
    {
        Debug.Log("Shot Enemy Bullet!");
    }
```
ここですね。ここで実装しているRPCを別のインスタンスへ移してしまいます。

まず、[その9 弾の同期](https://qiita.com/koyuki2007/items/eb612aa7bde99c014141)を参考にEnemyBulletとEnemyBulletsPool的なものを作っておいてください。

続いて、Enemy.csで書かれていたRPCを移植し、弾を発射するRPCを管理するような機能を作ります。

```c#:Syncer.cs
using UnityEngine;
using MonobitEngine;

/// <summary></summary>
public class Syncer : MonobitEngine.MonoBehaviour
{
    /// <summary>敵弾プールのオブジェクト</summary>
    private GameObject m_EnemyBulletsPoolObj;

    /// <summary>敵弾プール</summary>
    private EnemyBulletsPool m_EnemyBulletsPool;

    // Start is called before the first frame update
    void Start()
    {
        m_EnemyBulletsPoolObj = Instantiate(Resources.Load("EnemyBulletsPool"), Vector3.zero, Quaternion.identity);

        m_EnemyBulletsPool = m_EnemyBulletsPoolObj.GetComponent<EnemyBulletsPool>();
    }

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

    }

    /// <summary>弾を発射させる</summary>
    /// <param name="pos">発射位置</param>
    /// <param name="angle">発射方向</param>
    public void EnemyShot(Vector3 pos, float angle)
    {
        monobitView.RPC("RecvEnemyShot", MonobitTargets.All, pos, angle);
    }

    /// <summary>弾を発射させる</summary>
    /// <param name="pos">発射位置</param>
    /// <param name="angle">発射方向</param>
    [MunRPC]
    public void RecvEnemyShot(Vector3 pos, float angle)
    {
        m_EnemyBulletsPool.Shot(pos, angle);
    }
}
```
敵弾プールを持たせ、RPC処理を移しただけです。
このスクリプトをMonobitViewといっしょに空のオブジェクトにでも追加しておいてください。

続いてEnemy.csのRPCが記述されいた部分を変更します。

```c#:Enemy.cs
/// <summary>弾の発射</summary>
    private void Shot()
    {
        m_ShotIntervalCount += Time.deltaTime;

        if (m_ShotIntervalCount < m_ShotInterval) { return; }

        m_ShotIntervalCount = 0.0f;

        float baseAngle = m_Angle - (m_ShotAngleRange * 0.5f);

        float angleAmount = (m_ShotWay <= 1) ? m_ShotAngleRange : m_ShotAngleRange / (m_ShotWay - 1);

        // 先ほど作成したSyncerを取得します。
        Syncer syncer = GameObject.FindWithTag("Syncer").GetComponent<Syncer>();

        for (int i = 0; i < m_ShotWay; ++i)
        {
            // 取得したSyncerで実装されているShot()に位置と方向を渡して呼んであげる。
            syncer.Shot(transform.localPosition, baseAngle + (angleAmount * i));
        }      
    }

    /// <summary>弾を発射するRPC</summary>
    /// <param name="pos">弾の発射位置</param>
    /// <param name="angle">弾の発射方向</param>
    //[MunRPC]
    //private void RecvShot(Vector3 pos, float angle)
    //{
    //    Debug.Log("Shot Enemy Bullet!");
    //}
```
RPCが不要になったのでコメントアウトにしています。
<code>Shot()</code>関数内でSyncerを利用し弾をネットワーク越しに発射させるようにしました。

## もう一つのメリット
実は先ほどの実装方法を採用した理由がもう一つありまして、
[[Tips] RPCメッセージの送信量・頻度の軽減/Tips(1) : シーン内の情報同期が目的である場合、MonobitView コンポーネントを持つ、単独の静的シーンオブジェクトでRPCメッセージをやり取りする](http://www.monobitengine.com/doc/mun/contents/PerformanceTuning/TipsRPC.htm#Tips(1)%20:%20%E3%82%B7%E3%83%BC%E3%83%B3%E5%86%85%E3%81%AE%E6%83%85%E5%A0%B1%E5%90%8C%E6%9C%9F%E3%81%8C%E7%9B%AE%E7%9A%84%E3%81%A7%E3%81%82%E3%82%8B%E5%A0%B4%E5%90%88%E3%80%81MonobitView%20%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E6%8C%81%E3%81%A4%E3%80%81%E5%8D%98%E7%8B%AC%E3%81%AE%E9%9D%99%E7%9A%84%E3%82%B7%E3%83%BC%E3%83%B3%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A7RPC%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E3%82%84%E3%82%8A%E5%8F%96%E3%82%8A%E3%81%99%E3%82%8B)
![aaaaaa.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469983/8f2c6bf7-098c-7bfc-9c94-6110cd9b5f3f.jpeg)
ドキュメントのこの箇所のようなパフォーマンス改善の実装に組み込みやすくしています。

```c#:Syncer.cs
using UnityEngine;
using MonobitEngine;

public enum RPCType
{
    AddScore = 0,
    Spawn = 1,
    Chat = 2,
    PlayerShot = 3,
    EnemyShot = 4
}

/// <summary>RPCを専門に行う</summary>
public class Syncer : MonobitEngine.MonoBehaviour
{
    /// <summary>プレイヤー弾のプールのオブジェクト</summary>
    private GameObject m_PlayerBulletsPoolObj;

    /// <summary>プレイヤー弾のプール</summary>
    private PlayerBulletsPool m_PlayerBulletsPool;

    /// <summary>敵弾プールのオブジェクト</summary>
    private GameObject m_EnemyBulletsPoolObj;

    /// <summary>敵弾プール</summary>
    private EnemyBulletsPool m_EnemyBulletsPool;

    // Start is called before the first frame update
    void Start()
    {
        m_PlayerBulletsPoolObj = Instantiate(Resources.Load("PlayerBulletsPool"), Vector3.zero, Quaternion.identity);

        m_PlayerBulletsPool = m_PlayerBulletsPoolObj.GetComponent<PlayerBulletsPool>();

        m_EnemyBulletsPoolObj = Instantiate(Resources.Load("EnemyBulletsPool"), Vector3.zero, Quaternion.identity);

        m_EnemyBulletsPool = m_EnemyBulletsPoolObj.GetComponent<EnemyBulletsPool>();
    }

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

    }

    /// <summary>ネットワーク越しにスコア加算をさせる</summary>
    /// <param name="score">加算するスコア</param>
    public void AddScore(int score)
    {
        monobitView.RPC("OnReceive", MonobitTargets.All, (int)RPCType.AddScore, score, null);
    }

    /// <summary>ネットワーク越しに敵を出現させる</summary>
    public void Spawn()
    {
         monobitView.RPC("OnReceive", MonobitTargets.All, (int)RPCType.Spawn, null, null);
    }

    /// <summary>チャットを送信する</summary>
    /// <param name="chat">送信するチャット</param>
    public void Chat(string chat)
    {
         monobitView.RPC("OnReceive", MonobitTargets.All, (int)RPCType.Chat, chat, null);
    }

    /// <summary>弾を発射させる</summary>
    /// <param name="pos">発射位置</param>
    /// <param name="angle">発射方向</param>
    public void PlayerShot(Vector3 pos, float angle)
    {
        monobitView.RPC("OnReceive", MonobitTargets.All, (int)RPCType.PlayerShot, pos, angle);
    }

    /// <summary>弾を発射させる</summary>
    /// <param name="pos">発射位置</param>
    /// <param name="angle">発射方向</param>
    public void EnemyShot(Vector3 pos, float angle)
    {
        monobitView.RPC("OnReceive", MonobitTargets.All, (int)RPCType.EnemyShot, pos, angle);
    }

    /// <summary>受信したRPCの分岐</summary>
    /// <param name="rpcId">RPCの種類</param>
    /// <param name="param1">RPCのデータ1</param>
    /// <param name="Param2">RPCのデータ2</param>
    [MunRPC]
    public void OnReceive(int rpcId, object param1, object Param2)
    {
        switch((RPCType)rpcId)
        {
           case RPCType.AddScore: RecvAddScore((int)param1); break;
           case RPCType.Spawn: RecvSpawn(); break;
           case RPCType.Chat: RecvChat((string)param1); break;
           case RPCType.PlayerShot: RecvPlayerShot((Vector3)param1, (float)param2); break;
           case RPCType.EnemyShot: RecvEnemyShot((Vector3)param1, (float)param2); break;
           default:break;
        }
    }

    /// <summary>スコアの加算</summary>
    /// <param name="score">加算するスコア</param>
    private void RecvAddScore(int score)
    {
       // スコア加算の処理
    }

    /// <summary>敵の出現</summary>
    private void RecvSpawn()
    {
        // 敵の出現処理
    }

    /// <summary>チャットの受信</summary>
    /// <param name="score">チャット</param>
    private void RecvChat(string chat)
    {
        // チャット処理
    }

    /// <summary>プレイヤーの弾を発射させる</summary>
    /// <param name="pos">発射位置</param>
    /// <param name="angle">発射方向</param>
    private void RecvPlayerShot(Vector3 pos, float angle)
    {
        m_PlayerBulletsPool.Shot(pos, angle);
    }

    /// <summary>敵の弾を発射させる</summary>
    /// <param name="pos">発射位置</param>
    /// <param name="angle">発射方向</param>
    private void RecvEnemyShot(Vector3 pos, float angle)
    {
        m_EnemyBulletsPool.Shot(pos, angle);
    }
}
```
こんな感じでRPCを一つのMonobitViewで管理したときに簡単に組み込めます。
使い方は、さっきとは変わらないですね。

これで敵の弾を実装できました。
次回はダンジョンの生成同期について書きたいと思います。

# 参考
http://www.monobitengine.com/doc/mun/contents/PerformanceTuning/TipsRPC.htm
http://www.monobitengine.com/doc/mun/contents/PerformanceTuning/TipsRPC.htm#Tips(1)%20:%20%E3%82%B7%E3%83%BC%E3%83%B3%E5%86%85%E3%81%AE%E6%83%85%E5%A0%B1%E5%90%8C%E6%9C%9F%E3%81%8C%E7%9B%AE%E7%9A%84%E3%81%A7%E3%81%82%E3%82%8B%E5%A0%B4%E5%90%88%E3%80%81MonobitView%20%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E6%8C%81%E3%81%A4%E3%80%81%E5%8D%98%E7%8B%AC%E3%81%AE%E9%9D%99%E7%9A%84%E3%82%B7%E3%83%BC%E3%83%B3%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A7RPC%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E3%82%92%E3%82%84%E3%82%8A%E5%8F%96%E3%82%8A%E3%81%99%E3%82%8B