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

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

前回は、プレイヤーと敵にやられた時に爆発エフェクトが発生する仕組みを実装しました。

今回はAndroid向けに、プレイヤーの弾の発射ボタンを作りたいと思います。

Android向けに弾の発射ボタンを作る

Androidはコントローラーが無いので、Androidデバイスの画面上にボタンを作る必要があります。

この辺りはUI作成のセンスが問われるところですが、これまでのコンシューマー機を見ても色々なアプリをプレイしてみても、動くものと組み合わせるようなゲームの場合は左側(左手)が移動になって、右側が何らかのアクションを起こすようなボタンの配置になっています。

ですので、右側(右手側)に発射ボタンを設置し、発射ボタンを押下している間は弾を発射する、という処理を実装すれば大丈夫そうです。

という事で、作っていきましょう。

forAndroidControlにボタンを追加する

まずはボタンを作成しましょう。

既にforAndroidControlというCanvasがあってバーチャルスティックが動かせているので、こちらに発射ボタンを追加していきます。

forAndroidControlを選択しGameObject > UI > Button - TextMeshPro を選択します。

forAndroidControlを選択しGameObject > UI > Button - TextMeshPro を選択する

するとTextMeshProを初めて使う場合に、このようなダイアログが表示されます。

TextMeshProの初回作成時に表示されるダイアログ

これはTextMeshProを使う際に必要なものをインポートしてくれる機能になります。

ですので必要なものだけをインポートします。

Import TMP Essentialを押下します。

Import TMP Essentialを押下する

すると次の下のボタンがアクティブになります。

下のボタンがアクティブになる

こちらは今回は不要なので、右上の×ボタンでウィンドウを閉じます。

因みに今不要という事で閉じたものは、後から手動でインポートが可能です。

Window > TextMeshPro > Import TMP Examples and Extras でインポートが可能なので、必要に応じてインポートしてください。

これで取り敢えずforAndroidControlの子オブジェクトにボタンが作成できました。

forAndroidControlの子オブジェクトにボタンが作成できた

そしてボタンの子オブジェクトのテキストは、今回は使わないので削除します。

ボタンの子オブジェクトのテキストは、今回は使わないので削除する

ボタンの子オブジェクトのテキストを削除した状態

では、このボタンを使って設定していきます。

ボタンにEvent Triggerを設定する

さて、このボタンはどのように使うのかという事を説明します。

ボタンの外側の挙動だけをボタンで設定して、中身をEvent Triggerというもので設定します。

ではボタンの名前を「ShotButton」にして、まずは外側の挙動を設定します。

ボタンの名前を「ShotButton」に変更する

外側の挙動と言うのは、ボタンが押された間中は色が変わって押されていることを知らせてくれる挙動です。

ボタンのInspectorビューにあるPressed Colorで設定します。

色は好みで問題ありません。
無能は黄色にしました。

ボタンのInspectorビューにあるPressed Colorで設定する

次にボタンの位置を右下にします。

ボタンの位置を右下にする

ボタンの座標

そしてただの四角ではショットボタンだと分かり辛いので、画像を設定して弾が発射するボタンだと分かるようにします。

今回はこのような画像を設定しました。
※二次配布に抵触する可能性があるため、画像はリンク先を参照してください。


この画像をインポートして設定したものになります。

Gameビューのイメージ

ボタンの大きさ

設定した画像

では一度ゲームを実行して、ボタンの挙動を見てみます。

ボタンが押されている間黄色に変わった

これでショットボタンが押されていることが分かるようになりました。

では次にEvent Triggerで押された時の挙動を設定します。

ボタンのInspectorビューの下にあるAdd Componentボタンから、Event Triggerを検索してアタッチします。

ボタンのInspectorビューの下にあるAdd ComponentボタンからEvent Triggerを検索してアタッチする

Event Triggerを検索

Event Triggerをアタッチした

アタッチ出来たら、Event Triggerの中にあるAdd New Event Typeを押して、2種類のイベントタイプを追加します。

まずはボタンが押された時のPointerDownと、ボタンから指が離れた時のPointerUpの2種類を追加します。

PointerDownとPointerUpを追加する

Event Triggerとはそもそもどういうものなのかと言うと、Canvasを作成した時にEvent Systemが一緒に作られますが、そのEvent Systemとやり取りが発生する仕組みになります。

詳細はドキュメントを参照してください。

EventTrigger - Unity Documentation

