ターン制RPGの作り方#26|効果音とBGM:ボタン・攻撃・まほうに音を付けよう

ロビーで「地下へ」から「ちか1かい」を選ぶと画面が暗くなり、地下1階のバトルが始まる。「こぼれたインクたちが あらわれた!」のあと、スライムのコマンドを選ぶ
ロビーの曲から、バトルの曲へ

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

目次

今回作るもの

ProjectウインドのTurnBasedRpg/Audioフォルダ。bgm_battle・bgm_boss・bgm_lobby・bgm_title と、se_cancel・se_cursor・se_damage・se_decide・se_defeat・se_heal・se_hit・se_levelup・se_lose・se_magic・se_victory が並んでいる
効果音11個とBGM4曲
  • どこからでもSoundPlayer.Play(Sound.Hit)の1行で、効果音を鳴らせるようにする
  • 選ぶ・決める・取り消す、当たる・受ける、まほう・回復、倒れる、レベルアップ、勝ち・ぜんめつに、音を付ける
  • タイトル・ロビー・バトル・ボス戦で、BGMを切りかえる。シーンを切りかえても、BGMがとぎれないようにする
スクリプト変えること
SoundPlayer(新しく作る)効果音とBGMを鳴らす
MenuCursorボタンを選ぶ・押すときの音
CommandMenu・TargetSelector取り消す・相手を選ぶときの音
BattleEffects・UnitView当たる・受ける・回復・まほう・倒れるときの音
BattleManagerレベルアップ・勝ち・ぜんめつの音、バトルとボス戦のBGM
LobbyMenu・TitleMenuロビーとタイトルのBGM

音の鳴らし方(AudioSourceとPlayOneShot)は、12-1 音を鳴らそう:効果音とBGMで紹介しています。

スライム

やっと、音が出るんですね!

音を用意しよう

11個の効果音の波形。se_cursor(選ぶ)・se_decide(決める)は短く、se_hit(敵に当たる)・se_damage(仲間が受ける)は始めが大きくすぐ消える。se_levelup(レベルアップ)とse_victory(勝ち)は、短い音が並んだあとに長い音
作った効果音の形

効果音と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_battleのImport Settings。Load TypeがStreaming、Compression FormatがVorbis、Qualityが100
BGMのLoad TypeをStreamingに

BGMは、効果音よりずっと長いファイルです。最初に全部を読みこむと、メモリをたくさん使います。

  • Audioフォルダで、bgm_title・bgm_lobby・bgm_battle・bgm_boss の4つを選ぶ(Ctrlを押しながらクリック)
  • Inspectorの Load Type を Streaming にして、Apply を押す

Streamingにすると、鳴らしながら少しずつ読みこみます。効果音は短くて、すぐ鳴らしたいので、そのまま(Decompress On Load)にします。

SoundPlayerを作ろう

SoundPlayerプレハブのInspector。Audio Sourceが2つ(たたんだ状態)、Sound Player (Script)のSe SourceとBgm SourceにSoundPlayer (Audio Source)、Soundsが11個(Element 0〜2を開いて、Cursor・se_cursor・0.6、Decide・se_decide・0.7、Cancel・se_cancel・0.7)
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つのシーンに置く

BattleシーンのHierarchy。Main Camera・Global Light 2D・Canvas・EventSystem・Battle・TargetSelector・Effectsの下に、プレハブのSoundPlayer
どのシーンにも、同じSoundPlayerを置く

どのシーンから再生しても音が鳴るように、プレハブにして、Title・Lobby・Battleの3つのシーンに置きます。

  1. Titleシーンを開き、Hierarchyで右クリック>Create Empty、名前を SoundPlayer にする
  2. Add Component で Audio Source を2つ付ける。どちらも Play On Awake のチェックを外す。2つ目は Loop にチェックを入れて、Volume を 0.5 にする(BGM用)
  3. Add Component で SoundPlayer を付ける。Se Source に1つ目、Bgm Source に2つ目のAudio Sourceをドラッグする。同じオブジェクトに2つあるので、Inspectorの上のほうのAudio Sourceを、Se Sourceの欄にドラッグします
  4. 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のまま
  5. Bgms を 4 にして、Title・Lobby・Battle・Boss に、bgm_title・bgm_lobby・bgm_battle・bgm_boss を入れる
  6. SoundPlayerを、ProjectのPrefabsフォルダにドラッグしてプレハブにする
  7. Lobbyシーン・Battleシーンを開き、PrefabsフォルダのSoundPlayerを、Hierarchyにドラッグして置く

Lobbyシーンから再生するとLobbyのSoundPlayerが、Titleから再生するとTitleのSoundPlayerが残ります。どのシーンからでも確かめられて、遊ぶときには、いつも1つだけになります。

鳴らす場所に1行ずつ足そう

バトルのコマンドで、▶が「たたかう」から「どうぐ」「にげる」へ下がり、また上がる。「たたかう」を決めると「だれを ねらう?」で、ねらう敵が右へ移り、Escで取り消してコマンドに戻る
選ぶ・決める・取り消すで、音が鳴る

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

当たる・受ける・まほう・回復・倒れる

キョンシーがこぼれたインクに攻撃して「13の ダメージ!」、続けてフェアリーの こうげき で4のダメージ。当たるたびに、数字が飛び出して画面が揺れる
当たった瞬間に、当たる音

当たったときの演出は、#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回にまとめるので、大きくなりすぎません

レベルアップと、勝ち・ぜんめつ

「てきを ぜんぶ たおした!」で3人が喜び、「6ポイントの けいけんちを かくとく!」と出る
勝ったら、BGMを止めてファンファーレ

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で遊べるようにビルドしたいと思います!

スライム

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

フェアリー

音が付いたら、ボスがもっとこわくなったわね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

目次