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

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

前回は、ゲーム全体の調整をして、各プラットフォーム向けにビルドしてゲームを実行して確認しました。

今回はショットのパワーを実装したいと思います。

ショットのパワーを変化させる

敵・味方共にショットのパワーが1のままで変化が無いので、ショットのパワーを実装したいと思います。

この実装によって、敵・味方のHPを考えたりすることもゲームの調整に入ってくるので、よりゲーム性が出てくると思います。

では早速やっていきましょう。

パワーが変化したショットのオブジェクトを用意する

これは敵やアイテムと同じ考え方で、色違いのショットでパワーが変化したことをゲームのプレイヤーに知らせます。

ですので、まずは色違いのショットのオブジェクトを準備します。

ショットの画像を二つ複製し、それぞれstronger・strongestとします。

ショットの画像を二つ複製し、それぞれstronger・strongestとする

そしてPlayerBulletプレハブも二つ複製し、こちらもstronger・strongestとします。

PlayerBulletプレハブも二つ複製し、こちらもstronger・strongestとする

PlayerBulletプレハブの画像を、先程複製した画像のstronger・strongestを設定します。

stronger

strongest

後は色を変えればそれぞれのショットの出来上がりです。

無能はstrongerを緑に、strongestを赤にしました。

stronger

strongest

もし全然違うタイプのショットにしたい場合は、画像をインポートしてください。

その際にはColliderの適用を忘れずに行ってください。

また3段階以上を用意する場合も、プレハブを複製して想定する分だけショットを準備してください。

これでプレイヤーのショットのパワーが変化したオブジェクトの準備が出来ました。

次は敵のショットのパワーが変化したオブジェクトの準備をします。

敵のショットのパワーが変化したオブジェクトを準備する

敵のショットは、変わらず丸い弾にしておいて、色違いで強さを判別します。

敵の弾の画像は無いので、そのままプレハブを複製します。

複製したものをプレイヤーと同様にstronger・strongestにします。

敵の弾のプレハブを複製してリネームする

プレイヤーと同様にstrongerは緑、strongestは赤にします。

敵のstronger

敵のstrongest

これで敵のショットも準備出来ました。

敵の方は、このままスクリプトでSotPowerを変数に追加すれば実装出来そうなのですが、プレイヤーの方はと言えばショットのパワーが変化するきっかけがありません。

やられてリスポーンする時は最弱のショットになることは確定ですが、生存している中でショットのパワーを変化させるにはどうしたら良いでしょうか。

パッと思い付くのは、アイテムでショットパワーを強化するアイテムが出現し、それを取得するとプレイヤーのショットパワーが増加する、と言うのが良さそうです。

という事でショットパワーを加算するアイテムを作ります。

アイテムのオブジェクトの作成自体の流れは、他のアイテムと同様です。

無能はピンクにしました。

画像をインポート

画像の大きさを調整

アイテムのプレハブを複製して、この画像を設定して、アイテムの種類を決めれば準備完了です。

アイテムを複製してリネーム

画像とアイテムの種類を設定

これでお膳立てが出来ました。

次にスクリプトで処理を追記していくのですが、まずは手数が少なくて済むItemControllerから攻めていきます。

ItemController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]

public class ItemController : MonoBehaviour
{
    #region Variables
    #region var-CapsuleBase
    [Header("アイテムの基本パラメーター")]
    [SerializeField] float rotateSpeed = 10.0f;             // アイテムの回転速度
    [SerializeField] float dropSpeed = 1.0f;                // アイテムの進行速度
    [SerializeField] int itemType = 0;                      // アイテムの種類
    #endregion

    #region var-CapsuleIndividual
    [Header("アイテム個別のパラメーター")]
    [SerializeField] int healPoint = 5;                         // 回復アイテムの回復量
    [SerializeField] int itemScore = 5000;                      // スコアアイテムのスコア増加量
    [SerializeField] int remainingAdd = 1;                      // 残機の増加量
    [SerializeField] int shotPowerAdd = 1;                      // プレイヤーのショットパワー加算
    #endregion

