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

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

前回は、敵・味方共にショットのパワーを実装しました。

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

ステージのボスを実装する

ステージのボスとは言っていますが、要は単にサイズが大きいだけの、今まで作った敵の延長線上にあります。

とは言え、ボスなのでそれなりの演出は欲しいですよね。

ですのでまずはボス専用のUIを作りましょう。

ボスのUIを作成する

ボスのUIと言って、真っ先に思い浮かぶのがプレイヤーと同じようなボスのHPの表示でしょうか。

ボスのHPテキストとHPゲージが表示されたら、何となくボスっぽい雰囲気は出せるかと思うので、それらのUIを作ります。

作りますと言っても、プレイヤーのステータスを複製してボス用に流用します。

PlayerStatusを複製し、テキストやゲージを整えます。

PlayerStatusを複製し、ボス用に整える

HP表示テキスト

HPゲージ

複製して整えたボス用UI

UIが出来たので、次はボス本体を準備します。

ボスを用意する

ボスは自由に設定してしてください。

無能はEnemy_Normalのデカいバージョンをボスとして設定します。

敵の画像をボス用に複製して、画像サイズを大きくします。

敵の画像を複製

Pixels Per Unitを変更

ボスの画像

そして敵のプレハブを複製して、ボス用の画像を設定します。

敵のプレハブを複製

ボスの画像を設定

画像を変更したので、Colliderの初期化をします。

Polygon Colliderを初期化

Colliderが画像サイズに合うようになった

そして発射ポイントを調整します。

発射ポイントを調整

調整した発射ポイントの位置

調整が終わったら、Apply Allでプレハブの変更を適用しましょう。

そして爆発演出のプレハブを複製して、ボス向けに調整します。

爆発演出を複製

パーティクルを調整

パーティクルの見た目

ボスの撃破音(爆発時の効果音)を設定

これでボス用のオブジェクトとUIが準備出来ました。

ではこれらのUIがボスのHPに連動して変化するような処理をスクリプトで実装していきます。

今回のボスの出現条件は、一定のスコアを超えた時とします。

ボスのHP表示とUIが連動する処理を実装する

ボス用のUIと連動するためには、プレイヤーのHP表示と同様の処理を実装すれば良さそうです。

ですので、ボス用の処理を追記する必要があります。

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

