【Unity】2Dシューティングを作ってみる話 #27

記事をご覧いただき、誠にありがとうございます。
投稿主の無能です。

前回は、追加実装した機能の効果音を付けました。

今回は、敵が真っ直ぐに動くだけなので、種類を増やして動きを付けたいと思います。

敵に動きを付ける

敵に動きを付けると言っても、色々なものがあります。

今回は敵が速いスピードでプレイヤー狙いで特攻してくる動きと、プレイヤーの上下運動に合わせて動く敵を作りたいと思います。

最初に特攻してくる敵からやっていきましょう。

プレイヤー狙いで動く敵を作る

でもプレイヤー狙いで動く敵なんて作れるのか、と思うかもしれませんが、意外と今までの内容で作れたりします。

まずは敵のプレハブを複製し増やします。

複製の序に今までの直線的な動きの敵を「Enemy_Normal」に名前を変更し、新たに複製した方の敵の名前を「Enemy_PlayerAimAttack」に変更します。

敵の種類が増えるのでリネームする

敵の画像を差し替えて別の敵を作るのも良いですが、今回は同じアメーバのの画像を色違いにしてみます。

PlayerAimAttackのSprite Rendererの色相環で色を変えます。

Sprite Rendererの色相環で色を変える

Hierarchyビューに出して比べてみると結構はっきりと色が違いますね。

上がNormal、下がPlayerAimAttack

ではHierarchyビューにある敵は色の比較の為だけに出したので削除してください。

スクリプトで敵の違いを実装していきます。

スクリプトで敵の違いを作る

何だか似たようなことをやった気がします。

そう、アイテムの処理と同じです。

敵の種類をint型とenum型で管理して、それを複製した敵に持たせてあげれば敵の種類が増やせるようになります。

このようになります。

EnemyCotroller.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class EnemyController : MonoBehaviour
{
    #region var-EnemyType
    [Header("敵の種類")][SerializeField] int enemyType = 0;
    #endregion

    #region var-EnemyMove
    [Header("敵の移動スピード")][SerializeField] float moveSpeed = 1f;
    #endregion

    #region var-EnemyShot
    [Header("敵の弾の発射")]
    [SerializeField] GameObject enemyBullet;                // 敵の弾
    [SerializeField] Transform enemyFirePos;                // 発射ポイント
    [SerializeField] float shotSpeed = 5f;                  // 弾の速度
    [SerializeField] float shotDelay = 2f;                  // 弾の発射の閾値
    #endregion

    #region var-EnemyPerformance
    [Header("加算する点数")][SerializeField] int addScore = 100;
    [Header("爆発エフェクト")][SerializeField] GameObject enemyExplosion;
    #endregion

    #region var-Controller
    [Header("ゲームマネージャー")] GameManager gameManager;
    [Header("プレイヤーコントローラー")] PlayerController player;
    #endregion

    #region var-Internal
    Rigidbody2D enemyRB;                                    // 敵のリジッドボディ
    float shotInterval = 0;                                 // 弾の発射管理用
    enum enemyMoveType { normal, playerAimAttack }          // 敵の動きの種類
    #endregion

    #region var-ExternalAccess
    public static int shotDamage { get; } = 1;              // 弾のダメージ
    public static int bodyDamage { get; } = 3;              // 本体のダメージ
    #endregion

    #region Start
    void Start()
    {
        // 敵のリジッドボディを取得
        enemyRB = GetComponent<Rigidbody2D>();
        // ゲームマネージャーを取得
        gameManager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
        // プレイヤーコントローラーを取得
        player = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
    }
    #endregion

    #region Update
    void Update()
    {
        // 弾の発射 ---
        // deltaTimeを加算
        shotInterval += Time.deltaTime;
        // 発射間隔が閾値未満の場合
        if (shotInterval < shotDelay)
        {
            // 処理を中断
            return;
        }
        // 弾の発射関数を呼ぶ
        EnemyShot(enemyFirePos);
        // 発射間隔をリセット
        shotInterval = 0;
        // --- ここまで
    }
    #endregion

    #region FixedUpdate
    void FixedUpdate()
    {
        // 敵を動かす
        EnemyMove(enemyType);
    }
    #endregion

    #region EnemyMove
    // 敵を動かす
    void EnemyMove(int moveType)
    {
        // moveTypeで分岐
        switch (moveType)
        {
            // ノーマルの場合
            case (int)enemyMoveType.normal:
                // 敵に左方向のベクトルを持たせる
                enemyRB.velocity = new Vector2(-moveSpeed, 0);
                // swtich文を抜ける
                break;

            // プレイヤーに向かって特攻の場合
            case (int)enemyMoveType.playerAimAttack:
                // 敵にプレイヤーの位置に向かうように動かす
                enemyRB.velocity = new Vector2(-moveSpeed + player.transform.position.x, player.transform.position.y - transform.position.y);
                // swtich文を抜ける
                break;

            // 該当なしの場合
            default:
                // swtich文を抜ける
                break;
        }
    }
    #endregion

    #region OnTriggerEnter2D
    // 接触判定
    void OnTriggerEnter2D(Collider2D collision)
    {
        // プレイヤーに接触した場合
        if (collision.gameObject.CompareTag("Player"))
        {
            // 爆発演出
            EnemyExplosion(collision);
        }
        // プレイヤーの弾に接触した場合
        else if (collision.gameObject.CompareTag("PlayerBullet"))
        {
            // 爆発演出
            EnemyExplosion(collision);
            // スコアを加算
            gameManager.ScoreAdded(addScore);
        }
        // ObjectDestroyerに接触した場合
        else if (collision.gameObject.CompareTag("ObjectDestroyer"))
        {
            // 自身を破棄
            Destroy(gameObject);
        }
    }
    #endregion

    #region EnemyShot
    // 弾の発射
    void EnemyShot(Transform firePos)
    {
        // 弾を生成
        GameObject bulletClone = Instantiate(enemyBullet, firePos.position, Quaternion.identity);
        // 弾のRigidbodyに速度ベクトルをつける
        bulletClone.GetComponent<Rigidbody2D%gt;().velocity = new Vector2(-shotSpeed, 0);
    }
    #endregion

    #region EnemyExplosiion
    // 爆発演出
    void EnemyExplosion(Collider2D collision)
    {
        // 自身を破棄
        Destroy(gameObject);
        // 爆発エフェクトを生成
        Instantiate(enemyExplosion, transform.position, Quaternion.identity);
    }
    #endregion
}
では見ていきましょう。