    #region var-SE
    [Header("効果音関連")]
    [SerializeField] AudioClip SE_GetItem;                      // アイテム取得時の効果音
    [SerializeField] float getItemVol = 0.4f;                   // 取得時の効果音の音量
    #endregion

    #region var-Controller
    [Header("コントローラー関連")]
    [SerializeField] PlayerController player;                   // プレイヤーコントローラー
    [SerializeField] GameManager gameManager;                   // ゲームマネージャー
    Rigidbody2D capsuleRB;                                      // アイテムのリジッドボディ
    #endregion

    #region others
    Collider2D itemCollider;                                // アイテムのコライダー
    #endregion

    #region Internal
    enum itemEffectType { heal, addScore, oneUp, shield, addShotPower };      // 内部処理用(アイテムの効果の種類)
    #endregion
    #endregion

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

    #region Update
    void Update()
    {
        // アイテムをプレイヤーの方向へ動かす
        capsuleRB.velocity = new Vector2(-dropSpeed, 0);
        // アイテムを回転させる
        capsuleRB.transform.Rotate(0, 0, rotateSpeed);
    }
    #endregion

    #region OnTriggerEnter2D
    // 接触判定
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 接触したオブジェクトのタグがPlayerだった場合
        if (collision.gameObject.CompareTag("Player"))
        {
            // アイテムの効果を呼び出す
            ItemEffect(itemType, itemCollider);
        }
    }
    #endregion

    #region ItemEffect
    // アイテムの効果
    void ItemEffect(int iType, Collider2D collision)
    {
        // 引数iTypeで分岐
        switch (iType)
        {
            // 回復効果の場合
            case (int)itemEffectType.heal:
                // プレイヤーのHP表示を更新
                player.PlayerHPChanged(iType, healPoint, collision);
                // switch文を抜ける
                break;

            // スコア加算の場合
            case (int)itemEffectType.addScore:
                // スコアを加算する
                gameManager.ScoreAdded(itemScore);
                // 自身を破棄
                Destroy(gameObject);
                // switch文を抜ける
                break;

            // 1upの場合
            case (int)itemEffectType.oneUp:
                // 残機を増やす
                player.RemainingCounter(remainingAdd);
                // 自身を破棄
                Destroy(gameObject);
                // switch文を抜ける
                break;

            // シールドの場合
            case (int)itemEffectType.shield:
                // シールドを有効にする
                player.PlayerShieldActivate();
                // 自身を破棄
                Destroy(gameObject);
                // switch文を抜ける
                break;

            // ショットパワー加算の場合
            case (int)itemEffectType.addShotPower:
                // プレイヤーのショットパワーを増加
                player.PlayerShotPowerChanged(shotPowerAdd);
                // 自身を破棄
                Destroy(gameObject);
                // switch文を抜ける
                break;

            // 該当なしの場合
            default:
                // switch文を抜ける
                break;
        }
        // 取得時に効果音を再生
        AudioSource.PlayClipAtPoint(SE_GetItem, Camera.main.transform.position, getItemVol);
    }
    #endregion
    #endregion
}

では見ていきましょう。

メンバ変数のitemEffectTypeにaddShotPowerを追加して、分岐を増やしました。

それに応じてItemEffect関数のswitch文にaddShotPowerの分岐を増やし、プレイヤーのショットパワーを増加させ、アイテム自身を破棄します。

ここまではいつもの流れなので簡単ですね。

では次にPlayerControllerでショットパワーを増加させるように追記します。

このようになります。

PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Rendering.UI;
using UnityEngine.UI;

[RequireComponent(typeof(Rigidbody2D))]
public class PlayerController : MonoBehaviour
{
    #region Player-Variables
    #region var-Move
    [Header("移動関連")]
    [SerializeField] float moveSpeed = 1f;                      // プレイヤーの移動速度
    [SerializeField] float marginX = 1f, marginY = 1f;          // 余白x・y
    #endregion

