アマゾンバナーリンク

Unityで作る弾幕シューティングゲームその4 敵の弾幕と当たり判定追加

2020年7月18日

こんにちは!ジェイです。今回は敵に弾を発射させて当たり判定をつけます。

  • Assets>SCenes→Resouce→img→bulletのすべての弾幕のSpriteModeをMultiple、Pixels Per Unitを32に設定
  • 次にSprite Editor→Grid by Count横の分割数を指定して、Slice→Applyで弾幕を分割します。
  • CUtility.csにスプライトを分割して読み込む関数と角度をベクトルにして返す関数を追加
  • Player、Enemy、Bulletのタグをそれぞれ追加
  • 敵の弾幕を制御するCBullet.csを作成
  • CGameManager.csに弾幕を生成するクラスを追加
  • CEnemy.csに弾を発射する処理を追加
  • CPlayer.csにOnTriggerEnter2D、当たり判定を追加する

以上の手順で敵の弾幕と当たり判定が追加できます。追加するコードを見て行きましょう。光ってるとこが新たに追加した部分です。

using UnityEngine;

// 便利な関数を管理する静的クラス
public static class CUtility
{
    // 移動可能な範囲
    static Vector2 MoveLimitTopLeft = new Vector2(-5.7f, 4.2f);
    static Vector2 MoveLimitButtomRight = new Vector2(1.7f, -4.2f);

    static Vector2 EnemyLimitTopLeft = new Vector2(-7.0f, 6.2f);
    static Vector2 EnemyLimitButtomRight = new Vector2(3.0f, -5.2f);
    // 指定された位置を移動可能な範囲に収めた値を返す
    public static Vector3 ClampPosition(Vector3 position)
    {
        return new Vector3
        (
            Mathf.Clamp(position.x, MoveLimitTopLeft.x, MoveLimitButtomRight.x),
            Mathf.Clamp(position.y, MoveLimitButtomRight.y, MoveLimitTopLeft.y),
            0
        );
    }
    public static bool IsOut(Vector3 position)
    {
        return (position.x < EnemyLimitTopLeft.x ||
               EnemyLimitTopLeft.y  < position.y||
                EnemyLimitButtomRight.x < position.x ||
                position.y < EnemyLimitButtomRight.y);
    }
    // 引数に角度( 0 ~ 360 )を渡すとベクトルに変換して返す
    public static Vector3 GetDirection360(float angle360)
    {
        float angle = angle360 * Mathf.Deg2Rad;
        return new Vector3
        (
            Mathf.Cos(angle),
            Mathf.Sin(angle),
            0
        );
    }
    // 引数に角度( 0 ~ 1.0f )を渡すとベクトルに変換して返す
    public static Vector3 GetDirection1(float angle1)
    {
        float angle = angle1 / (3.14f * 2);
        return new Vector3
        (
            Mathf.Cos(angle),
            Mathf.Sin(angle),
            0
        );
    }
    // 引数に角度( 0 ~ PI2 )を渡すベクトルに変換して返す
    public static Vector3 GetDirectionPI2(float angle_pi2)
    {
        return new Vector3
        (
            Mathf.Cos(angle_pi2),
            Mathf.Sin(angle_pi2),
            0
        );
    }
    ///【機能】マルチプルスプライトからスライスしたスプライトを取得する
    ///【第1引数】スプライトファイル名(正確にはResources フォルダからのスプライトファイルまでのパス)
    ///【第2引数】取得したいスライスされたスプライト名
    ///【戻り値】取得したスプライト
    public static Sprite GetSprite(string fileName, string spriteName)
    {
        Sprite[] sprites = Resources.LoadAll<Sprite>(fileName);
        return System.Array.Find<Sprite>(sprites, (sprite) => sprite.name.Equals(spriteName));
    }
}


