
前回、セーブとタイトル画面を作って、遊んだ続きから始められるようにしました。でも、ここまでのゲームは、ずっと音が出ていませんでした・・・。今回は、ボタンを選ぶ・攻撃が当たる・まほうを使う、といった場面に効果音を付けて、タイトル・ロビー・バトル・ボス戦で、BGMを切りかえたいと思います!
- シリーズの目次:ターン制RPGの作り方
- 前回:#25 セーブ・ロードとタイトル画面
今回作るもの

- どこからでも
SoundPlayer.Play(Sound.Hit)の1行で、効果音を鳴らせるようにする - 選ぶ・決める・取り消す、当たる・受ける、まほう・回復、倒れる、レベルアップ、勝ち・ぜんめつに、音を付ける
- タイトル・ロビー・バトル・ボス戦で、BGMを切りかえる。シーンを切りかえても、BGMがとぎれないようにする
| スクリプト | 変えること |
|---|---|
| SoundPlayer(新しく作る) | 効果音とBGMを鳴らす |
| MenuCursor | ボタンを選ぶ・押すときの音 |
| CommandMenu・TargetSelector | 取り消す・相手を選ぶときの音 |
| BattleEffects・UnitView | 当たる・受ける・回復・まほう・倒れるときの音 |
| BattleManager | レベルアップ・勝ち・ぜんめつの音、バトルとボス戦のBGM |
| LobbyMenu・TitleMenu | ロビーとタイトルのBGM |
音の鳴らし方(AudioSourceとPlayOneShot)は、12-1 音を鳴らそう:効果音とBGMで紹介しています。
スライムやっと、音が出るんですね!
音を用意しよう


効果音とBGMは、プログラムで作った仮の音を、素材のTurnBasedRpgAssets.zipに足しました。
- zipをダウンロードし直して、
TurnBasedRpg/Audioフォルダを、プロジェクトのAssets/TurnBasedRpgに入れる
自分の好きな音に差し替えるときは、同じファイル名にすると、設定を変えずにそのまま使えます。
| ファイル | 鳴らすところ |
|---|---|
| se_cursor・se_decide・se_cancel | ボタンを選ぶ・押す・取り消す |
| se_hit・se_damage | 敵に当たる・仲間が受ける |
| se_magic・se_heal | まほうを使う・回復する |
| se_defeat・se_levelup | 敵が倒れる・レベルが上がる |
| se_victory・se_lose | 勝ち・ぜんめつ |
| bgm_title・bgm_lobby・bgm_battle・bgm_boss | タイトル・ロビー・バトル・ボス戦の曲 |
BGMは、少しずつ読みこむ


BGMは、効果音よりずっと長いファイルです。最初に全部を読みこむと、メモリをたくさん使います。
- Audioフォルダで、bgm_title・bgm_lobby・bgm_battle・bgm_boss の4つを選ぶ(Ctrlを押しながらクリック)
- Inspectorの Load Type を Streaming にして、Apply を押す
Streamingにすると、鳴らしながら少しずつ読みこみます。効果音は短くて、すぐ鳴らしたいので、そのまま(Decompress On Load)にします。
SoundPlayerを作ろう