    #region var-Shot
    [Header("ショット関連")]
    [SerializeField] GameObject[] playerBullet;                 // 弾のプレハブ
    [SerializeField] float bulletSpeed = 10f;                   // 弾の速度
    [SerializeField] Transform playerFirePos;                   // 発射ポイント
    [SerializeField] float bulletDelay = 1f;                    // 発射の閾値
    #endregion

    #region var-HP
    [Header("プレイヤーのHP")]
    [SerializeField] int playerHPMax = 10;                      // プレイヤーのHP(初期値:10)
    [SerializeField] int playerHPCurrent;                       // プレイヤーの現在のHP
    [SerializeField] TMP_Text playerHPText;                     // プレイヤーのHP表示のテキスト
    [SerializeField] Slider playerHPGauge;                      // プレイヤーのHP表示のゲージ
    #endregion

    #region var-Remaining
    [Header("プレイヤーの残機")]
    [SerializeField] TMP_Text remainingCount_Text;              // プレイヤーの残機表示のテキスト
    [SerializeField] GameObject[] remainingCount_Icon;          // プレイヤーの残機表示のアイコン
    [SerializeField] int remainingCountMax = 5;                 // プレイヤーの残機の最大数
    [SerializeField] int remainingCountCurrent = 3;             // プレイヤーの残機の現在値
    [SerializeField] Transform respawnPos;                      // プレイヤーの復活地点
    #endregion

    #region var-Shield
    [Header("シールド関連")]
    [SerializeField] GameObject playerShield;                                   // プレイヤーのシールド
    [SerializeField] int ShieldHPMax = 5;                                       // シールドの耐久値の最大値
    #endregion

    #region var-SE
    [Header("効果音関連")]
    [SerializeField] AudioClip SE_playerDamaged;                                // プレイヤー被弾時の効果音
    [SerializeField] AudioClip SE_ShieldDamaged;                                // シールドが攻撃された効果音
    [SerializeField] float playerDamagedVol = 0.4f, shieldDamagedVol = 0.4f;    // 被弾時の効果音の音量(プレイヤー,シールド)
    #endregion

    #region var-Controllers
    [Header("バーチャルスティック")][SerializeField] VariableJoystick joyStick;
    [Header("爆発エフェクト")][SerializeField] GameObject playerExplosion;

    [Header("ゲームマネージャー")][SerializeField] GameManager gameManager;
    [Header("エネミーコントローラー")][SerializeField] EnemyController enemyController;
    #endregion

    #region base-Variables
    Rigidbody2D playerRB;                                       // プレイヤーのRigidbody
    Vector2 moveDirection, min, max;                            // プレイヤーのベクトル,画面サイズの最小ベクトル/最大ベクトル
    Vector2 playerPos;                                          // プレイヤーの移動制限用
    float bulletInterval;                                       // 弾の発射間隔管理用
    bool isShotPressed = false;                                 // 発射ボタン管理用
    #endregion

    #region Internal-Variables
    int playerHPMin = 0;                                        // HPの最小値
    enum calcType { heal, damage, display }                     // HP表示・計算のタイプ
    int remainingCountMin = 0;                                  // プレイヤーの残機の最小値
    int changeRemaining = -1, remainingCountDisp = 0;           // プレイヤーの残機減少用,表示用
    int shieldHPCurrent, shieldHPMin = 0;                       // シールドの現在の耐久値
    int playerShotPowerMax = 3, playerShotPowerMin = 1;         // ショットパワーの最大値,最小値
    int countCtrl = 1;                                          // カウント調整用
    #endregion

    public static int PlayerShotPower { set; get; } = 1;        // ショットの強さ
    public static int PlayerBodyDamage { get; } = 3;            // プレイヤー本体に衝突した時のダメージ


    #endregion