[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;
    [Header("ボス用爆発エフェクト")][SerializeField] GameObject enemyBossExplosion;
    #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の最小値,最大値
    float bossStopPos = 3f;                                                     // ボスの停止地点
    int countCtrl = 1;                                                          // カウント調整用
    enum enemyMoveType { normal, playerAimAttack, playerFollowY, EnemyBoss }    // 敵の動きの種類
    enum enemyDamageType { heal, damaged }                                      // ダメージの種類
    #endregion

    #region var-ExternalAccess
    public static int EnemyShotPower { set; get; } = 1;                         // 弾のダメージ
    public static int EnemyBodyDamage { get; } = 3;                             // 本体のダメージ
    public static int EnemyBossBodyDamage { get; } = 10;                        // ボス本体のダメージ
    #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;

            // ボスの場合
            case (int)enemyMoveType.EnemyBoss:
                // 敵にプレイヤーの位置に向かうように動かす
                enemyRB.velocity = new Vector2(-moveSpeed, 0);
                // x軸の位置がボス出現ポイントを超えた場合
                if (transform.position.x < bossStopPos)
                {
                    // ベクトルを0にする(動かなくなる)
                    enemyRB.velocity = Vector2.zero;
                }
                // 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);
        // タグがEnemyBossの場合
        if (gameObject.CompareTag("EnemyBoss"))
        {
            // ボス用の爆発エフェクトを生成
            Instantiate(enemyBossExplosion, transform.position, Quaternion.identity);
        }
        // それ以外の場合
        else
        {
            // 爆発エフェクトを生成
            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;
                // タグがEnemyBossの場合
                if (gameObject.CompareTag("EnemyBoss"))
                {
                    // ボスのHPを減算
                    gameManager.bossHPCurrent = enemyHP;
                    // ボスのHPが最小値以下の場合
                    if (gameManager.bossHPCurrent <= enemyHPMin)
                    {
                        // ボスのHPを最小値にする
                        gameManager.bossHPCurrent = enemyHPMin;
                        // 爆発演出
                        EnemyExplosion(collision);
                    }
                    // ボスのHP表示を更新
                    gameManager.EnemyBossHPDisplay();
                }
                // それ以外の場合
                else
                {
                    // HPが最小値以下になる場合
                    if (enemyHP <= enemyHPMin)
                    {
                        // 爆発演出
                        EnemyExplosion(collision);
                    }

                }

                // 接触オブジェクトのタグがPlayerBulletの場合
                if (collision.gameObject.CompareTag("PlayerBullet"))
                {
                    // 接触オブジェクトを破棄
                    Destroy(collision.gameObject);
                }

                // switch文を抜ける
                break;

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

では見ていきましょう。

メンバ変数はenumのenemyMoveTypeにenemyBossを追加しました。

ボス用の爆発演出を追加し、外部アクセス用にボス本体のダメージを追加しています。

爆発演出のEnemyExplosion関数では、ボス用の爆発演出をタグで分岐するようにしています。

EnemyHPChanged関数のダメージの分岐では、タグがEnemyBossだった場合の分岐を追加しています。

GameManagerの現在のHPに応じての処理をしてから、ボスのHP表示の更新をしています。

大雑把に説明しましたが、あくまでもボス用のタグ付けをして、ボス用の処理を行っているだけで、追記した殆どの内容が今までの敵の処理とそれ程相違ないということです。

このEnemyCotrollerの変更を受けて、GameManagerの変更はこのようになります。

GameManager.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class GameManager : MonoBehaviour
{
    #region var-BaseVars
    [Header("画面上の発射ボタン")][SerializeField] GameObject ShotButton;
    [Header("ゲーム中断のUI")][SerializeField] GameObject UI_Pause;
    [Header("コンティニューのUI")][SerializeField] GameObject UI_Continue;
    [Header("スコアのUI")][SerializeField] TMP_Text scoreText;
    [Header("敵生成オブジェクト")][SerializeField] EnemyGenerator enemyGenerator;
    #endregion

    #region var-BossUI
    [Header("ボス関連")]
    [SerializeField] int bossVisibleScore = 5000;           // ボス出現のスコア
    [SerializeField] GameObject enemyBoss;                  // ボスのオブジェクト
    [SerializeField] Transform spawnPoint;                  // 出現位置
    [SerializeField] GameObject UI_BossStatus;              // ボスのUI関連の親オブジェクト
    [SerializeField] TMP_Text UI_BossHPText;                // ボスのHP表示(テキスト)
    [SerializeField] Slider UI_BossHPGauge;                 // ボスのHP表示(ゲージ)
    #endregion

    #region var-Internal
    EnemyController enemyController;                        // エネミーコントローラー
    int pause = 0, pauseRelease = 1;                        // ゲーム中断時のタイムスケール
    int score = 0, scoreInit = 0, scoreMax = 99999999;      // スコア,スコアの初期値,最大値
    int bossHPMax = 50;                                     // ボスのHPの最小値、最大値
    bool isBossVisible = false;                             // ボスの出現フラグ
    #endregion

    #region var-External
    public int bossHPCurrent { set;  get; }                 // ボスの現在のHP
    #endregion

    void Start()
    {
#if PLATFORM_STANDALONE_WIN
        ShotButton.SetActive(false);
#endif
        // スコアを初期化
        ScoreInit();
        // ボスのHPを初期化
        bossHPCurrent = bossHPMax;
    }

    void Update()
    {
        // Escキーが押された場合
        if (Input.GetKey(KeyCode.Escape))
        {
            // ゲーム中断のUIをアクティブ
            UI_Pause.SetActive(true);
            // ゲーム中断処理
            GamePause();
        }

        // スコアがボス出現の閾値以上だった場合
        if (score >= bossVisibleScore)
        {
            // ボス出現フラグがfalseだった場合
            if (!isBossVisible)
            {
                // ボスのステータスが非アクティブの場合
                if (!UI_BossStatus.activeInHierarchy)
                {
                    // ボスのステータスをアクティブ
                    UI_BossStatus.SetActive(true);

                }
                // ボス出現フラグをtrue
                isBossVisible = true;
                // 敵の自動生成を止める
                enemyGenerator.CancelInvoke();
                // ボスを生成
                Instantiate(enemyBoss, spawnPoint.position, Quaternion.identity);
                // ボスのHP表示を更新
                EnemyBossHPDisplay();
            }
        }
        // それ以外の場合
        else if (isBossVisible)
        {
            // ボスのHP表示を更新
            EnemyBossHPDisplay();
        }
    }

    // ゲーム中断
    void GamePause()
    {
        // タイムスケールを0にする
        Time.timeScale = pause;
    }

    // ゲーム中断解除
    public void GamePauseRelease()
    {
        // タイムスケールを1にする
        Time.timeScale = pauseRelease;
        // ゲーム中断のUIを非アクティブ
        UI_Pause.SetActive(false);
    }

    // ゲーム終了
    public void GameExit()
    {
        // アプリケーションを終了する
        Application.Quit();
    }

    // コンティニューのUIを表示
    public void VisibleUI_Continue()
    {
        // ゲームを中断
        GamePause();
        // コンティニューのUIをアクティブ
        UI_Continue.SetActive(true);
    }

    // コンティニュー
    public void StageContinue()
    {
        // 中断を解除
        Time.timeScale = pauseRelease;
        // 現在のシーンを再読み込み
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
        // スコアを初期化
        ScoreInit();
    }

    // スコアを初期化
    void ScoreInit()
    {
        // スコアを0にする
        score = scoreInit;
        // スコアを表示
        scoreText.SetText("Score : " + score);
    }

    // スコアを加算
    public void ScoreAdded(int addScore)
    {
        // スコアを加算する
        score += addScore;
        // スコアが最大値だった場合
        if (score <= scoreMax)
        {
            // スコアを最大値にする
            score = scoreMax;
        }
        // スコアを表示
        scoreText.SetText("Score : " + score);
    }

    // ボスのHPを表示・更新
    public void EnemyBossHPDisplay()
    {
        // ボスのHP表示(テキスト)を更新
        UI_BossHPText.SetText("BossHP : " + bossHPCurrent + " / " + bossHPMax);
        // ボスのHPゲージの最大値を更新
        UI_BossHPGauge.maxValue = bossHPMax;
        // ボスのHPゲージの現座値を更新
        UI_BossHPGauge.value = bossHPCurrent;
    }
}

メンバ変数にボス関連のものを追加しています。

ボスが出現する条件のスコア、ボスのオブジェクト、ボスの出現位置、ボスのUIをまとめているオブジェクト、ボスのHP表示のテキストとゲージがあります。

内部処理用として、ボスHPの最大値とボス出現のフラグがあります。

そして外部アクセス用にボスの現在のHPを用意しました。

Start関数ではボスのHPを初期化しています。

Update関数では、ボスの出現条件を一定のスコアを超えた時にしたので、閾値を超えたらボス出現フラグをアクティブにします。

そしてボスのステータス(HP表示)をアクティブにして、敵の自動生成を止めてからボスを生成・出現させています。

EnemyBossHPDisplay関数では、ボスのHP表示のテキスト・HPゲージの更新をしています。

最後に、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("EnemyBoss"))
        {
            // ダメージの算出
            PlayerHPChanged((int)calcType.damage, EnemyController.EnemyBossBodyDamage, 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)
    {
        // 自身を非アクティブ
        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;
                }
                // 接触オブジェクトのタグがItemまたはEnemyBulletの場合
                if (collision.gameObject.CompareTag("Item") || collision.gameObject.CompareTag("EnemyBullet"))
                {
                    // 接触したオブジェクトを消す
                    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
}

変化があるのは
  • OnTriggerEnter2D関数内にEnemyBossの分岐が増えた
  • それに伴いPlayerExplosion関数のDestroyの処理
  • PlayerHPChanged関数の一部が変更
という変化です。

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

まずはGameManagerの追加項目を設定します。

GameManagerの追加項目を設定

ボス出現のスコアの閾値、ボスのオブジェクト、そしてUIです。

次はEnemy_Bossプレハブになります。

Enemy_Bossプレハブの追加項目を設定

敵の種類、HP、爆発演習を設定します。

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

ボス出現のスコアの閾値を超えたらボスが出現した

ボスのダメージにUIが連動

ボスを撃破

ボスの出現から撃破までが実装出来ました。

まとめ

今回は
  • ボスを実装した
という事をやりました。

後は自由にゲームを作ってみてください。

ステージクリアを表示する、次のステージに遷移する、タイトル画面を作る、ボス演出をもっと良くするetc…考えられるものは皆さんのアイディア次第になります。

無能は今回シューティングゲームを作ってみましたが、あくまでも一つの事例です。

皆さんがゲームを作る際の参考事例の一つに過ぎませんので、無能以上の良いものがあればそちらを参考にしてください。

ゲームの道は一日にして成らず…。

時にはモチベーションが下がったり、解決できない壁が出てきたりします。

無能はそんな時は、習慣で乗り切ります。

無能の習慣はほぼ毎日Unityを触ることです。時間は決まっていません。
不思議なもので、どんなにやる気が起きなくても30分~1時間はUnityに触っています。

体調面でどうしようもない時や、用事でUnityが触れないことがあるため、必ず毎日ではありませんが、ほぼ毎日です。

Unityをやらない日は、何となく罪悪感と言うか物寂しい感じになります。

このような習慣があるから、無能のゲーム作りはどうにか続いています。

皆さんもモチベーションを上げる方法やる気を出す方法などがあったら、それらと共にUnityに触れてみてください。

皆さんのゲーム作りに幸あれ!

ではまた、何時か何処かで。

コメント