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

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

前回は、各プラットフォーム向けにビルドして、ゲームの中断とコンティニューの処理が正常か確認しました。

今回はスコアが加算できるようにしたいと思います。

スコアの実装

スコアと一口に言っても、色々準備がありますね。

まずはスコアを表示するUIを作成していきます。

スコアのUIを作成する

スコアを表示出来るようにUIを作成します。

何処に表示させるかは好みですが、無能は下側にしたいと思います。

序に前回ちょっと上過ぎたUIも直しておきましょう。

まずはUIを修正します。

UIを修正(ゲーム中断)

UIを修正(ゲームオーバー)

文言の位置の修正

文言の位置の修正

では下側にスコアを表示するテキストを作成します。

はじめに、これからゲームの状態を表示させるようなオブジェクトを作ります。

「GameStatus」という名前の空のオブジェクトを作成し、GameStatusキャンバスの子オブジェクトにします。

「GameStatus」という名前の空のオブジェクトを作成し、GameStatusキャンバスの子オブジェクトにする

このGameStatusは常時表示させておくものになるので、ここにスコアを表示させたいと思います。

ではGameStatusの子オブジェクトに「Score」というテキストオブジェクトを追加します。

GameStatusの子オブジェクトに「Score」というテキストオブジェクトを追加する

表示するテキストはスクリプトで設定するので、ここでの設定は無意味になってしまいますが、表示する幅と高さを決めたいのでテキストを設定します。

スコアは一億点の手前の999999999にしておきます。

スコアの表示を99999999にする

あとはこのスコアのUIを下側の見やすい位置に表示します。

位置を調整

Scoreの位置

今はバーチャルスティックがありますが、ゲームが始まると見えなくなるので、この位置にしました。

ではスコアを加算する仕組みを実装していきます。

スクリプトでスコアを加算する仕組みを作る

ではどのようにスコアを加算する仕組みを作るか、という話になりますね。

敵はこれからも種類が増えていくことを踏まえると、敵それぞれに加算する点数を持たせるのが良さそうです。

そしてゲームの統括を行うGameManagerで、スコアを一括管理出来れば良いですね。

方針が決まったので、それらをスクリプトで表現していきます。

まずはスコアの統括をするGameManagerから追記します。

このようになります。

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

public class GameManager : MonoBehaviour
{
    [Header("画面上の発射ボタン")][SerializeField] GameObject ShotButton;
    [Header("ゲーム中断のUI")][SerializeField] GameObject UI_Pause;
    [Header("コンティニューのUI")][SerializeField] GameObject UI_Continue;
    [Header("スコアのUI")][SerializeField] TMP_Text scoreText;

    int pause = 0, pauseRelease = 1;                        // ゲーム中断時のタイムスケール
    int score = 0, scoreInit = 0, scoreMax = 99999999;      // スコア,スコアの初期値,最大値

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

    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);
        // スコアを初期化
        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);
    }
}
では見ていきましょう。

メンバ変数にスコアのUIとスコア関連のint型を追加しました。

そしてStart関数内でスコアを初期化するためScoreInit関数を呼んでいます。

ScoreInit関数はスコアを0にしてUIを更新します。

同様にStageContinue関数の最後でもScoreInit関数を呼び出し、コンティニューの際にスコアを初期化しています。

スコアを加算するのはScoreAdded関数で、こちらは敵を倒した際に外部からアクセスしてスコアを加算します。

引数のaddScoreをscoreに足し合わせています。

スコアが最大値の場合は、最大値以上加算されないようにしています。

これでスコアの表示・更新・加算の仕組みが出来たので、加算するための処理をEnemyCotrollerに追記したいと思います。

このようになります。

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

[RequireComponent(typeof(Rigidbody2D))]
public class EnemyController : MonoBehaviour
{
    [Header("敵の移動スピード")][SerializeField] float moveSpeed = 1f;
    [Header("敵の弾の発射")]
    [SerializeField] GameObject enemyBullet;    // 敵の弾
    [SerializeField] Transform enemyFirePos;    // 発射ポイント
    [SerializeField] float shotSpeed = 5f;      // 弾の速度
    [SerializeField] float shotDelay = 2f;      // 弾の発射の閾値
    
    [Header("加算する点数")][SerializeField] int addScore = 100;
    [Header("爆発エフェクト")][SerializeField] GameObject enemyExplosion;

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

    Rigidbody2D enemyRB;                        // 敵のリジッドボディ
    float shotInterval = 0;                     // 弾の発射管理用

    void Start()
    {
        // 敵のリジッドボディを取得
        enemyRB = GetComponent<Rigidbody2D>();
        // ゲームマネージャーを取得
        gameManager = GameObject.FindWithTag("GameManager").GetComponent<GameManager>();
    }

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

    void FixedUpdate()
    {
        // 敵を動かす
        EnemyMove();
    }

    // 敵を動かす
    void EnemyMove()
    {
        // 敵に左方向のベクトルを持たせる
        enemyRB.velocity = new Vector2(-moveSpeed, 0);
    }

    // 接触判定
    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);
        }
    }

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

    // 爆発演出
    void EnemyExplosion(Collider2D collision)
    {
        // 接触した方(プレイヤー)を破棄
        Destroy(collision.gameObject);
        // 自身を破棄
        Destroy(gameObject);
        // 爆発エフェクトを生成
        Instantiate(enemyExplosion, transform.position, Quaternion.identity);
    }
}
では見ていきましょう。

メンバ変数に加算するためのaddScoreと関数にアクセスするためgameManagerを追加しています。

Start関数ではGameManagerというタグが付いたオブジェクトを見つけ出し、GameManagerを取得しています。

これまで同様にInspectorビューで設定するようにしたいのですが、HierarchyビューにあるGameManagerをドラッグしても、オブジェクト選択画面にも出てきません。

このような場合は、手段の一つとしてタグを付けてオブジェクトを見つけ出し、そのオブジェクトのコンポーネントを取得する、という方法があります。

その方法をStart関数で行ってGameManagerを取得していますので、もしInspectorビューで設定出来ない場合は参考にしてみてください。

スコアを加算するのはプレイヤーの弾と接触した際の爆発演出後にしています。

これでスコアが加算されるようになったので、諸々の設定をして確認します。

最初にGameManagerにタグを作成して付けます。

GameManagerにタグを作成して付ける

GameManagerにスコアのUIを設定します。

スコアのUIを設定する

Enemyプレハブで加算する点数を決めます。

100点にしていますが自由に決めてください。

加算する点数を決める(初期値は100)

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

スコアが加算された

あとはコンティニューの際にスコアがリセットされているか確認します。

コンティニューしたらスコアがリセットされた

これでスコアの加算が実装出来ました。

フォントが切れてしまう等の場合は、同じフォントの太いものを使いフォントアセットを作成するか、全く別の見やすいフォントでフォントアセットを作成してください。

まとめ

今回は
  • スコアの処理を実装した
という事をやりました。

次回はプレイヤーが一発でやられてしまうのは面白くないので、プレイヤーにHPを付けたいと思います。

では、また次回!

コメント