    #region Method
    #region Start
    void Start()
    {
        // プレイヤーのRigidbodyを取得
        playerRB = GetComponent<Rigidbody2D>();
        // プレイヤーのHPを初期化
        playerHPCurrent = playerHPMax;
        // プレイヤーのHPを表示
        PlayerHPDisplay();
        // プレイヤーの残機を表示
        RemainingCounter(remainingCountDisp);
    }
    #endregion

    #region Update
    void Update()
    {
        // 入力受付
        InputProcess();

        // バーチャルスティック処理 ---
        // バーチャルスティックに何らかの入力が合った場合
        // (joyStickが0では無い場合)
        if (joyStick.Direction != Vector2.zero)
        {
            // moveDirectionをバーチャルスティックの入力にする
            moveDirection = joyStick.Direction;
        }
        // --- ここまで

        // 弾の発射 ---
        // deltaTimeを加算
        bulletInterval += Time.deltaTime;
        // 発射間隔が閾値未満の場合
        if (bulletInterval < bulletDelay)
        {
            // 処理を中断
            return;
        }
        // スペースキーが押された場合/isShotPressedがtrueの場合
        if (Input.GetKey(KeyCode.Space) || isShotPressed)
        {
            // 弾の発射関数を呼ぶ
            PlayerShot(playerFirePos, PlayerShotPower);
        }
        // 発射間隔をリセット
        bulletInterval = 0;
        // --- ここまで

    }
    #endregion

    #region FixedUpdate
    void FixedUpdate()
    {
        // プレイヤーを動かす
        PlayerMove();
    }
    #endregion

    #region InputProcess
    // 入力受付
    void InputProcess()
    {
        // 入力を正規化して受け取る
        float x = Input.GetAxisRaw("Horizontal");
        float y = Input.GetAxisRaw("Vertical");
        // 入力を正規化する
        moveDirection = new Vector2(x, y).normalized;
    }
    #endregion

    #region PlayerMove
    // プレイヤーを動かす
    void PlayerMove()
    {
        // 移動制限
        MoveClamp();
        // プレイヤーを動かす
        playerRB.velocity = moveDirection * moveSpeed;
    }
    #endregion

    #region MoveClamp
    // プレイヤーの移動制限
    void MoveClamp()
    {
        // 最後の移動地点を取得
        playerPos = transform.position;
        // ビューポート座標からワールド座標を取得
        min = Camera.main.ViewportToWorldPoint(Vector2.zero);
        max = Camera.main.ViewportToWorldPoint(Vector2.one);
        // 移動を制限する
        playerPos.x = Mathf.Clamp(playerPos.x, min.x + marginX, max.x - marginX);
        playerPos.y = Mathf.Clamp(playerPos.y, min.y + marginY, max.y - marginY);
        // プレイヤーの位置を取得した最後の地点にする
        transform.position = playerPos;
    }
    #endregion

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

