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

[前回の記事](https://qiita.com/koyuki2007/items/e7d16e020539f38c6e7c)でプレイヤーの同期を実装しました。
今回はプレイヤー同期で一旦保留した弾の発射部分を実際に弾を出すようにしていきたいと思います。

# 今回の目標
プレイヤー弾を実装します。

## 弾の位置同期について
プレイヤーオブジェクトと同じ方法で<code>MonobitNetwork.Instantiate()</code>を使えば実装は可能ですが、弾の飛ぶ数が多いと一つ一つ生成と削除を行うことになるので効率が悪いです。
更に、多く弾が存在しているとその分だけ位置やら回転やらの同期も行うことになります。
ということでこれらを解消できる方法で実装しようと思います。

## シンプルな動作の弾は位置の同期は考えなくていい
等速直線運動で進行方向に変化がない弾に関しては、発射位置と発射の向きだけ発射タイミング時に渡してあげれば大体同期できているように見えるため、MonobitView及びMonobitTransformViewによる同期の方法を取る必要はありません。

## オブジェクトプールによる弾の管理
Instantiateを行う回数を減らすために予め大量に弾を作成しておき、アクティブ/非アクティブに切り替えて弾を扱います。

# 実装
これでInstantiateの利用回数の削減と同期の削減が出来そうなので、これらを踏まえて実装していきます。

とりあえず発射する弾のクラスを実装します。

```c#:PlayerBullet.cs
using UnityEngine;

/// <summary>プレイヤーの弾</summary>
public class PlayerBullet : MonoBehaviour
{
    /// <summary>移動量</summary>
    private Vector3 m_MovementAmount = Vector3.zero;

    /// <summary>移動方向(Degree)</summary>
    private float m_DegAngle;

    /// <summary>弾の生存時間</summary>
    private float m_ActiveTime = 3.0f;

    /// <summary>弾の生存時間カウント</summary>
    private float m_ActiveTimeCount = 0.0f;

    private void Start()
    {

    }
        
    /// <summary></summary>
    private void FixedUpdate()
    {
        Move();

        m_ActiveTimeCount += Time.deltaTime;

        if (m_ActiveTimeCount < m_ActiveTime ) { return; }

        m_ActiveTimeCount = 0.0f;

        gameObject.SetActive(false);
    }

    private void OnEnable()
    {
        
    }

    private void OnDisable()
    {
        transform.localPosition = Vector2.zero;

        m_MovementAmount = Vector3.zero;

        m_DegAngle= 0.0f;
    }

    /// <summary></summary>
    private void Move()
    {
        m_MovementAmount.x = Mathf.Cos(m_DegAngle);
        m_MovementAmount.y = Mathf.Sin(m_DegAngle);

        transform.localPosition += m_MovementAmount * m_Velocity * Time.deltaTime;
    }

    /// <summary></summary>
    public void Shot(Vector3 pos, float angle)
    {    
        transform.localPosition = pos;
  
        m_DegAngle= angle * Mathf.Deg2Rad;
     }
}
```
位置等を同期する必要がないので、MUNの機能は使わずにシンプルな処理になっています。

続いてこの弾を管理するクラスを作ります。

```c#:PlayerBulletsPool.cs
using UnityEngine;

/// <summary>プレイヤー弾を管理する</summary>
public class PlayerBulletsPool : MonoBehaviour
{
    /// <summary>プレイヤー弾のプレハブ</summary>
    private Object m_BulletPrefub;

    /// <summary>プレイヤー弾</summary>
    private GameObject[] m_Bullets;

    /// <summary>プレイヤー弾の最大数</summary>
    private const int MAX_BULLETS_COUNT = 512;

    // Start is called before the first frame update
    void Start()
    {
        // プレイヤー弾のプレハブを読み込み
        m_BulletPrefub = Resources.Load("PlayerBullet");

        m_Bullets = new GameObject[MAX_BULLETS_COUNT];
        for (int i = 0; i < MAX_BULLETS_COUNT; ++i)
        {
            m_Bullets[i] = GameObject.Instantiate(m_BulletPrefub, Vector3.zero, Quaternion.identity) as GameObject;
            m_Bullets[i].transform.SetParent(transform);
            m_Bullets[i].SetActive(false);
        }
    }

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

    }

    /// <summary>弾を発射させる</summary>
    /// <param name="pos">発射位置</param>
    /// <param name="angle">発射方向</param>
    public void Shot(Vector3 pos, float angle)
    {
        for (int i = 0; i < MAX_BULLETS_COUNT; ++i)
        {
            if (m_Bullets[i].activeSelf) { continue; }

            m_Bullets[i].SetActive(true);

            m_Bullets[i].GetComponent<PlayerBullet>().Shot(pos, angle);

            break;
        }
    }
}
```
こちらもいたってシンプルです、非アクティブな弾を探してアクティブにしてあげるだけですね。
<code>Shot()</code>関数をRPCで呼び出すことで、各ルームメンバーで弾の発射同期を行なわせます。

弾を発射できる準備が整ったので[前回](https://qiita.com/koyuki2007/items/e7d16e020539f38c6e7c)のプレイヤークラスに加えていきます。
※前回から変更の無い所は省略しています。

```c#:Player.cs
using UnityEngine;

using MonobitEngine;

 /// <summary>プレイヤーの制御</summary>
public class Player : MonobitEngine.MonoBehaviour
{
    /// <summary>移動方向</summary>
    private Vector3 m_MovementAmount = Vector3.zero;

    /// <summary>移動速度</summary>
    private float m_Velocity = 1.0f;

    /// <summary>発射間隔</summary>
    private float m_ShotInterval = 0.1f;

    /// <summary>発射間隔のカウント</summary>
    private float m_ShotIntervalCount = 0.0f;

  /// <summary>発射間隔のカウント</summary>
    private Vector3 m_ShotIntervalCount = 0.0f;

  /// <summary>弾の発射向き</summary>
    private float m_Angle = 0.0f;

    /// <summary>移動</summary>
    private void Move()
    {
        m_MovementAmount.x = Input.GetAxis("Horizontal");
        m_MovementAmount.y = Input.GetAxis("Vertical");

        // 弾の発射向きをマウスカーソル位置で決める
        Vector3 screenPos = Camera.main.WorldToScreenPoint(m_Rigidbody.position);

        m_ShotDirection.x = (Input.mousePosition.x - screenPos.x) - Vector2.zero.x;
        m_ShotDirection.y = (Input.mousePosition.y - screenPos.y) - Vector2.zero.y;

        m_Angle = Mathf.Atan2(m_ShotDirection.y, m_ShotDirection.x) * Mathf.Rad2Deg;

        transform.localPosition += m_MovementAmount * m_Velocity * Time.deltaTime;
    }

    /// <summary>弾発射</summary>
    private void Shot()
    {
        if (!Input.GetMouseButton(0))
        {
            m_ShotIntervalCount = 0.0f;

            return;
        }

        m_ShotIntervalCount += Time.deltaTime;

        if (m_ShotIntervalCount < m_ShotInterval) { return; }

        m_ShotIntervalCount = 0.0f;

        /// <summary>自分も含めたルームメンバーに弾の発射を行わせる</summary>
        monobitView.RPC("RecvShot", MonobitTargets.All, transform.localPosition, m_Angle);
    }

    /// <summary>弾発射RPC</summary>
    [MunRPC]
    private void RecvShot(Vector3 position, float angle)
    {
        GameObject.FindWithTag("PlayerBulletsPool").GetComponent<PlayerBulletsPool>().Shot(position, angle);
    }
}
```
マウスカーソルのある方向から弾を発射する方向を算出する処理を加え、RPCの引数にも発射方向を加えてます。
PlayerBulletsPoolの<code>Shot()</code>をRecvShot内で呼ぶことで、各ルームメンバーで発射の同期を実装しています。
<code>GameObject.FindWithTag</code>をつかってPlayerBulletsPoolを取得していますが、予めStart関数内で取得したりstaticで扱ったり等、お好みで取得方法を変えてください。

これでとりあえずはプレイヤーの弾の発射が実際に弾が飛んでいくところまでを実装できました。

次回は敵を作っていこうと思います。

# 参考
http://www.monobitengine.com/doc/mun/