弾をリロードする

前回の記事でプレイヤーが弾を発射できるようにしました。今の状態では弾を無限に撃てるようになっています。今回はこれを修正してFPSのように弾がなくなったらリロードして補充するようにします。
前回の記事のURLは以下です。
https://qiita.com/yanagiiiiiii_t/items/66aae05650c1789bb37a

# リロード機能の実装
まず最初に撃てる弾の数を制限します。弾を全て使い切るとZキーを押しても弾が発射されなくなります。Rキーを押してリロードを実行すると弾が補充され、再び弾を発射できるようにします。プレイヤーオブジェクトにアタッチされているスクリプトPlayerController.csを以下のように修正します。

```C#:PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(Rigidbody))]
public class PlayerController : MonoBehaviour
{
    public float moveSpeed; //プレイヤーの移動速度
    public float jumpPower; //ジャンプ力
    public float bulletSpeed; //弾の速度
    public GameObject bulletPrefab; //弾のPrefabを入れるための変数
    private const uint maxBullet = 20; //弾の最大数を20個にする
    private uint bulletCount; //残弾の数
    private bool reloading = false; //リロード判定
    private CharacterController controller;
    private Vector3 moveVelocity;

    void Start()
    {
        controller = GetComponent<CharacterController>(); //CharacterControllerを取得
        bulletCount = maxBullet; //最初に残弾数を最大にする
    }

    void Update()
    {
        moveVelocity.x = Input.GetAxis("Horizontal") * moveSpeed; //横軸の入力を取得
        moveVelocity.z = Input.GetAxis("Vertical") * moveSpeed; //縦軸の入力を取得
        transform.LookAt(this.transform.position + new Vector3(moveVelocity.x, 0, moveVelocity.z)); //移動方向に向く

        if (controller.isGrounded) //オブジェクトが地面にいる場合
        {
            if (Input.GetButtonDown("Jump"))
            {
                moveVelocity.y = jumpPower; //ジャンプボタン入力でY軸正方向に移動
            }
        }
        else
        {
            moveVelocity.y += Physics.gravity.y * Time.deltaTime; //重力によるY軸負方向への移動
        }

        controller.Move(moveVelocity * Time.deltaTime); //プレイヤーが移動する

        //Zキーが入力され、残弾が1個以上かつリロード中でない
        if (Input.GetKeyDown(KeyCode.Z) && bulletCount >= 1 && !reloading)
        {
            Shot(); //弾を発射
            bulletCount--; //残弾数を1個減らす
        }

        //Rキーが入力され、残弾が最大値より少ないかつリロード中でない
        if (Input.GetKeyDown(KeyCode.R) && bulletCount < maxBullet && !reloading)
        {
            StartCoroutine(Reload()); //リロードを開始
        }
    }

    void Shot()
    {
        GameObject newbullet = Instantiate(bulletPrefab, this.transform.position, Quaternion.identity); //弾を生成
        Rigidbody bulletRigidbody = newbullet.GetComponent<Rigidbody>(); //Rigidbodyを取得
        bulletRigidbody.AddForce(this.transform.forward * bulletSpeed); //キャラクターが向いている方向に弾に力を加える
        Destroy(newbullet, 10); //10秒後に弾を消す
    }

    IEnumerator Reload()
    {
        reloading = true; //リロード判定をtrueにする
        yield return new WaitForSeconds(3); //3秒待機
        bulletCount = maxBullet; //残弾数を最大値まで回復する
        reloading = false; //リロード判定をfalseにする
    }
}
```

前回からの変更点を中心にコードの解説をします。クラスのフィールドには新たに3つのパラメーターが追加されました。
・maxBullet
・bulletCount
・reloading