    #region OnTriggerEnter2D
    // 接触判定
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 敵の場合
        if (collision.gameObject.CompareTag("Enemy"))
        {
            // ダメージの算出
            PlayerHPChanged((int)calcType.damage, EnemyController.EnemyBodyDamage, collision);
        }
        // 敵の弾の場合
        else if (collision.gameObject.CompareTag("EnemyBullet"))
        {
            // ダメージの算出
            PlayerHPChanged((int)calcType.damage, EnemyController.EnemyShotPower, collision);
        }
        // プレイヤーの弾の場合
        else if (collision.gameObject.CompareTag("PlayerBullet"))
        {
            // 処理を中断
            return;
        }
    }
    #endregion

    #region PlayerExplosion
    // 爆発演出
    void PlayerExplosion(Collider2D collision)
    {
        // 接触した方を破棄
        Destroy(collision.gameObject);
        // 自身を非アクティブ
        gameObject.SetActive(false);
        // プレイヤーの位置に爆発オブジェクトを作成
        Instantiate(playerExplosion, playerPos, Quaternion.identity);
    }
    #endregion

    #region OnPointerDown
    // 発射ボタンが押された時のイベント
    public void OnPointerDown()
    {
        // 発射ボタンのフラグを変更
        isShotPressed = true;
    }
    #endregion

    #region OnPointerUp
    // 発射ボタンが元に戻った時のイベント
    public void OnPointerUp()
    {
        // 発射ボタンのフラグを変更
        isShotPressed = false;
    }
    #endregion

    #region Continue
    // コンティニュー
    void Continue()
    {
        // ゲームマネージャーのコンティニューUIを表示
        gameManager.VisibleUI_Continue();
    }
    #endregion

    #region PlayerHPDisplay
    // プレイヤーのHP表示・更新
    void PlayerHPDisplay()
    {
        // HPのテキストを表示・更新
        playerHPText.SetText("PlayerHP : " + playerHPCurrent + " / " + playerHPMax);
        // 現在のHPをゲージに反映
        playerHPGauge.value = playerHPCurrent;
    }
    #endregion

    #region PlayerHPChanged
    // プレイヤーのダメージ算出
    public void PlayerHPChanged(int cType, int damage, Collider2D collision)
    {
        // switchで条件分岐:対象→cType
        switch (cType)
        {
            // cTypeが回復だった場合
            case (int)calcType.heal:
                // ダメージ分を加算する
                playerHPCurrent += damage;
                // 接触されたオブジェクトを破棄
                Destroy(collision.gameObject);
                // プレイヤーのHPが最大値以上の場合
                if (playerHPCurrent >= playerHPMax)
                {
                    // プレイヤーのHPを最大値にする
                    playerHPCurrent = playerHPMax;
                }
                // switch文を抜ける
                break;

            // cTypeがダメージだった場合
            case (int)calcType.damage:
                // シールドがアクティブになっている場合
                if (playerShield.activeInHierarchy)
                {
                    // シールドの耐久値を減算する
                    PlayerShieldIsActive(damage, collision);
                }
                else
                {
                    // ダメージ分を減算する
                    playerHPCurrent -= damage;
                }

                // 接触したオブジェクトを消す
                Destroy(collision.gameObject);

                // プレイヤーのHPが0以下(最小値以下)だった場合
                if (playerHPCurrent <= playerHPMin)
                {
                    // 爆発演出
                    PlayerExplosion(collision);
                    // プレイヤーのHPを0(最小値)にする
                    playerHPCurrent = playerHPMin;
                    // 残機を更新
                    RemainingCounter(changeRemaining);
                }
                // それ以外の場合
                else
                {
                    // シールドが有効な場合
                    if (playerShield.activeInHierarchy)
                    {
                        // 被弾時に効果音を再生(シールドへのダメージ)
                        AudioSource.PlayClipAtPoint(SE_ShieldDamaged, Camera.main.transform.position, shieldDamagedVol);
                    }
                    else
                    {
                        // 被弾時に効果音を再生(プレイヤーへのダメージ)
                        AudioSource.PlayClipAtPoint(SE_playerDamaged, Camera.main.transform.position, playerDamagedVol);
                    }
                    // switch文を抜ける
                    break;
                }
                // switch文を抜ける
                break;

            // cTypeが表示だった場合
            case (int)calcType.display:
                // switch文を抜ける
                break;

            // 該当なしの場合
            default:
                // HP表示の分岐へジャンプ
                goto case (int)calcType.display;
        }
        // プレイヤーのHPを表示
        PlayerHPDisplay();
    }
    #endregion

    #region RemainingCountDisplay
    // 残機表示・更新
    void RemainingCountDisplay()
    {
        // 現在の残機が最大値を超えている場合
        if (remainingCountCurrent > remainingCountMax)
        {
            // 残機を最大値にする
            remainingCountCurrent = remainingCountMax;
        }
        // プレイヤーの残機表示のテキストを更新
        remainingCount_Text.SetText("Remaining Count:" + remainingCountCurrent + "/" + remainingCountMax);

        // 残機の最大数までループ
        for (int i = 0; i < remainingCountMax; i++)
        {
            // 現在の残機数がi以下の場合
            if (remainingCountCurrent <= i)
            {
                // 残機アイコンを非アクティブ
                remainingCount_Icon[i].SetActive(false);
            }
            // それ以外の場合
            else
            {
                // 残機アイコンをアクティブ
                remainingCount_Icon[i].SetActive(true);
            }
        }
    }
    #endregion

    #region RemainingCounter
    // 残機カウントを更新
    public void RemainingCounter(int addRemaining)
    {
        // 残機に変更がない場合(addRemainingが0の場合)
        if (addRemaining == remainingCountDisp)
        {
            // 残機を表示する
            RemainingCountDisplay();
        }
        // 残機を増やす場合(addRemainingがプラスの場合)
        else if (addRemaining < remainingCountDisp)
        {
            // 残機を増やす
            remainingCountCurrent += addRemaining;
            // 残機が最大値を超える場合
            if (remainingCountCurrent > remainingCountMax)
            {
                // 現在の残機を最大値にする
                remainingCountCurrent = remainingCountMax;
            }
        }
        // 残機を減らす場合(addRemainingがマイナスの場合)
        else if (addRemaining < remainingCountDisp)
        {
            // 残機を減らす
            remainingCountCurrent += addRemaining;
            // 残機が最小値(0)以下の場合
            if (remainingCountCurrent < remainingCountMin)
            {
                // 残機を最小値にする
                remainingCountCurrent = remainingCountMin;
                // コンティニュー処理
                Continue();
            }
            // それ以外の場合
            else
            {
                // プレイヤーを復活地点に移動
                PlayerRespawn(respawnPos);
            }
        }
        // 残機表示を更新
        RemainingCountDisplay();
    }
    #endregion

    #region PlayerRespawn
    // プレイヤーの復活
    void PlayerRespawn(Transform rePos)
    {
        // プレイヤーをアクティブ
        gameObject.SetActive(true);
        // プレイヤーを復活地点に移動
        gameObject.transform.position = rePos.position;
        // プレイヤーのHPを戻す
        playerHPCurrent = playerHPMax;
        // ショットの強さを戻す
        PlayerShotPower = playerShotPowerMin;
        // プレイヤーのHPを更新
        PlayerHPDisplay();
    }
    #endregion

    #region PlayerShieldActivate
    // シールドをアクティブにする
    public void PlayerShieldActivate()
    {
        // シールドをアクティブ
        playerShield.SetActive(true);
        // シールドの耐久値を最大にする
        shieldHPCurrent = ShieldHPMax;
    }
    #endregion

    #region PlayerShieldIsActive
    // シールドのダメージ
    void PlayerShieldIsActive(int shieldDamage, Collider2D collision)
    {
        // シールドで受けたダメージを減算
        shieldHPCurrent -= shieldDamage;
        // 耐久値が最小値(0)未満になった場合
        if (shieldHPCurrent < shieldHPMin)
        {
            // 耐久値を超過した分をexcessDamageに代入
            int excessDamage = shieldHPCurrent;
            // シールドを非アクティブ
            playerShield.SetActive(false);
            // 超過したダメージ分はプレイヤーへダメージ
            PlayerHPChanged((int)calcType.damage, -excessDamage, collision);
        }
    }
    #endregion

    #region PlayerShotPowerChanged
    // ショットパワーを変更する
    public void PlayerShotPowerChanged(int shotPowerChange)
    {
        // ショットパワーの変更が無い場合
        if (shotPowerChange == 0)
        {
            // 処理を返す
            return;
        }
        // ショットパワーがプラスの場合
        else if (shotPowerChange > 0)
        {
            // ショットパワーを加算
            PlayerShotPower += shotPowerChange;
            // ショットパワーが最大値を超える場合
            if (PlayerShotPower > playerShotPowerMax)
            {
                // ショットパワーを最大値にする
                PlayerShotPower = playerShotPowerMax;
            }
        }
        // ショットパワーを減らす場合
        else if (shotPowerChange < 0)
        {
            // ショットパワーを減算する
            PlayerShotPower -= shotPowerChange;
            // ショットパワーが最小値未満になる場合
            if (PlayerShotPower < playerShotPowerMin)
            {
                // ショットパワーを最小値にする
                PlayerShotPower = playerShotPowerMin;
            }
        }
    }
    #endregion
    #endregion
}