メンバ変数に敵の種類を示すenemyTypeと、enum型のenemyMoveTypeを追加しました。

そして移動の目標を算出するためにPlayerControllerを追加しました。

Start関数ではPlayerControllerを取得する処理を追加しています。

fixedUpdate関数でEnemyMove関数を呼んでいますが、敵の種類を判別するため引数を渡してします。

EnemyMove関数では、渡された引数でswitch文による分岐をしています。

追加したplayerAimAttackの分岐では、x軸で取得したプレイヤーの位置と敵の動く速度を足し合わせて、y軸でプレイヤーの位置のyから自身の位置のyを減算しています。

この演算で、プレイヤーに向かって特攻する動きになります。

では追加された項目を設定します。

Enemy_PlayerAimAttackで敵の種類を設定

EnemyGeneratorの敵リストにPlayerAimAttackを追加

ではゲームを実行して確認します。

敵がプレイヤーに向かって一直線に特攻するようになった

敵が結構なスピードで向かってくるのでびっくりしますね。

速さは進行方向に向かうmoveSpeedを足し合わせているので、moveSpeedで調整が可能です。

次はプレイヤーの上下運動に合わせて動く敵を作ります。

プレイヤーの上下運動に合わせて動く敵を作る

作る流れはPlayerAimAttackの時と一緒で、増やす敵を複製して色違いにします。

敵を複製して「PlayerFollowY」にリネーム

色を青っぽくする

PlayerFollowY

では敵を複製したので、スクリプトを追記しましょう。

このようになります。

