UnityでFPSをつくる その11-2 [ 音 ]

前回は、TitlleSceneでBGMと決定音の実装を行いました。

No.670

今回はMainSceneに音を実装していきます。
まずはBGMです。
MainSceneに切り替えて、SceneManagerオブジェクトに、
Add Component → Audio → Audio Source を加えてください。

No.671

AudioClipの項目に「BGM_MainScene」を指定、Play On Awakeの項目でチェックを外し、Loopの項目にチェックを入れます。

No.672

BGMはボイス音の直後に再生させたいので、同じスクリプト内で制御していきます。
「VoiceSoundManage」と名付けて新規スクリプトを作成し、以下のコードをコピペしてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class VoiceSoundManage : MonoBehaviour
{
    [SerializeField] AudioClip voiceSound_getReady, voiceSound_start;

    AudioSource audioSource;

    ImmediatelyAfterTheStart immediatelyAfterTheStart;

    bool justOnce_voiceSound_getReady, justOnce_voiceSound_start, justOnce_bgm;

    void Start()
    {
        audioSource = GetComponent<AudioSource>();

        immediatelyAfterTheStart = GameObject.Find("SceneManager").GetComponent<ImmediatelyAfterTheStart>();
    }

    void Update()
    {
        if (immediatelyAfterTheStart.getReady.enabled && !justOnce_voiceSound_getReady)
        {
            audioSource.PlayOneShot(voiceSound_getReady);

            justOnce_voiceSound_getReady = true;
        }

        if (immediatelyAfterTheStart.start.enabled && !justOnce_voiceSound_start)
        {
            audioSource.PlayOneShot(voiceSound_start);

            justOnce_voiceSound_start = true;
        }

        if (immediatelyAfterTheStart.battleStartFlag && !justOnce_bgm)
        {
            audioSource.Play();

            justOnce_bgm = true;
        }
    }
}

基本の流れはTitleSceneの時と同じです。
AudioSourceから鳴らしたい音をAudioClipで指定する為、変数を宣言。

No.673

音を鳴らすタイミングをImmediatelyAfterTheStartスクリプトから判断する為、取得用の変数を宣言。

No.674

一度だけ実行する為に、判定用フラグの宣言。

No.675

AudioSourceとスクリプトのコンポーネントを取得。

No.676

開始のフラグが立った時点で、一度だけBGMやボイスの再生を開始。
AudioSourceのAudioClipにはBGMの音を指定しているので、そのままPlayメソッドで再生。
ボイスは各ボイス音を指定して、PlayOneShotメソッドで再生します。

No.677

アクセスレベルのエラーが発生しているので、スクリプト「ImmediatelyAfterTheStart」へ以下のコードをコピペしてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ImmediatelyAfterTheStart : MonoBehaviour
{
    [SerializeField] PlayerMove playerMove;

    [SerializeField] SightDisplay sightDisplay;

    [SerializeField] Image sight;

    [SerializeField] EnemyMove enemyMove;

    [SerializeField] EnemyShooting enemyShooting;

    //11-2追記
    public Text getReady;

    //11-2追記
    public Text start;

    float timeElapsed,

          flameOutPositionY = 600f,

          getReadyTextStartTime = 0.5f,

          getReadyTextEndTime = 2f,

          startTextStartTime = 2f,

          startTextEndTime = 3.5f,

          battleStartTime = 4.5f;

    bool getReadyTextFlag, startTextFlag;

    //11-2追記
  //publicを付けて外部からアクセス可能にする
    public bool battleStartFlag;

    // Start is called before the first frame update
    //ゲーム開始直後では停止させておきたいスクリプトやUIをOFF
    void Start()
    {
        playerMove.enabled = false;

        sightDisplay.enabled = false;

        sight.enabled = false;

        enemyMove.enabled = false;

        enemyShooting.enabled = false;
    }

    // Update is called once per frame
    void Update()
    {
        //時間計測
        timeElapsed += Time.deltaTime;

        //開始時間になったらgetReadyを表示、処理の繰り返しを避ける為にフラグを立てる
        if (timeElapsed >= getReadyTextStartTime && !getReadyTextFlag)
        {
            getReady.enabled = true;

            getReadyTextFlag = true;
        }
        //終了時間になったらgetReadyを非表示
        else if (timeElapsed >= getReadyTextEndTime && getReady.enabled)
        {
            getReady.enabled = false;
        }

        //開始時間になったらstartを表示、処理の繰り返しを避ける為にフラグを立てる
        if (timeElapsed >= startTextStartTime && !startTextFlag)
        {
            start.enabled = true;

            startTextFlag = true;
        }
        //終了時間になったらstartを+y方向へフレームアウト
        else if (timeElapsed >= startTextEndTime && start.transform.localPosition.y < flameOutPositionY)
        {
            //どのくらい時間をかけるのか
            var frameOutDuration = 0.1f;

            start.transform.localPosition += new Vector3(0f, flameOutPositionY * Time.deltaTime / frameOutDuration, 0f);
        }

        //バトル開始時間になったらスクリプトや、UIをON。処理の繰り返しを避ける為にフラグを立てる
        if (timeElapsed >= battleStartTime && !battleStartFlag)
        {
            playerMove.enabled = true;

            sightDisplay.enabled = true;

            sight.enabled = true;

            enemyMove.enabled = true;

            enemyShooting.enabled = true;

            battleStartFlag = true;
        }
    }
}

