【Unity】List.FindIndexでループを使わずオブジェクトプーリング_改

#はじめに
令和元年おめでとうございます。新時代は弾幕系STGを作りたいので、なんとか納得のいくオブジェクトプーリングシステムを作ります。タイトルの改は前回からの改良ということで改です。

前回のオブジェクトプーリング
https://qiita.com/nuruoki86/items/a1ae99427e632caaacf6
では配列を使って、オブジェクトのプールをしてました。
C#にはListなるものがあり、1次元配列で何かを管理するならList使ったほうがいいようなので、こちらの方法でやって見ます。

#List.FindIndexを使って使用できるオブジェクトを見つける
ラムダ式([=>]こういうの)を使って、Listの中から条件にあうモノの番号をint型で返してくれます。条件に合うものがなければ-1を返してくれます。
今回の条件はListにはいってるそのオブジェクトが非アクティブであるかどうかです。

```cs:List.FindIndex
//プールから使うオブジェクトのナンバー。なければｰ1
int Obj_No = Bullet_List.FindIndex(b => b.activeSelf == false);
```
ラムダ式を使ってる部分はこんな感じ。Bullet_Listが今回GameObjectを格納してるListです。
ラムダ式で使ってる"b"という部分、型宣言もせずいきなり文字だけ打つのにすごく違和感を感じるのですが、Listにいれてるデータの型になってるみたいです。変数みたいなので"b"である必要はなく、自分の好きなように変えてもいいみたいです。hogeとか
今回はGameObject
![b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367795/a46e772f-72b6-1b91-9258-cd79190b9af2.png)