デッキ構築の#16と同じ形の、どこからでも鳴らせるSoundPlayerを作ります。今回は、シーンを切りかえても、BGMがとぎれないようにします。
SoundPlayer
Scriptsフォルダに、SoundPlayerを作ります。
using System.Collections.Generic;
using UnityEngine;
// 鳴らす効果音の種類
public enum Sound
{
Cursor, // 選んでいるものが動く
Decide, // 決める
Cancel, // 取り消す
Hit, // 敵に当たる
Damage, // 仲間が受ける
Magic, // まほうを使う(杖をふる・力をためる)
Heal, // 回復する
Defeat, // 敵が倒れる
LevelUp, // レベルが上がる
Victory, // 勝つ
Lose, // ぜんめつ
}
// BGM の種類
public enum Bgm
{
Title,
Lobby,
Battle,
Boss,
}
// 効果音と BGM を鳴らす。どのスクリプトからでも SoundPlayer.Play(Sound.Hit) のように呼べる
// シーンを切り替えても BGM がとぎれないように、GameSession と同じく1つだけ残す
public class SoundPlayer : MonoBehaviour
{
// 効果音の種類と、鳴らすファイル・大きさの組
[System.Serializable]
public class SoundEntry
{
public Sound sound;
public AudioClip clip;
[Range(0f, 1f)] public float volume = 1f;
}
// BGM の種類と、鳴らすファイルの組
[System.Serializable]
public class BgmEntry
{
public Bgm bgm;
public AudioClip clip;
}
[SerializeField] private AudioSource seSource; // 効果音用
[SerializeField] private AudioSource bgmSource; // BGM 用(Loop にチェック)
[SerializeField] private List<SoundEntry> sounds = new List<SoundEntry>();
[SerializeField] private List<BgmEntry> bgms = new List<BgmEntry>();
[SerializeField] private float hitPitchRange = 0.1f; // 当たる音の高さを、毎回この幅で変える
private static SoundPlayer instance; // 残っている SoundPlayer(1つだけ)
// その音を最後に鳴らしたフレーム(全体のまほうで、同じ音が何人分も重なって大きくならないように)
private readonly Dictionary<Sound, int> playedFrames = new Dictionary<Sound, int>();
private void Awake()
{
// 前のシーンから来た SoundPlayer があれば、そちらを使い、新しいほうは消す
if (instance != null)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
}
// 効果音を鳴らす(SoundPlayer が無いシーンでは何もしない)
public static void Play(Sound sound)
{
if (instance != null)
{
instance.PlayOne(sound);
}
}
// BGM を鳴らす。同じ曲が鳴っていれば、そのまま続ける(バトルが続いても、最初に戻らない)
public static void PlayBgm(Bgm bgm)
{
if (instance != null)
{
instance.ChangeBgm(bgm);
}
}
// BGM を止める(勝った・ぜんめつ・ボスの登場の前)
public static void StopBgm()
{
if (instance != null)
{
instance.bgmSource.Stop();
}
}
private void PlayOne(Sound sound)
{
// このフレームで、もう鳴らしていたら鳴らさない
if (playedFrames.TryGetValue(sound, out int frame) && frame == Time.frameCount)
{
return;
}
playedFrames[sound] = Time.frameCount;
foreach (SoundEntry entry in sounds)
{
if (entry.sound == sound)
{
// 当たる・受ける音は、高さを少しずつ変えて、何度鳴っても単調にならないようにする
bool changesPitch = sound == Sound.Hit || sound == Sound.Damage;
seSource.pitch = changesPitch ? 1f + Random.Range(-hitPitchRange, hitPitchRange) : 1f;
seSource.PlayOneShot(entry.clip, entry.volume);
return;
}
}
}
private void ChangeBgm(Bgm bgm)
{
foreach (BgmEntry entry in bgms)
{
if (entry.bgm == bgm)
{
if (bgmSource.clip == entry.clip && bgmSource.isPlaying)
{
return;
}
bgmSource.clip = entry.clip;
bgmSource.Play();
return;
}
}
}
}
- 音の種類は
enumにします(6-15 選択肢に名前を付けよう:enumとswitch文)。"hit"のような文字で書くと、打ちまちがえても気づけませんが、Sound.Hitならコンパイルの時点でエラーになります Awakeは、#17のGameSessionと同じ形です。どのシーンにもSoundPlayerを置いておき、最初に作られた1つだけを残します。BGMを鳴らしているAudioSourceが消えないので、シーンを切りかえても、曲がとぎれませんPlayBgmは、同じ曲がもう鳴っていたら、何もしません。地下の階では、Battleシーンを読みこみ直して次のバトルへ進みます(#21)。そのたびに曲が最初から始まらないようにしています- 全体のまほうでは、ダメージや回復の演出が、何人分も同じフレームに呼ばれます。同じ音が重なると大きくなりすぎるので、同じフレームの2回目からは鳴らしません。
Dictionaryに、音ごとに最後に鳴らしたフレームの番号(Time.frameCount)を入れて調べています
プレハブにして、3つのシーンに置く