EnemyCotroller.cs
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class EnemyController : MonoBehaviour
{
    #region var-EnemyType
    [Header("敵の種類")][SerializeField] int enemyType = 0;
    #endregion

    #region var-EnemyMove
    [Header("敵の移動スピード")][SerializeField] float moveSpeed = 1f;
    #endregion

    #region var-EnemyShot
    [Header("敵の弾の発射")]
    [SerializeField] GameObject enemyBullet;                			// 敵の弾
    [SerializeField] Transform enemyFirePos;                			// 発射ポイント
    [SerializeField] float shotSpeed = 5f;                  			// 弾の速度
    [SerializeField] float shotDelay = 2f;                  			// 弾の発射の閾値
    #endregion

    #region var-EnemyPerformance
    [Header("加算する点数")][SerializeField] int addScore = 100;
    [Header("爆発エフェクト")][SerializeField] GameObject enemyExplosion;
    #endregion

    #region var-Controller
    [Header("ゲームマネージャー")] GameManager gameManager;
    [Header("プレイヤーコントローラー")] PlayerController player;
    #endregion

    #region var-Internal
    Rigidbody2D enemyRB;                                                // 敵のリジッドボディ
    float shotInterval = 0;                                             // 弾の発射管理用
    enum enemyMoveType { normal, playerAimAttack, playerFollowY }       // 敵の動きの種類
    #endregion

    #region var-ExternalAccess
    public static int shotDamage { get; } = 1;                          // 弾のダメージ
    public static int bodyDamage { get; } = 3;                          // 本体のダメージ
    #endregion

    #region Start
    void Start()
    {
        // 敵のリジッドボディを取得
        enemyRB = GetComponent<Rigidbody2D>();
        // ゲームマネージャーを取得
        gameManager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
        // プレイヤーコントローラーを取得
        player = GameObject.FindWithTag("Player").GetComponent<PlayerController>();
    }
    #endregion

    #region Update
    void Update()
    {
        // 弾の発射 ---
        // deltaTimeを加算
        shotInterval += Time.deltaTime;
        // 発射間隔が閾値未満の場合
        if (shotInterval < shotDelay)
        {
            // 処理を中断
            return;
        }
        // 弾の発射関数を呼ぶ
        EnemyShot(enemyFirePos);
        // 発射間隔をリセット
        shotInterval = 0;
        // --- ここまで
    }
    #endregion

    #region FixedUpdate
    void FixedUpdate()
    {
        // 敵を動かす
        EnemyMove(enemyType);
    }
    #endregion

    #region EnemyMove
    // 敵を動かす
    void EnemyMove(int moveType)
    {
        // moveTypeで分岐
        switch (moveType)
        {
            // ノーマルの場合
            case (int)enemyMoveType.normal:
                // 敵に左方向のベクトルを持たせる
                enemyRB.velocity = new Vector2(-moveSpeed, 0);
                // swtich文を抜ける
                break;

            // プレイヤーに向かって特攻の場合
            case (int)enemyMoveType.playerAimAttack:
                // 敵にプレイヤーの位置に向かうように動かす
                enemyRB.velocity = new Vector2(-moveSpeed + player.transform.position.x, player.transform.position.y - transform.position.y);
                // swtich文を抜ける
                break;

            // プレイヤーの上下運動に合わせて動く場合
            case (int)enemyMoveType.playerFollowY:
                // 敵をプレイヤーの上下の位置にあわせて動かす
                enemyRB.velocity = new Vector2(-moveSpeed, player.transform.position.y - transform.position.y);
                // swtich文を抜ける
                break;

            // 該当なしの場合
            default:
                // swtich文を抜ける
                break;
        }
    }
    #endregion

    #region OnTriggerEnter2D
    // 接触判定
    void OnTriggerEnter2D(Collider2D collision)
    {
        // プレイヤーに接触した場合
        if (collision.gameObject.CompareTag("Player"))
        {
            // 爆発演出
            EnemyExplosion(collision);
        }
        // プレイヤーの弾に接触した場合
        else if (collision.gameObject.CompareTag("PlayerBullet"))
        {
            // 爆発演出
            EnemyExplosion(collision);
            // スコアを加算
            gameManager.ScoreAdded(addScore);
        }
        // ObjectDestroyerに接触した場合
        else if (collision.gameObject.CompareTag("ObjectDestroyer"))
        {
            // 自身を破棄
            Destroy(gameObject);
        }
    }
    #endregion

    #region EnemyShot
    // 弾の発射
    void EnemyShot(Transform firePos)
    {
        // 弾を生成
        GameObject bulletClone = Instantiate(enemyBullet, firePos.position, Quaternion.identity);
        // 弾のRigidbodyに速度ベクトルをつける
        bulletClone.GetComponent<Rigidbody2D>().velocity = new Vector2(-shotSpeed, 0);
    }
    #endregion

    #region EnemyExplosiion
    // 爆発演出
    void EnemyExplosion(Collider2D collision)
    {
        // 自身を破棄
        Destroy(gameObject);
        // 爆発エフェクトを生成
        Instantiate(enemyExplosion, transform.position, Quaternion.identity);
    }
    #endregion
}

先程作成したenemyMoveTypeにPlayerFollowYを追加して、EnemyMove関数の分岐を増やしています。

動きはx軸方向はnormalと同じで、y軸でプレイヤーの位置に向けて動くようにしました。

では追加された項目を設定し、ゲームを実行して確認します。

敵の種類を設定

敵リストにPlayerFollowYを追加


プレイヤーの上下に追従してくるようになった

これで弾の発射間隔を調整すれば、意外と強敵になりそうですね。

敵の種類を増やして動きを付けることが出来ました。

switch文の分岐を増やして、皆さん独自の動きを作ってみてください。

まとめ

今回は
  • 敵の種類を増やした
  • 敵に動きを付けた
という事をやりました。

次回は、プレイヤーにシールドが張れる機能を実装したいと思います。

では、また次回!

コメント