次に敵の弾幕を制御するCBullet.csを作成します。ここで作成した物は他のスクリプトからアタッチするので、インスペクターでアタッチする必要はないです。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CBullet : MonoBehaviour
{
    float Speed = 3.0f, Angle = 0.0f;
    void Start()
    {
        
    }
    public void SetParam(float speed, float angle)
    {
        Speed = speed;
        Angle = angle;
    }
    void Update()
    {
        Vector3 pos = GameObject.FindGameObjectWithTag("Player").transform.position;
        Vector3 dir = pos-transform.position;
        Angle = Mathf.Atan2(pos.y-transform.position.y, pos.x-transform.position.x);
        print(Angle);
        transform.position += CUtility.GetDirectionPI2(Angle) * Speed * Time.deltaTime;

        // 弾が進行方向を向くようにする
        var angles = transform.localEulerAngles;
        angles.z = Angle * Mathf.Rad2Deg - 90;
        transform.localEulerAngles = angles;

        if (CUtility.IsOut(transform.position))
        {
            Destroy(gameObject);
        }
    }
}

次に実際に弾幕を生成するクラスのCGameManager.csです。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CGameManager : MonoBehaviour
{
    public class CBulletFactory
    {
        public GameObject Bullet;
        public float Radius;
        public string[] SpriteName;
        public Sprite[] BulletSprite;
        public bool ColliderType;
        float SizeX, SizeY;
        public CBulletFactory(string[] sprite_name, bool collider_type, float radius=0.5f,float size_x=0.5f, float size_y = 0.5f)
        {
            Radius = radius;
            SpriteName = sprite_name;
            BulletSprite = new Sprite[SpriteName.Length];
            ColliderType = collider_type;
            SizeX = size_x;
            SizeY = size_y;
        }
        public void Load()
        {
            for (int i = 0; i < SpriteName.Length-1; ++i)
            {
                BulletSprite[i] = CUtility.GetSprite(SpriteName[0], SpriteName[i + 1]);
            }
        }
        public void CreateBullet(Vector3 pos, int color)
        {
            GameObject newParent = new GameObject("Empty");
            Bullet = Instantiate(newParent, pos, Quaternion.identity);
            Bullet.tag = "Bullet";
            SpriteRenderer sr = Bullet.AddComponent<SpriteRenderer>();
            sr.sprite = BulletSprite[color];
            sr.sortingLayerName = "Bullet";
            Bullet.transform.localScale = new Vector3(0.7f, 0.7f, 0.7f);
            Bullet.AddComponent<CBullet>();
            if (ColliderType)
            {
                CircleCollider2D cc = Bullet.AddComponent<CircleCollider2D>();//.radius = SizeX;
                cc.radius = Radius;
                cc.isTrigger = true;
            }
            else
            {
                BoxCollider2D bc = Bullet.AddComponent<BoxCollider2D>();//.size = new Vector2(SizeX, SizeY);
                bc.size = new Vector2(SizeX, SizeY);
                bc.isTrigger = true;
            }
            Destroy(newParent);
        }
    }
    public CBulletFactory[] BulletFactory = new CBulletFactory[]
    {
            new CBulletFactory(new string[]{ "img/bullet/b0","b0_0","b0_1","b0_2","b0_3","b0_4" }, true),
            new CBulletFactory(new string[]{ "img/bullet/b1","b1_0","b1_1","b1_2","b1_3","b1_4","b1_5" },true),
            new CBulletFactory(new string[]{ "img/bullet/b2","b2_0","b2_1" ,"b2_2" ,"b2_3" ,"b2_4" ,"b2_5" ,"b2_6" ,"b2_7" ,"b2_8" ,"b2_9"},false),
            new CBulletFactory(new string[]{ "img/bullet/b3","b3_0","b3_1" ,"b3_2" ,"b3_3" ,"b3_4" },false),
            new CBulletFactory(new string[]{ "img/bullet/b4","b4_0","b4_1" ,"b4_2" ,"b4_3" ,"b4_4" ,"b4_5" ,"b4_6" ,"b4_7" ,"b4_8" ,"b4_9"},true),
            new CBulletFactory(new string[]{ "img/bullet/b5","b5_0","b5_1" ,"b5_2" },false),
            new CBulletFactory(new string[]{ "img/bullet/b6","b6_0","b6_1" ,"b6_2"},false),
            new CBulletFactory(new string[]{ "img/bullet/b7","b7_0","b7_1" ,"b7_2" ,"b7_3" ,"b7_4" ,"b7_5" ,"b7_6" ,"b7_7" ,"b7_8" ,"b7_9" },true),
            new CBulletFactory(new string[]{ "img/bullet/b8","b8_0","b8_1" ,"b8_2" ,"b8_3" ,"b8_4" ,"b8_5" ,"b8_6" ,"b8_7" ,"b8_8" },false),
            new CBulletFactory(new string[]{ "img/bullet/b9","b9_0","b9_1" ,"b9_2" },false),
            new CBulletFactory(new string[]{ "img/bullet/b10","b10_0","b10_1" ,"b10_2" ,"b10_3" ,"b10_4" ,"b10_5" ,"b10_6" ,"10_7" },true),
            new CBulletFactory(new string[]{ "img/bullet/b11","b11_0","b11_1" ,"b11_2" ,"b11_3" ,"b11_4" ,"b11_5" ,"b11_6" ,"11_7" },true),
            new CBulletFactory(new string[]{ "img/bullet/b12","b12_0","b12_1" ,"b12_2" ,"b12_3" ,"b12_4" ,"b12_5" ,"b12_6" ,"b12_7" ,"b12_8" ,"b12_9" },true),
            new CBulletFactory(new string[]{ "img/bullet/b13","b13_0","b13_1" ,"b13_2" ,"b13_3" ,"b13_4" ,"b13_5" ,"b13_6" ,"b13_7" ,"b13_8" ,"b13_9" },true),
            new CBulletFactory(new string[]{ "img/bullet/b14","b14_0","b14_1" ,"b14_2" ,"b14_3" },true),
            new CBulletFactory(new string[]{ "img/bullet/b15","b15_0","b15_1" ,"b15_2" },true),
            new CBulletFactory(new string[]{ "img/bullet/_b6","_b6_0","_b6_1","_b6_2","_b6_3","_b6_4","_b6_5","_b6_6","_b6_7","_b6_8","_b6_9" },false),
            new CBulletFactory(new string[]{ "img/bullet/l0", "l0_0", "l0_1" },false),
            new CBulletFactory(new string[]{ "img/bullet/l0_moto", "l0_moto_0", "l0_moto_1" },true),
    };
    void Start()
    {
        for(int i = 0; i < BulletFactory.Length; ++i)
        {
            BulletFactory[i].Load();
        }
    }
}