どのシーンから再生しても音が鳴るように、プレハブにして、Title・Lobby・Battleの3つのシーンに置きます。
- Titleシーンを開き、Hierarchyで右クリック>Create Empty、名前を SoundPlayer にする
- Add Component で Audio Source を2つ付ける。どちらも Play On Awake のチェックを外す。2つ目は Loop にチェックを入れて、Volume を 0.5 にする(BGM用)
- Add Component で SoundPlayer を付ける。Se Source に1つ目、Bgm Source に2つ目のAudio Sourceをドラッグする。同じオブジェクトに2つあるので、Inspectorの上のほうのAudio Sourceを、Se Sourceの欄にドラッグします
- Sounds を 11 にして、上から Cursor・Decide・Cancel・Hit・Damage・Magic・Heal・Defeat・LevelUp・Victory・Lose の順に、Soundと同じ名前のファイル(se_cursor など)を入れる。Volume は、選ぶ音を0.6、決める・取り消す音を0.7、まほう・回復・倒れる音を0.8、レベルアップ・勝ち・ぜんめつを0.9、当たる・受ける音は1のまま
- Bgms を 4 にして、Title・Lobby・Battle・Boss に、bgm_title・bgm_lobby・bgm_battle・bgm_boss を入れる
- SoundPlayerを、ProjectのPrefabsフォルダにドラッグしてプレハブにする
- Lobbyシーン・Battleシーンを開き、PrefabsフォルダのSoundPlayerを、Hierarchyにドラッグして置く
Lobbyシーンから再生するとLobbyのSoundPlayerが、Titleから再生するとTitleのSoundPlayerが残ります。どのシーンからでも確かめられて、遊ぶときには、いつも1つだけになります。
鳴らす場所に1行ずつ足そう