では見ていきましょう。

メンバ変数で変化したのは、playerBulletが配列になったことです。

一種類ではなく、扱う弾の種類が増えたので当たり前ですね。

それから内部用として、ショットパワーの最小値・最大値、カウント調整用のcountCtrlを用意しました。

そして外部アクセスのために、プレイヤーの弾のパワーとプレイヤー本体が敵に与えるダメージを用意しました。

プレイヤーのショットパワーは、ショットパワーを変更するためsetを、プレイヤーのショットの強さをダメージにするためgetを用意しています。

プレイヤー本体のダメージは読み取りだけになるので、get-onlyにしています。

因みにstatic変数は、ゲーム全体の変数として扱われるので、Inspectorビューには表示されない事だけ覚えておいてください。

Update関数内のショットを呼び出す関数には、プレイヤーのショットパワーを伝える引数を新たに追加しています。

そしてPlayerShot関数で弾をクローン生成する際に、配列に変更したので配列の順序からcountCtrlで1引いた数を生成します。

これはダメージの計算にも関わってくる部分なので、ショットの生成時にcountCtrlで1を引いて配列にあわせています。

そしてOnTriggerEnter2D関数内を、EnemyCotrollerとあわせて修正しています。

そして新たにPlayerShotPowerChanged関数を作成し、こちらでショットパワーの変更の処理を行っています。