#実際に使ってみた
今回は前回のn-way弾(https://qiita.com/nuruoki86/items/2c92a0d7f105eb1d81ad)
をサンプルとして使います。
![hie.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367795/19a28ff0-d909-972a-cac8-2f5761efb0d4.png)
ヒエラルキーはこんな感じです。Targetオブジェクトは完全にターゲットで何もしないので、
今回は「Object_Pool」と「Luncher」についてだけ説明します。



##Object_Poolについて

```cs:ObjectPool.cs
public class ObjectPool : MonoBehaviour{
    //プールするオブジェクト
    public GameObject pool_Bullet;

    //プールしたオブジェクトを入れるリスト
    List<GameObject> Bullet_List = new List<GameObject>();

    public GameObject poolBullet(Vector2 shotpos) {
        //プールから使うオブジェクトのナンバー。なければｰ1
        int Obj_No = Bullet_List.FindIndex(b => b.activeSelf == false);

        if (Obj_No == -1) {
            //リストにないなら、新しく追加
            Bullet_List.Add((GameObject)Instantiate(pool_Bullet, shotpos, transform.rotation));

            //リストに追加したのを子オブジェクトに
            Obj_No = Bullet_List.Count - 1;
            Bullet_List[Obj_No].transform.parent = gameObject.transform;
        } 
        else Bullet_List[Obj_No].SetActive(true);//Listにあればそのオブジェクトをアクティブに

        //使うオブジェクトを返す
        return Bullet_List[Obj_No];
    }
}
```

前回は弾を撃つ処理と一緒に書いてましたが、今回はオブジェクトプールだけで独立させました。適当なオブジェクト(今回はObject_Poolオブジェクト)にくっつけて、poolBullet関数を呼び出せば大丈夫です。
最初にループ文等使って、いくつかプールするオブジェクトの確保はせず、足りなくなったらそのつど生成するようになってます。

```cs:Bullet_Sc.cs
    public float Velocity_0, theta;

    Vector2 cmin, cmax;
    Rigidbody2D rid2d;

    private void Start() {
        //Rigidbody取得
        rid2d = GetComponent<Rigidbody2D>();

        cmin = Camera.main.ViewportToWorldPoint(new Vector2(0, 0));
        cmax = Camera.main.ViewportToWorldPoint(new Vector2(1, 1));
    }

    private void Update() {
        Vector2 bulletV = rid2d.velocity;
        bulletV.x = Velocity_0 * Mathf.Cos(theta);
        bulletV.y = Velocity_0 * Mathf.Sin(theta);
        rid2d.velocity = bulletV;

        Bullet_false();
    }

    void Bullet_false() {
        Vector2 bpos = transform.position;

        if (bpos.x < cmin.x || bpos.y < cmin.y || bpos.x > cmax.x || bpos.y > cmax.y) {
            gameObject.SetActive(false);
        }
    }
```
弾についてるコードはこんな感じです。画面外で非アクティブにする条件がかなりごり押しなのは目をつぶってください。後で何とかしておきます。

オブジェクトプール動作の様子
![Qiita_11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367795/3d7a1b3f-84ca-1d38-80df-9805cba173e5.gif)


##Luncherから呼び出している部分
オブジェクトプールの説明は上までで終わりました。ここからはLuncherの説明です。
Luncher.csとShotMenu.csという二つのコンポーネントを持ってます。

```cs:Luncher.cs
    public string TargetName;
    float WaitFrame = 0;

    Shot_Menu shotmenu;
    private void Awake() {
        shotmenu = GetComponent<Shot_Menu>();
    }

    void Update() {
        //自身の座標取得
        Vector2 ShotPos = transform.position;

        //10フレームに1回発射
        WaitFrame++;
        if (WaitFrame > 10) {

            shotmenu.n_way_Shot(20, 5, 30, ShotPos);
 
            WaitFrame = 0;
        }
    }

    Vector2 TargetPos() {
        GameObject _Target = GameObject.FindWithTag(TargetName);
        Vector2 Tpos = _Target.transform.position;

        return Tpos;
    }
```
前回までずっとループの処理も直接書いてましたがShotMenuコンポーネントを作ってまとめました。しかるべき場所でn_way_Shot関数を呼び出してもらえれば、弾が撃てます。今回は10フレームに1回です。引数は前から順に(弾速, way数, 何度の範囲で出すか, ランチャーの座標)です。

引数の各型はこんな感じ。ちなみにオーバーロードできます。5つ目の引数にターゲットの座標を渡せば自機狙いになります。
![Qiita10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367795/061b0f42-936e-7b05-e332-ba4f746984bc.png)
ターゲットの座標に関しては、Luncher.csの下のほうにあるTargetPos関数がタグ検索で座標を返してくれます。

##ShotMenuコンポーネント
全体が長くなったので今回使ってる関数の部分を載せます。
オブジェクトプールへのアクセスはShotMenuコンポーネントが行ってます。

```cs:n_way_Shot
   public void n_way_Shot(float Velocity_0, int Angle_Split, float Degree, Vector2 shotPos) {
        float Theta = 0;

        for (int i = 0; i <= (Angle_Split - 1); i++) {
            //n-way弾の端から端までの角度
            float diffusion_angle = PI * (Degree / 180);

            //弾インスタンスに渡す角度の計算
            if (Angle_Split > 1)
                Theta = (diffusion_angle / (Angle_Split - 1)) * i - 0.5f * diffusion_angle;
            else
                Theta = 0;

            Bulletfunc(Velocity_0, Theta, shotPos);
        }
    }
```

ランチャー側から呼び出して、計算した角度とともに、速度と座標をBulletfunc関数に渡してます。

```cs:Bulletfunc
    private void Bulletfunc(float Velocity_0, float Theta, Vector2 shotPos) {
        //弾の座標を撃つ場所に
        GameObject Bullet_obj = objp.poolBullet(shotPos);
        Bullet_obj.transform.position = shotPos;

        //いろいろ弾に渡す部分
        Bullet_Sc bullet_cs = Bullet_obj.GetComponent<Bullet_Sc>();
        bullet_cs.theta = Theta;
        bullet_cs.Velocity_0 = Velocity_0;
    }
```
やっとオブジェクトプールを使います。poolBullet関数はここで呼び出してます。

#終わり
List.FindIndexをつかって、前回に比べるとだいぶすっきりしたと思います。
長くなったのでわかりづらいところがあったり、変なところがあれば教えていただくとありがたいです。
あと、n-way弾の自機狙いはこんな感じです
![n-way2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367795/4073dda8-e00e-8de8-6382-f00d1fcc839b.gif)