SoundPlayerができたら、あとは鳴らしたい場所にSoundPlayer.Play(…)を1行ずつ足すだけです。
選ぶ・決める・取り消す
ボタンの音は、#4でボタンに付けたMenuCursorに入れます。コマンド・まほう・どうぐ・ロビー・タイトルのボタンは、どれもMenuCursorが付いたプレハブなので、ここに足すだけで全部のボタンが鳴ります。
まず、決定キーとクリックを受け取れるように、2つのインターフェースを足します(9-7 UIをドラッグ&ドロップしよう:マウスのイベントを受け取ると同じ、イベントのインターフェースです)。
public class MenuCursor : MonoBehaviour, ISelectHandler, IDeselectHandler, IPointerEnterHandler, ISubmitHandler, IPointerClickHandler
OnSelectに、選ぶ音を足します。
public void OnSelect(BaseEventData eventData)
{
cursor.SetActive(true);
// 十字キー・矢印キーで動かしたときだけ鳴らす(メニューを開いたときに、スクリプトで選んだときは鳴らさない)
if (eventData is AxisEventData)
{
SoundPlayer.Play(Sound.Cursor);
}
}
OnPointerEnterを、次のように書きかえて、残りのメソッドを足します。
public void OnPointerEnter(PointerEventData eventData)
{
if (selectable.IsInteractable() && EventSystem.current.currentSelectedGameObject != gameObject)
{
SoundPlayer.Play(Sound.Cursor);
EventSystem.current.SetSelectedGameObject(gameObject);
}
}
// 決定キー(Enter・ゲームパッドの下のボタン)で押したとき
public void OnSubmit(BaseEventData eventData)
{
PlayDecide();
}
// クリックで押したとき
public void OnPointerClick(PointerEventData eventData)
{
PlayDecide();
}
// 押せるボタンなら、決める音を鳴らす(灰色のボタンは鳴らさない)
private void PlayDecide()
{
if (selectable.IsInteractable())
{
SoundPlayer.Play(Sound.Decide);
}
}
- メニューを開いたときは、スクリプトで
SetSelectedGameObjectを呼んで、最初のボタンを選んでいます。そのたびに選ぶ音が鳴ると、うるさく感じます。十字キーや矢印キーで動かしたときは、OnSelectにAxisEventData(動かした向きが入ったもの)が来るので、そのときだけ鳴らします - マウスを乗せたときは、もう選んでいるボタンなら鳴らしません
取り消す音は、CommandMenuのUpdateの、キャンセルのところに足します。
if (inputActions.UI.Cancel.WasPressedThisFrame())
{
SoundPlayer.Play(Sound.Cancel);
Choose(-1);
ねらう相手を選ぶTargetSelector(#6)は、ボタンではないので、それぞれの場所に足します。Updateの決定とキャンセルに足します。
if (inputActions.UI.Submit.WasPressedThisFrame())
{
SoundPlayer.Play(Sound.Decide);
targetSource.TrySetResult(targets[index]);
}
else if (inputActions.UI.Cancel.WasPressedThisFrame())
{
SoundPlayer.Play(Sound.Cancel);
targetSource.TrySetResult(null);
}
ChangeTargetの最初に、選ぶ音を足します。
SoundPlayer.Play(Sound.Cursor);
targets[index].SetHighlight(false);
index = (newIndex + targets.Length) % targets.Length;
マウスで敵を指したとき(PointAt)は、今ねらっている敵なら鳴らさないようにします。クリックで決めたとき(Choose)にも、決める音を足します。
if (found >= 0 && found != index) // 今ねらっている敵なら、そのまま(音を鳴らさない)
{
ChangeTarget(found);
if (isWaiting && System.Array.IndexOf(targets, target) >= 0)
{
SoundPlayer.Play(Sound.Decide);
targetSource.TrySetResult(target);
当たる・受ける・まほう・回復・倒れる


当たったときの演出は、#8でBattleEffectsにまとめました。ここに足せば、たたかう・わざ・まほう・敵の攻撃の、どれでも鳴ります。
BattleEffectsのPlayHitAllAsyncで、ヒットストップの前に、当たる音を足します。
// 仲間が1人でも受けたら、受ける音。敵だけなら、当たる音
SoundPlayer.Play(HitsParty(targets) ? Sound.Damage : Sound.Hit);
Awaitable shake = ShakeAsync(maxDamage);
await HitStopAsync();
PlayHitAllAsyncの下に、仲間が入っているかを調べるメソッドを足します。
// targets に仲間がいるか
private static bool HitsParty(UnitView[] targets)
{
foreach (UnitView target in targets)
{
if (!target.Unit.IsEnemy)
{
return true;
}
}
return false;
}
PlayHealAsyncとPlayMpHealAsyncの、ShowTextの上に、回復の音を足します。
SoundPlayer.Play(Sound.Heal);
UnitViewでは、倒れる・まほうを使うポーズの最初に足します。DisappearAsync・PlayOrbAsync・PlayCastAsyncの、それぞれの{のすぐ下です。
public async Awaitable DisappearAsync()
{
SoundPlayer.Play(Sound.Defeat);
public async Awaitable PlayOrbAsync()
{
SoundPlayer.Play(Sound.Magic);
public async Awaitable PlayCastAsync()
{
SoundPlayer.Play(Sound.Magic);
- 敵が使う まほう・ためる も、同じポーズのメソッドを通るので、鳴ります(ポーズの絵が無い敵は、ふくらむだけ)
- 全体の攻撃で何体も同時に倒れても、SoundPlayerが同じフレームの音を1回にまとめるので、大きくなりすぎません
レベルアップと、勝ち・ぜんめつ


BattleManagerに足します。#12で、ShowLevelUpAsyncに「ここで、レベルアップの効果音を鳴らす」と印を付けておきました。このコメントを、1行に置きかえます。
SoundPlayer.Play(Sound.LevelUp);
ShowVictoryAsyncの最初に、BGMを止めて、勝ちの音を足します。
private async Awaitable<bool> ShowVictoryAsync()
{
SoundPlayer.StopBgm();
SoundPlayer.Play(Sound.Victory);
RunAsyncのcase BattlePhase.Defeat:のすぐ下に、ぜんめつの音を足します。
case BattlePhase.Defeat:
SoundPlayer.StopBgm();
SoundPlayer.Play(Sound.Lose);
BGMを止めておくと、ファンファーレがよく聞こえます。次のバトルに進むと、PlayBgmでバトルの曲がもう一度始まります(止まっているので、最初から鳴らし直します)。
BGMを切りかえよう


それぞれのシーンの始まりで、PlayBgmを呼びます。
TitleMenuのStartの最初に足します。
SoundPlayer.PlayBgm(Bgm.Title);
LobbyMenuのStartの最初に足します。
SoundPlayer.PlayBgm(Bgm.Lobby);
BattleManagerのStartBattleAsyncでは、ボスがいるかで曲を変えます。ボスのときは、登場の演出(#23)の間だけ静かにして、現れてから、ボスの曲を始めます。
if (boss != null)
{
messageText.text = ""; // 演出の間は、メッセージを空にしておく
SoundPlayer.StopBgm(); // 登場の演出の間は、静かにする
await bossEntrance.PlayAsync(boss.View);
SoundPlayer.PlayBgm(Bgm.Boss);
await messageWindow.ShowAsync($"{boss.Name}が めを さました!");
return;
}
SoundPlayer.PlayBgm(Bgm.Battle);
await messageWindow.ShowAsync($"{lineup.EnemyUnits[0].Name}たちが あらわれた!");
- 地下の1回目のバトルで始まったバトルの曲は、2回目・3回目では
PlayBgmが「同じ曲が鳴っている」と判断するので、そのまま続きます - 勝ってBGMを止めたあとは、次のバトルで、また最初から鳴ります



ボスが出てくるまで、シーンとするのがいいわね!
動かして確認!


- Titleシーンから再生する。タイトルの曲が鳴る
- ボタンを十字キー・矢印キーで動かすと「ピッ」、決めると「ピロッ」、Escで取り消すと「ポッ」と鳴る
- ロビーに入るとロビーの曲、地下へ行くとバトルの曲になる。2回目のバトルでは、曲が最初に戻らずに続く
- 当たると「ザシュッ」、仲間が受けると低い「ドスッ」。何度か当てると、少しずつ高さが変わっている
- 全体のまほう(いなずま など)で何体も倒しても、音が大きくなりすぎない
- 勝つと、BGMが止まってファンファーレ。レベルが上がると、上がっていく音
- 地下3階の4回目では、ボスが現れるまで静かで、現れてからボスの曲になる
#26の動作確認
- ボタンを選ぶ・決める・取り消すで、音が鳴る
- 当たる・受ける・まほう・回復・倒れる・レベルアップ・勝ち・ぜんめつで、音が鳴る
- タイトル・ロビー・バトル・ボス戦で、BGMが切りかわる
- シーンを切りかえても、SoundPlayerは1つだけ
- 続けてバトルをしても、バトルの曲が最初に戻らない
あとがき
今回は、効果音とBGMを付けました。SoundPlayerを1つ作っておけば、あとは鳴らしたい場所に1行ずつ足すだけです。音が付くと、同じ攻撃でも、当たった手応えがぐっと強くなりますね。次回は、いよいよ最終回です。ボスを倒したあとのエンディングを作って、Windowsで遊べるようにビルドしたいと思います!



ピロッて鳴るの、つい何回も押したくなっちゃいます・・・!



音が付いたら、ボスがもっとこわくなったわね!
コメント
コメント一覧 (1件)
[…] 前回:#26 効果音とBGM […]