効果音の同時再生数を制御する

ページ更新日 :
ページ作成日 :

検証環境

Windows
  • Windows 11
Unity エディター
  • 2021.3.3f1
入力システムパッケージ
  • 1.3.0

この Tips の前提設定

この Tips の説明の前提として以下の設定を事前に行っています。

サンプルに付属している素材について

以下のサイト様より効果音を借用しています。

音声ファイルについて

Unity の標準機能で再生できる音声ファイルの形式は以下のようなものがあります。本 Tips で使用するので事前に用意しておいてください。

  • WAV (.wav)
  • OggVorbis (.ogg)
  • MPEG layer 3 (.mp3)

詳しくは Unity 公式のマニュアルを参照してください。

音声の複数同時再生の上限と音量について

前回の Tips では AudioClip で音声データを用意しつつ AudioSource.PlayOneShot で再生することにより音声を複数重ね掛けして再生できるようになりました。 しかし、この再生できる数には上限がないためゲーム中に制限を掛けていないと同時に音が沢山再生されてしまい大音量が流れてしまう場合があります。

ここでは独自にプログラムを作成し、同時に再生できる効果音の数を制御してみたいと思います。

サンプルの作成

今回はボタンをクリックするたびに効果音を再生させるので図のように UI を作成します。 細かい部分は適当でいいです。

プロジェクトに再生する音声ファイルをドロップして追加します。

前回までは標準機能の AudioSource をヒエラルキーに追加していましたが、 今回は独自の制御を入れるのでスクリプトを追加します。 名前は SoundPlayManager としておきます。

スクリプトは以下のように作成します。

using System.Collections.Generic;
using UnityEngine;

/// <summary>音声再生管理クラスです。</summary>
public class SoundPlayManager : MonoBehaviour
{
  /// <summary>1つの種類の音声の再生情報を保持するクラスです。</summary>
  private class PlayInfo
  {
    /// <summary>再生している AudioSource の一覧です。</summary>
    public AudioSource[] AudioSource { get; set; }

    /// <summary>現在の再生インデックスです。</summary>
    public int NowIndex { get; set; }
  }

  /// <summary>再生しようとしている音声データのキューです。</summary>
  private HashSet<AudioClip> Queue = new HashSet<AudioClip>();

  /// <summary>再生している音声を管理している一覧です。</summary>
  private Dictionary<AudioClip, PlayInfo> Sources = new Dictionary<AudioClip, PlayInfo>();

  /// <summary>
  /// 同一音声同時再生最大数。
  /// </summary>
  [SerializeField, Range(1, 32)] private int MaxSimultaneousPlayCount = 2;

  protected void Update()
  {
    foreach (var item in Queue)
    {
      AudioSource source;
      if (Sources.ContainsKey(item) == false)
      {
        // 一度も再生されていない Clip がある場合は PlayInfo を生成します
        var info = new PlayInfo()
        {
          AudioSource = new AudioSource[MaxSimultaneousPlayCount],
        };
        for (int i = 0; i < MaxSimultaneousPlayCount; i++)
        {
          var s = gameObject.AddComponent<AudioSource>();
          s.clip = item;
          info.AudioSource[i] = s;
        }
        Sources.Add(item, info);
        source = info.AudioSource[0];
      }
      else
      {
        // 再生に使用する AudioSource を順番に取得します
        var info = Sources[item];
        info.NowIndex = (info.NowIndex + 1) % MaxSimultaneousPlayCount;
        source = info.AudioSource[info.NowIndex];
      }

      source.Play();
    }
    Queue.Clear();
  }

  /// <summary>
  /// 効果音を再生します。
  /// </summary>
  public void Play(AudioClip clip)
  {
    // 同一フレームで複数再生しないようにすでにキューに入っているか確認します
    if (Queue.Contains(clip) == false)
    {
      Queue.Add(clip);
    }
  }

  public void OnDestroy()
  {
    // 不要になった参照をすべて外します
    foreach (var source in Sources)
    {
      source.Value.AudioSource = null;
    }
    Sources.Clear();
    Queue.Clear();
  }
}

スクリプトが長いので細かい説明は省略しますが、以下のような機能を持たせています。

  • AudioClip を渡して音声を再生できる
  • 同一種類の音声を重ねて再生したとき、一定数を超えて重ねて再生しようとした場合古い再生データが停止する (初期値は2再生まで)
  • 同一フレームで重ねて再生できないようにしている

これにより、同じ音声を連続で再生したときに音量が大きくなってしまうのを防ぐことができます。 機能的には最低限のものしか入れていないので必要な機能があれば追加してください。

スクリプトは EventSystem にアタッチしておきます。

次にボタン用のスクリプトを作成します。

スクリプトは以下のように作成します。

using UnityEngine;

public class ButtonEvent : MonoBehaviour
{
  /// <summary>再生する音声データ。</summary>
  [SerializeField] private AudioClip AudioClip;

  /// <summary>自作した音声再生管理クラス。</summary>
  [SerializeField] private SoundPlayManager SoundPlayManager;

  /// <summary>ボタンをクリックしたとき。</summary>
  public void OnClick()
  {
    SoundPlayManager.Play(AudioClip);
  }
}

再生処理は自作した SoundPlayManager がやってくれるのでボタンイベントでは音声データを渡して再生するだけです。

スクリプトは EventSystem にアタッチしておきます。アタッチ管理などは本 Tips の本題では無いので適当です。

オーディオクリップには再生する音声ファイルをセットしてください。 Sound Play Manager には SoundPlayManager をアタッチしているオブジェクトをセットしてください。 ここでは EventSystem にアタッチしているので EventSystem をセットします。

最後にボタンのクリックイベントに OnClick を割り当てます。

完成したらゲームを実行します。 初期値では同時再生数上限を2に設定しているので、2つ音が鳴っている最中にもう一度ボタンをクリックすると一番最初の音が消えることを確認してみてください。

今回作成したプログラムは音の種類ごとに再生数の上限が働くので、他の効果音を織り交ぜても自然に聞こえるようになっています。 ただ、音の種類によっては上限2では足りない場合もあると思うので、音の種類ごとに上限を設定できるようにしてみても良いかもしれません。 まあそこまで音の重ね掛けの機会があるかどうかはわかりませんが…。