次にCEnemy.csに弾を発射する処理を追加します。

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class CEnemy : MonoBehaviour
{
    int Cnt = 0;
    int Life = 60;


    // Start is called before the first frame update
    void Start()
    {

    }
    //GameObject newParent = new GameObject();
    // Update is called once per frame
    void Update()
    {
        if (Cnt < 100)
        {
            transform.position += new Vector3(0.0f, -3.0f, 0.0f) * Time.deltaTime;
        }
        if( Cnt == 300)
        {
            GameObject.Find("GameManager").GetComponent<CGameManager>().BulletFactory[3].CreateBullet(transform.position, 0);
        }
        if (Cnt > 100 + 240)
        {
            transform.position += new Vector3(0.0f, 3.0f, 0.0f) * Time.deltaTime;
        }
        Cnt++;
        if(CUtility.IsOut(transform.position))
        {
            Destroy(gameObject);
        }
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.gameObject.tag == "Shot")
        {
            Life -= collision.GetComponent<CShot>().ShotPower;
            if (Life <= 0)
            {
                Destroy(gameObject);
            }
            Destroy(collision.gameObject);
        }
    }
}

最後にCPlayer.csにOnTriggerEnter2Dで敵の弾が当たった時の処理を追加しましょう。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CPlayerControl : MonoBehaviour
{
    Animator _Animator;
    // Start is called before the first frame update
    public GameObject[] ShotObjs;
    void Start()
    {
        _Animator = GetComponent<Animator>();
        Sqrt2 = 1.0f / Mathf.Sqrt(2.0f);
        InputArray["Fire1"] = 0;
        InputArray["Fire2"] = 0;
        InputArray["Fire3"] = 0;
    }
    float VX = 0;
    float VY = 0;
    float Sqrt2;
    Dictionary<string, int> InputArray = new Dictionary<string, int>();
    void CalcInput()
    {
        string[] str = { "Fire1", "Fire2", "Fire3" };
        for(int i = 0; i < str.Length; ++i)
        {
            if (Input.GetButton(str[i]))
            {
                ++InputArray[str[i]];
            }
            else
            {
                InputArray[str[i]] = 0;
            }
        }
    }
    // Update is called once per frame
    void Update()
    {
        CalcInput();
        VX = VY = 0 < InputArray["Fire3"] ? 2.5f * Time.deltaTime : 7.0f * Time.deltaTime;
 
        float x = Input.GetAxisRaw("Horizontal");
        float y = Input.GetAxisRaw("Vertical");

        if (x < 0) VX = -VX; else if(x == 0) VX = 0;
        if (y < 0) VY = -VY; else if(y == 0) VY = 0;
        // 斜め方向の速度調節
        if (VX != 0 && VY != 0)
        {
            VX = VX * Sqrt2;
            VY = VY * Sqrt2;
        }
        transform.position = CUtility.ClampPosition(new Vector3(transform.position.x+VX, transform.position.y+VY, 0));
        
        // Zキー押しっぱなしで6フレームに1度ショットを発射する
        if (0 < InputArray["Fire1"] && InputArray["Fire1"] % 6 == 0)
        {
            if (0 < InputArray["Fire3"])//低速移動中なら
            {
                LowerSpeedShot();
            }
            else // 通常移動中なら
            {
                HiSpeedShot();
            }
        }
        _Animator.SetFloat("H", x);
    }
    int[] CShot0Num = { 2, 4 };
    Vector3[] HiSpeedShotOffsetPos = 
    {
        new Vector3(-0.15f,0.8f),
        new Vector3(0.15f,0.8f),
        new Vector3(-0.45f,0.4f),
        new Vector3(0.45f,0.4f),
    };
    // 通常ショット登録
    void HiSpeedShot()
    {
        //	Power < 200 ?0 : 1
        for (int i = 0; i < CShot0Num[1]; i++)
        {
            //SGP.Shot.Add(new CShot(X + CShot0Pos_X[i], Y + CShot0Pos_Y[i], 0.75f, 10));
            Instantiate(ShotObjs[0], transform.position + HiSpeedShotOffsetPos[i], Quaternion.identity);
        }
    }
    Vector3[] LowerSpeedShotOffsetPos =
{
        new Vector3(-0.05f,0.8f),
        new Vector3(0.05f,0.8f),
        new Vector3(-0.25f,0.4f),
        new Vector3(0.25f,0.4f),
    };
    //低速通常ショット登録
    void LowerSpeedShot()
    {
        for (int i = 0; i < CShot0Num[1]; i++)
        {
            //SGP.Shot.Add(new CShot(X + CShot0Pos_X[i], Y + CShot0Pos_Y[i], 0.75f, 10));
            Instantiate(ShotObjs[0], transform.position + LowerSpeedShotOffsetPos[i], Quaternion.identity);
        }
    }
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag == "Bullet")
        {
            Destroy(gameObject);
            Destroy(collision.gameObject);
        }
    }
}

なお、OnTriggerEnter2Dを使用する時には、衝突する物体にColliderが追加されていて、IsTriggerにチェックが入っていて、どちらかのオブジェクトにRigidbosy2Dがアタッチされているのが条件になります。