VoiceSoundManageスクリプトからアクセスできるよう、該当する変数のアクセサをpublicにしています。

No.678
No.679

SceneManagerオブジェクトへVoiceSoundManageスクリプトをアタッチし、AudioClipに同名のファイルを指定します。

No.680

実行してみましょう。

ボイス音とBGMの実装が完了しました。
次は被弾音の実装をしていきます。

No.682

被弾音は2種類用意してあります。
ライフの残りが有る場合と、無い場合です。

No.683

まずは、「DamageSoundManage」と名付けて新規スクリプトを作成し、以下のコードをコピペしてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DamageSoundManage : MonoBehaviour
{
    //HP「4~1」の時に鳴らす音、HP「0」の時に鳴らす音
    [SerializeField] AudioClip damageSound_player, damageSound_noLife_player;

    AudioSource audioSource;

    //プレイヤーのHPを参照する為のクラス型変数
    PlayerHP playerHP;

    //音を1回だけ鳴らす為の判断フラグ
    bool justOnce_damageSound_noLife_player;

    // Start is called before the first frame update
    void Start()
    {
        audioSource = GetComponent<AudioSource>();

        playerHP = GameObject.Find("Player").GetComponent<PlayerHP>();
    }

    // Update is called once per frame
    void Update()
    {
        if (playerHP.playerHP > 0 && playerHP.damageSoundFlag)
        {
            audioSource.PlayOneShot(damageSound_player);

            playerHP.damageSoundFlag = false;
        }
        else if(playerHP.playerHP <= 0 && !justOnce_damageSound_noLife_player)
        {
            audioSource.PlayOneShot(damageSound_noLife_player);

            justOnce_damageSound_noLife_player = true;
        }
    }
}

ボイス音の実装と同様に、ループ再生をしない為のフラグを用意し、他のオブジェクトにアタッチされたスクリプトから再生のタイミングを計っています。