ショットパワーの加算値が0の場合は処理を返します。

1以上の場合はショットパワーを加算し、0未満の場合はショットパワーを減算します。

何れも最大値・最小値を超えないようにif文で条件分岐させています。

ここまででプレイヤー側のショットパワーの変更について出来たので、次は敵のショットパワーを追記します。

プレイヤーにショットパワーを実装したので、敵の方はプレイヤーとは異なり予め決まっているので簡単ですね。

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-EnemyHP
    [Header("敵のHP")]
    [SerializeField] int enemyHP = 1;
    #endregion

    #region var-EnemyShot
    [Header("敵の弾の発射")]
    [SerializeField] GameObject[] enemyBullet;                          // 敵の弾
    [SerializeField] Transform enemyFirePos;                            // 発射ポイント
    [SerializeField] float shotSpeed = 5f;                              // 弾の速度
    [SerializeField] float shotDelay = 2f;                              // 弾の発射の閾値
    [SerializeField] int shotPower = 1;                                 // 敵の弾の強さ
    #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;                                             // 弾の発射管理用
    int enemyHPMin = 0, enemyHPMax = 10;                                // 敵HPの最小値,最大値
    int countCtrl = 1;                                                  // カウント調整用
    enum enemyMoveType { normal, playerAimAttack, playerFollowY }       // 敵の動きの種類
    enum enemyDamageType { heal, damaged }                              // ダメージの種類
    #endregion

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

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

    #region Update
    void Update()
    {
        // 弾の発射 ---
        // deltaTimeを加算
        shotInterval += Time.deltaTime;
        // 発射間隔が閾値未満の場合
        if (shotInterval < shotDelay)
        {
            // 処理を中断
            return;
        }
        // 弾の発射関数を呼ぶ
        EnemyShot(enemyFirePos, EnemyShotPower);
        // 発射間隔をリセット
        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"))
        {
            // ダメージの算出
            EnemyHPChanged((int)enemyDamageType.damaged, PlayerController.PlayerBodyDamage, collision);
        }
        // プレイヤーの弾に接触した場合
        else if (collision.gameObject.CompareTag("PlayerBullet"))
        {
            // ダメージの算出
            EnemyHPChanged((int)enemyDamageType.damaged, PlayerController.PlayerShotPower, collision);
            // スコアを加算
            gameManager.ScoreAdded(addScore);
        }
        // ObjectDestroyerに接触した場合
        else if (collision.gameObject.CompareTag("ObjectDestroyer"))
        {
            // 自身を破棄
            Destroy(gameObject);
        }
    }
    #endregion

    #region EnemyShot
    // 弾の発射
    void EnemyShot(Transform firePos, int sPower)
    {
        // 弾を生成
        GameObject bulletClone = Instantiate(enemyBullet[sPower - countCtrl], 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

    #region EnemyHPChanged
    // ダメージの算出
    void EnemyHPChanged(int damageType, int damage, Collider2D collision)
    {
        // ダメージの種類で分岐
        switch (damageType)
        {
            // HP回復の場合
            case (int)enemyDamageType.heal:
                // 回復分をHPに加算
                enemyHP += damage;
                // HPの最大値以上に鳴る場合
                if (enemyHP >= enemyHPMax)
                {
                    // HPを最大値にする
                    enemyHP = enemyHPMax;
                }
                // switch文を抜ける
                break;

            // ダメージの場合
            case (int)enemyDamageType.damaged:
                // ダメージ分を減算
                enemyHP -= damage;
                // HPが最小値以下に鳴る場合
                if (enemyHP <= enemyHPMin)
                {
                    // 爆発演出
                    EnemyExplosion(collision);
                }
                // switch文を抜ける
                break;

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

では見ていきましょう。

敵の方は今までHPと言う概念は持っていませんでしたので、ショットパワーを実装する際にHPを持たせるようにしました。

メンバ変数にenemyHPとenemyHPMin・MaxというHP関連の変数を持たせます。

あとはプレイヤーと同じくBulletを配列にしてcountCtrlで調整し、ショットパワーの最小値・最大値を持たせました。

敵のパラメータを変更しやすいようにshotPowerを持たせています。

Start関数ではshotPowerをEnemyShotPowerに代入しています。

敵のショットパワーは敵が生成された時点で固定になっているので、Start関数でEnemyShotPowerに代入することでInspectorビューでの変更がしやすくなります。

static変数は共通でクラス名.変数で何処からでもアクセス可能になりますが、そのような「共通である」という一つの考え方からInspectorビューに表示されないようになっています。

そのためshotPowerでInspectorビューから変更可能にしておき、Start関数で代入するというわけです。

Update関数ではプレイヤーと同様にショットパワーを伝える引数を追加し、EnemyShot関数でcountCtrlで配列の数の調整をしています。

変化があるのはEnemyHPChanged関数が新たに追加されたことと、OnTriggerEnter2D関数でEnemyHPChanged関数が呼ばれています。

これらはプレイヤーと同様HPの変化を処理しています。

プレイヤーとの違いはコンティニュー処理とアイテム処理が無い事です。

一応回復する処理も追記していますが、現段階では敵が回復する処理は実装していません。

時間経過で敵のHPが回復する処理を実装したりすると面白いかも知れません。

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

ItemGeneratorにShotPowerUpを追加

プレイヤーのBulletに弾の種類を追加

今回はEnemy_Normalのショットパワーを2、PlayerAimAttackを1、PlayerFollowYを3にしました。

Normalのショットパワーを2

PlayerAimAttackのショットパワーを1

PlayerFollowYのショットパワーを3

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

今回確認することは
敵がそれぞれ設定した弾を発射するか
それぞれが発射した弾は設定通りのダメージになっているか
になります。

敵がそれぞれ設定した弾を発射するか:OK(1/3)

敵がそれぞれ設定した弾を発射するか:OK(2/3)

敵がそれぞれ設定した弾を発射するか:OK(3/3)

発射された弾の計算も大丈夫でした。
(スクリーンショットは割愛します)

これでショットの強さを実装出来ました。

まとめ

今回は
敵・味方共にショットのパワーを実装した
という事をやりました。

次回は、ステージボスを実装したいと思います。

では、また次回!

コメント