maxBulletは充填できる弾の最大数を表します。今回は20に設定していますが、この値は好きに変えてもらっていいです。次にbulletCountは弾の残り数を意味します。bulletCountは弾を撃つ度に1ずつ減少し、0になるとZキーを押しても弾が発射されなくなります。そしてreloadingはリロード中かどうかを判定するフラグです、初期値はfalseにします。
Start関数ではbulletCountにmaxBulletを代入する行が追加されています。
Update関数では弾の発射のIf文に条件を追加しています。Shot関数が実行されればbulletCountを1減らします。
リロードはコルーチンを使ってReload関数として実装されています。弾丸のリロードの処理を書くのにコルーチンはかなり適しています。コルーチンの使い方は参考を見てもらった方がわかりやすいです。
これで弾を撃ってリロードして補充してまた撃つという処理が実現出来ました。

# 弾の数を表示する
通常FPSとかでは弾が残り何個か画面に表示されていたりするので、残弾数表示機能を追加します。UnityのGUIを使えば簡単に実装できます。PlayerController.csを以下のように修正します。

```C#:PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(CharacterController))]
[RequireComponent(typeof(Rigidbody))]
public class PlayerController : MonoBehaviour
{
    public float moveSpeed;
    public float jumpPower;
    public float bulletSpeed;
    public GameObject bulletPrefab;
    private const uint maxBullet = 20;
    private uint bulletCount;
    private bool reloading = false;
    private CharacterController controller;
    private Vector3 moveVelocity;
    private GUIStyle style;

    void Start()
    {
        controller = GetComponent<CharacterController>();
        bulletCount = maxBullet;
        style = new GUIStyle(); //GUIStyleのインスタンスを生成
        style.fontSize = 30; //フォントサイズを指定
    }

    void Update()
    {
        moveVelocity.x = Input.GetAxis("Horizontal") * moveSpeed;
        moveVelocity.z = Input.GetAxis("Vertical") * moveSpeed;
        transform.LookAt(this.transform.position + new Vector3(moveVelocity.x, 0, moveVelocity.z));

        if (controller.isGrounded)
        {
            if (Input.GetButtonDown("Jump"))
            {
                moveVelocity.y = jumpPower;
            }
        }
        else
        {
            moveVelocity.y += Physics.gravity.y * Time.deltaTime;
        }

        controller.Move(moveVelocity * Time.deltaTime);

        if (Input.GetKeyDown(KeyCode.Z) && bulletCount >= 1 && !reloading)
        {
            Shot();
            bulletCount--;
        }

        if (Input.GetKeyDown(KeyCode.R) && bulletCount < maxBullet && !reloading)
        {
            StartCoroutine(Reload());
        }
    }

    void Shot()
    {
        GameObject newbullet = Instantiate(bulletPrefab, this.transform.position, Quaternion.identity);
        Rigidbody bulletRigidbody = newbullet.GetComponent<Rigidbody>();
        bulletRigidbody.AddForce(this.transform.forward * bulletSpeed);
        Destroy(newbullet, 10);
    }

    IEnumerator Reload()
    {
        reloading = true;
        yield return new WaitForSeconds(3);
        bulletCount = maxBullet;
        reloading = false;
    }

    void OnGUI()
    {
        string text = bulletCount.ToString() + "/" + maxBullet.ToString(); //残弾数を表示するための文字列
        GUI.Label(new Rect(30, 400, 100, 30), text, style); //文字列を指定した位置に表示
    }
}
```

OnGUI関数の中のGUI.Labelの第一引数で、テキストを表示するための位置を、画面左上を原点とした長方形領域を指定します。bulletCountやmaxBulletは数値型なのでテキストとして表示する場合はToStringメソッドでstring型に変換することを忘れないようにしましょう。GUIの詳しい使い方も参考のサイトを見てもらった方がわかりやすいです。
以上で完成です。実行すると下の図のように弾を撃つと左下の数値が減少していき、0になると弾が発射されなくなり、リロードすると最大値で指定した値に戻ります。射撃とリロードによる変化が見えるようになりました。
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644510/8baaeee8-cb85-fc6d-4b7e-a0858db1ad4f.png)

# 参考
https://tech.pjin.jp/blog/2021/12/23/unity-coroutine/
https://game.daraha.me/basic/text/