アクセスレベルのエラーを修正する為に、PlayerHPスクリプトへ以下のコードをコピペしてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PlayerHP : MonoBehaviour
{
    //11-2追記
    public bool damageSoundFlag;

    //HPゲージ
    //11-2追記
    //publicへ変更
    public int playerHP;

    //被ダメージ量
    [SerializeField] int receveDamageScore;

    //被ダメージ演出用ボード
    public Image damageBoard, deathStateBoard;

    //HPゲージ用のUI複数をまとめたオブジェクト
    public GameObject remainingHp;

    //カメラの動きを制御する
    Transform transformCamera;

    //カメラを揺らす時間の長さ、大きさ
    [SerializeField] float duration, magnitude;

    //相対位置取得用
    Vector3 pos;

    // Start is called before the first frame update
    void Start()
    {
        //被ダメージ演出用ボードを透明に初期化
        damageBoard.color = Color.clear;

        //カメラのTransformを取得
        transformCamera = Camera.main.transform;

        //Playerからの相対位置を取得
        pos = transformCamera.localPosition;
    }

    // Update is called once per frame
    void Update()
    {

    }

    void OnTriggerEnter(Collider other)
    {
        //弾にぶつかったら
        if (other.tag == "EnemyBullet")
        {
            //11-2追記
            damageSoundFlag = true;

            //プレイヤーのHPを減少させる
            playerHP -= receveDamageScore;

            //画面を点滅させるコルーチンの開始
            StartCoroutine("damageFlashing");

            //カメラを揺らすコルーチンの開始
            StartCoroutine(shakeCamera(duration, magnitude));

            //ダメージを受けたら対応するHPゲージを黒色にする
            if (playerHP <= 4)
            {
                remainingHp.transform.Find("RemainingHp_5").gameObject.GetComponent<Image>().color = Color.black;
            }
            if (playerHP <= 3)
            {
                remainingHp.transform.Find("RemainingHp_4").gameObject.GetComponent<Image>().color = Color.black;
            }
            if (playerHP <= 2)
            {
                remainingHp.transform.Find("RemainingHp_3").gameObject.GetComponent<Image>().color = Color.black;
            }
            if (playerHP <= 1)
            {
                remainingHp.transform.Find("RemainingHp_2").gameObject.GetComponent<Image>().color = Color.black;
            }
            if (playerHP <= 0)
            {
                remainingHp.transform.Find("RemainingHp_1").gameObject.GetComponent<Image>().color = Color.black;

                //スクリプトをOFFにする
                GetComponent<PlayerMove>().enabled = false;

                //その場で停止させる
                GetComponent<Rigidbody>().velocity = Vector3.zero;

                //死亡状態用に画面を赤くする
                deathStateBoard.color = new Color(1f, 0f, 0f, 0.9f);

                //ゲームオーバーシーンへ遷移
                GameObject.FindGameObjectWithTag("SceneManager").GetComponent<GameOverSceneChange>().ToGameOverScene();//追記部分
            }
        }
    }

    //画面を点滅
    IEnumerator damageFlashing()
    {
        //被ダメージ演出用ボードを薄い赤色にする
        damageBoard.color = new Color(1f, 0f, 0f, 0.7f);

        //次の処理に移行するまでの待機時間
        yield return new WaitForSeconds(0.15f);

        //被ダメージ演出用ボードを透明にする
        damageBoard.color = Color.clear;
    }

    //カメラを揺らす
    IEnumerator shakeCamera(float duration, float magnitude)
    {
        //時間計測用
        var elapsed = 0f;

        //指定の時間を経過するまで
        while (elapsed < duration)
        {
            //カメラのXY(縦横)位置を動かす
            transformCamera.localPosition = new Vector3(pos.x + Random.Range(-1f, 1f) * magnitude, pos.y + Random.Range(-1f, 1f) * magnitude, pos.z);

            //経過時間
            elapsed += Time.deltaTime;

            //次フレームで処理を再開。
            yield return null;
        }
        //揺れる前の位置にカメラを戻す
        transformCamera.localPosition = pos;
    }
}

残りHP4~1の時に、1回ずつ鳴らす為の判定フラグを宣言し、被弾した際にフラグを立てます。
また、playerHP変数のアクセサをpublicに変更しています。

No.684
No.685

DamageSoundManageスクリプトをSceneManagerオブジェクトにアタッチし、AudioClipの項目へ同名の音声ファイルを指定します。

No.686

実行して確認します。

被弾音の実装完了です。
ここで一つ不具合を発見したので、修正したいと思います。
敵の弾が接近してきた際に、照準が見えなくなっています。

No.688

bloomエフェクトの影響なので、照準のUIだけは別Canvasに移したいと思います。
Hierarchyで新たにCanvasを作成し、Sightオブジェクトをそこへ移動させます。

No.689

InspectorからSightのTransformの値をリセットし、以下のように入力してください。

No.690

これで照準が見えるようになりました。

No.691

長くなってきたので、続きは次回にしたいと思います。おつかれさまでした!

FPS

Posted by kenji