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

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

前回は、HP回復以外のアイテムを追加しました。

今回は、これまで追加実装したプレイヤーのHPやアイテムがあるので、それらにまつわる効果音を付けていきたいと思います。

追加する効果音

これまで追加で実装したものと言うと
  • プレイヤーにHPを持たせた
  • アイテムを実装した
大まかにこれらです。

プレイヤーにHPを持たせたことによって、敵の弾に当たるなどの爆発演出以外の効果音が必要になりました。

後はアイテムが実装されたので、アイテムを取得した際の効果音も必要です。

やはり取得したりダメージを受けても反応が無いのは寂しいですよね。

ということで、まずは素材集めから始めましょう。

プレイヤー被弾時の効果音を付ける

プレイヤーが被弾した時の効果音から付けていきます。

ガン、という金属を殴った時のような音にします。
※二次配布に抵触する可能性があるため、リンク先を参照してください。

ロボットを殴る1 - 効果音ラボ

では素材をUnityにインポートします。

素材をインポート

被弾した時の音をスクリプトから鳴らします。

被弾してもプレイヤーが生き残る場合、つまり爆発演出がされない時だけ被弾した効果音を鳴らすようにします。

音を鳴らすには、PlayClipAtPointを使えば良さそうです。

このようになります。

PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using TMPro;
using Unity.VisualScripting;
using UnityEngine;
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-SE
    [Header("効果音関連")]
    [SerializeField] AudioClip SE_playerDamaged;                // プレイヤー被弾時の効果音
    [SerializeField] float playerDamagedVol = 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;           // プレイヤーの残機減少用,表示用
    #endregion
    #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);
        }
        // 発射間隔をリセット
        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)
    {
        // 弾を生成
        GameObject bulletClone = Instantiate(playerBullet, 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.bodyDamage, collision);
        }
        // 敵の弾の場合
        else if (collision.gameObject.CompareTag("EnemyBullet"))
        {
            // ダメージの算出
            PlayerHPChanged((int)calcType.damage, EnemyController.shotDamage, 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:
                // ダメージ分を減算する
                playerHPCurrent -= damage;
                // 接触したオブジェクトを消す
                Destroy(collision.gameObject);
                // プレイヤーのHPが0以下(最小値以下)だった場合
                if (playerHPCurrent <= playerHPMin)
                {
                    // 爆発演出
                    PlayerExplosion(collision);
                    // プレイヤーのHPを0(最小値)にする
                    playerHPCurrent = playerHPMin;
                    // 残機を更新
                    RemainingCounter(changeRemaining);
                }
                // それ以外の場合
                else
                {
                    // 被弾時に効果音を再生
                    AudioSource.PlayClipAtPoint(SE_playerDamaged, 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;
        // プレイヤーのHPを更新
        PlayerHPDisplay();
    }
    #endregion
    #endregion
}

では見ていきましょう。

メンバ変数に被弾時のSEのAudioClipと効果音の音量を追加しました。

PlayerHPChanged関数のダメージを受けた時の処理にPlayClipAtPointを追加しています。

ここが爆発演出を伴わない場合、プレイヤーが生き残っている場合になります。

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

追加された項目を設定

効果音が聴こえない

あれ…?効果音が聴こえない…?

エラーは出ていない

エラーが出ていないという事は、処理は正常にされているようです。

つまり、効果音が正常に再生されている、という事になります。

じゃあどうして聴こえないのかと言うと、それはメインカメラから遠く離れた位置で再生されているからです。

という事は、プレイヤーの位置ではなく、AudioListenerがアタッチされているメインカメラの側で再生してやれば聞こえるようになります。

要は耳元で再生してやるという事ですね。

そこでPlayClipAtPointの行をこのようにします。

PlayerController.cs - PlayClipAtPointの修正
// 被弾時に効果音を再生
AudioSource.PlayClipAtPoint(SE_playerDamaged, Camera.main.transform.position, playerDamagedVol);

再生位置をプレイヤーの位置ではなく、メインカメラの位置にしてやれば大丈夫です。

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

被弾時の効果音が聴こえた

被弾時の効果音が聴こえるようになりました。

ではアイテム取得時の方もやっていきましょう。

アイテム取得時の効果音を付ける

まずは素材を準備します。
※二次配布に抵触する可能性があるため、リンク先を参照してください。

カーソル移動3 - 効果音ラボ

Unityにインポートします。

素材をインポート

アイテム取得時もアイテム自身は破棄されるので、同様にPlayClipAtPointで音を鳴らします。

このようになります。

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;                  // 残機の増加量
    #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 };         // 内部処理用(アイテムの効果の種類)
    #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;

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

処理自体はプレイヤーの被弾時と同じ処理です。

こちらも効果音を再生するタイミングを考えれば、switch文を抜けた時が全てのアイテムに適用できますね。

アイテムはプレハブなので、全てのアイテムに追加の項目を設定する必要があります。

アイテム全てに追加された項目を設定

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

アイテム取得時の効果音が再生された

これで効果音が追加できました。

音量などは各自の好みに設定してください。

まとめ

今回は
  • 追加実装した処理に効果音を付けた
という事をやりました。

次回は、敵が真っ直ぐ動くだけなので、種類を増やして動きを付けたいと思います。

では、また次回!

コメント