UnityでFPSをつくる その11-2 [ 音 ]
前回は、TitlleSceneでBGMと決定音の実装を行いました。

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

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

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で指定する為、変数を宣言。

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

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

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

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

アクセスレベルのエラーが発生しているので、スクリプト「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にしています。


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

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

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

まずは、「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に変更しています。


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

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

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

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

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

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