今回のやることを例にすると、ボタンが押されたらPointerDownへイベントが走ります。

その時、PointerDownに登録された関数が呼ばれて、関数の処理が実行されます。

PointerUpも同様になります。

Roll a Ballを作成したことがあるなら、ステージの終わりにリトライ(コンティニュー)ボタンを追加して再度ステージを実行できる処理を実装したと思いますが、OnClickに関数を設定するのとほぼ同じような感じになります。

では何となく理解できたところで、スクリプトに処理を追記しましょう。

Event Triggerで設定したイベント用の関数を追記する

さて、方法はわかりましたが、どのような処理を書けばボタンに発射の処理が実装できるか見当を付けましょう。

今の状態だと、ボタンが押された(押されている)・ボタンが元に戻る、という二つの状態があることが分かります。

二つの状態と聞いてピンときた方は鋭いですね。

そうです、bool値を使ってこの二つの状態を表現します。
所謂フラグというやつです。

  • ボタンが押された(押されている)=true
  • ボタンが元に戻る=false
とすればいいですね。

そしてtrueになっている間は発射し続けるようにスクリプトに追記してやればいい感じに実装出来そうです。

では見当も付いたことだし実際にスクリプトに追記します。

このようになります。

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;

    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);
        }
    }

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

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

    // 発射ボタンが元に戻った時のイベント
    public void OnPointerUp()
    {
        // 発射ボタンのフラグを変更
        isShotPressed = false;
    }
}
では見ていきましょう。

まずメンバ変数にisShotPressedというbool型の変数を追加しました。

この変数をフラグとして扱えば良いので、OnPointerDown関数とOnPointerUp関数を追加しています。

OnPointerDown関数とOnPointerUp関数は、外部アクセスが必要になるのでアクセスレベルをpublicにしています。

この関数たちでは先程のフラグの切り替えだけを行っています。

理由は既に発射の仕組みを実装しているので、発射条件にこのフラグを追加するだけで良いからです。

スペースキーが押されたら発射できる仕組みの条件に、isShotPressedがtrueの場合を追加しています。

ifの条件式に「|」(パイプ)が二つ並んでいますが、これはorの短絡評価です。

パイプ(|)で「または」という条件になります。

二つ並べると短絡評価になり、パイプ(|)の前の条件が条件式に合致していない場合は、残りの条件の評価は破棄されます。

仮にパイプ(|)一つで記述すると、条件式の内部が条件に合致していないとしても評価されるようになります。

間違いではないですが、無駄だと分かっているものをわざわざ処理させるのは負荷にもなり得るので、短絡評価で処理している形になります。

ゲーム作りでは短絡評価の方が使われるかと思いますが、ゲーム以外での評価やゲームでもシビアな評価が必要になる場合も考えられるので、この表記を覚えておきましょう。

また「&」(且つ)での評価も同様になります。

では設定する関数が出来たので、Event Triggerに追加した項目にそれぞれ設定します。

Event TriggerのPointerDownで+ボタンを押して項目を追加します。

+ボタンを押して項目を追加する

そして出てきた項目の左下の〇を押して、オブジェクト選択画面を表示します。

オブジェクト選択画面のSceneタブを選択してPlayerを選択します。

PlayerにPlayerControllerスクリプトがアタッチされているので、その中の関数にアクセスしたいのでこのようになります。

オブジェクト選択画面でSceneタブの中のPlayerを選択する

そして右側のNo Functionを開き、PlayerController > OnPointerDownを選択します。

PlayerController > OnPointerDownを選択する

同様にPointerUpも設定します。

PointerDownと同様に設定

ここで注意するのは、PointerDown・PointerUpに違う関数を設定しないようにしてください。

間違えやすいのはPointerDownにOnPointerUp関数を設定したり、その逆をやってしまう事です。

あと、PlayerControllerの中にOnPointerDown・OnPointerUp関数が出てこない場合は、関数のアクセスレベルがpublicでない可能性がありますので確認しましょう。

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

確認することはボタンが押されている間は弾が発射されて、離すと止まるかです。


無事想定通りの動きになりました。

これでAndroidデバイス向けの弾の発射ボタンが実装出来ました。

まとめ

今回は
Androidデバイス向けの弾の発射ボタンを作成した
Event Triggerでボタンのイベントを実装した
という事をやりました。

次回は、今回実装した内容がAndroidデバイスで制s上に表示されているか・動作しているかをビルドして確認したいと思います。

では、また次回!

コメント