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

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

前回は、ゲームの中断を実装しました。

今回は前回の続きで、コンティニューの実装をしたいと思います。

コンティニューの実装

ではコンティニューの実装ですが、ゲームの中断と同様にUIから作成します。

えー、またUIを作るの…?と思われた方、安心してください。
既に前回のゲームの中断で基礎が出来ています。

難しいUIでなければ、複製して文言を変えるだけで大丈夫です。

何故かと言うと、要点としては
  • GameOver
  • コンティニューしますか?
  • はいボタン
  • いいえボタン
だけです。

前回のゲームの中断と似ていますね。

要はゲームオーバーの表示と質問→質問に応答するボタンという感じです。
なので簡単にUIを実装できます。

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

ゲームの中断UIを複製する

ゲームの中断のUIの頭(親オブジェクト)はPauseになっています。

そのPauseを複製してやれば大丈夫です。

Pauseを複製

後はそれぞれを相応しい・処理に合致する名前にしていきます。

GameOverで問われることと言えば、まずはGameOverの表示、そしてコンティニューするかでですね。

GameOverは前回作成したPauseを変更すればいいので楽勝ですし、違う文言はテキストのPauseの部分だけになります。

GameOverという表示の下にYes・Noボタンがあるという構造も同じなので、名前を変えて整理してみましょう。

名前を変えて複製したオブジェクトを整理してみる

これでオブジェクトの名前の整理が出来ました。

次に各オブジェクトで表示させる文言を変更します。

Text_GameOverはGameOverを伝える文言になります。

一度Continueをアクティブにしてから作業してみます。

Text_GameOverの文言を変更する

Gameビュー

これだけでコンティニューのUIが出来ました。

もし違うコンティニュー用のUIが必要であれば作成しても問題ありません。

コンティニュー処理を実装

ではスクリプトでコンティニューの処理を実装します。

Noボタンの場合は、ゲームの中断と変わらずアプリを終了するので変更はありません。

ということは、Yesボタンの処理だけ作れば大丈夫です。

このようになります。

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

public class GameManager : MonoBehaviour
{
    [SerializeField] GameObject ShotButton;     // 画面上の発射ボタン
    [SerializeField] GameObject UI_Pause;       // ゲーム中断のUI
    [SerializeField] GameObject UI_Continue;    // コンティニューのUI

    int pause = 0, pauseRelease = 1;            // ゲーム中断時のタイムスケール

    void Start()
    {
#if PLATFORM_STANDALONE_WIN
        ShotButton.SetActive(false);
#endif
    }

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

    // ゲーム中断
    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);
    }


}

では見ていきましょう。

メンバ変数にUI_Pause同様にコンティニューのUIを追加しています。

ゲームオーバーの際にアクティブにして表示させるためですね。

VisibleUI_Continue関数でコンティニューのUIをアクティブにするのですが、一度ゲームの中断と同様にゲームの流れを止めます。

何故ならゲームオーバーとなっても、いなくなった(破棄された)のはプレイヤーだけなので、ゲームの世界では絶えず敵が生成され続けてしまうからです。

ゲームの流れを止めた後にUIを表示します。

UIを表示した後はYes・Noボタンだけが操作可能なので、Yesボタンが押された場合にコンティニューの処理が走るようにしてあげます。

コンティニューの処理ですが、usingの部分にUnityEngine.SceneManagementが追加されています。

これはSceneManagerを使う時に必要になるものです。

StageContinue関数で、現在のシーンを再読み込みしています。

括弧内はどうなっているかと言うと、現在のアクティブになっているシーン番号を取得しています。

シーン番号と言うのは、ビルド設定画面で確認できます。

シーンの右側の数字がシーン番号

これはシーンを追加・削除・並び替えをすると自動的に割り振られるようになっています。

ですのでシーン一つひとつが必ずユニークな番号を持っています。

括弧内ではこの番号を取得していたんですね。

一つ注意点として、タイムスケールを戻さないとシーンを再読み込みしても止まったままになります。

ですのでStageContinue関数ではシーン再読み込み前にタイムスケールを戻しておきます。

結構忘れがちな処理なので覚えておきましょう。

ではYesボタンのOnClick設定を変更しましょう。

YesボタンのOnClick設定を変更する

次にContinueを非アクティブにします。

Continueを非アクティブにする

次にGameManagerに追加されたUI_Continueを設定します。

GameManagerに追加されたUI_Continueを設定する

これでコンティニューのUIの準備が出来ました。

あとはこのUIをアクティブにするには、PlayerControllerからGameManagerにアクセスして、VisibleUI_Continue関数を呼ぶようにすれば良いので、PlayerControllerにその処理を追記します。

このようになります。

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

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

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

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

    [Header("ゲームマネージャー")][SerializeField] GameManager gameManager;

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

    void Start()
    {
        // プレイヤーのRigidbodyを取得
        playerRB = GetComponent<Rigidbody2D>();
    }

    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);
        }
        // 発射間隔をリセット
        bulletInterval = 0;
        // --- ここまで

    }

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

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

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

    // プレイヤーの移動制限
    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;
    }

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

    // 接触判定
    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 敵の場合
        if (collision.gameObject.CompareTag("Enemy"))
        {
            // 爆発演出
            PlayerExplosion(collision);
        }
        // 敵の弾の場合
        else if (collision.gameObject.CompareTag("EnemyBullet"))
        {
            // 爆発演出
            PlayerExplosion(collision);
        }
        // プレイヤーの弾の場合
        else if (collision.gameObject.CompareTag("PlayerBullet"))
        {
            // 処理を中断
            return;
        }
        // コンティニュー
        Continue();
    }

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

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

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

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

では見ていきましょう。

メンバ変数にGameManagerを追加しています。

これはGameManagerの中にあるVisibleUI_Continue関数にアクセスするためです。

次に接触判定のOnTriggerEnter2D関数内のif文に、PlayerBulletに接触した場合のelse if文を追加しています。

これは何故かと言うと、プレイヤーが弾を撃った瞬間も接触判定がなされてしまうので、PlayerBulletの場合は処理を中断します。

そしてOnTriggerEnter2D関数の最後にContinue関数を呼んでいます。

Continue関数では、GameManagerのVisibleUI_Continue関数を呼びます。

これでコンティニューのUIが表示されるので、コンティニューが可能になります。

ではPlayerで追加されたgameManagerを設定します。

HierarchyビューにあるGameManagerオブジェクトをドラッグして設定できます。

Playerに追加されたgameManagerを設定する

コンティニュー出来るようになったか、ゲームを実行して確認します。

確認することは
  • プレイヤーが弾を発射してもコンティニューの処理にならない(つまり何も起きない事)を確認
  • プレイヤーが敵の弾を受けた、若しくは敵と接触したらコンティニューのUIが表示されることを確認
  • コンティニューのUIでYesを押したらシーンが再読み込みされるかを確認
の三点になります。

プレイヤーが弾を撃っても問題無し

コンティニューのUIが表示された

Yesボタンでシーンが再読み込みされた

これでゲームの中断とコンティニューの処理が実装出来ました。

まとめ

今回は
コンティニューの処理を実装した
という事をやりました。

次回は、ゲームの中断とコンティニューの処理が正しく出来るかを確認するため、各デバイス向けにビルドしていきます。

では、また次回!

コメント