Unityを触り始めた頃って、つい「音の処理は全部 AudioManager の Update に書いちゃえ!」となりがちですよね。
プレイヤーが洞窟に入ったらリバーブをかけて、ホールに入ったら別のリバーブをかけて……と条件が増えるたびに if 文が増え、Update() が音の分岐だらけの God クラスになっていきます。
このやり方だと、
- シーンごとに音の仕様が変わるたびに AudioManager を編集しないといけない
- どのエリアでどんなリバーブがかかるのかがコードから直感的に分からない
- レベルデザイナーがシーン上で調整しづらい
といった問題が出てきます。
そこでこの記事では、「エリアごとにリバーブ設定を持たせる」ための小さなコンポーネント
ReverbZone(残響エリア) を作って、シーン上のコライダーにポンと付けるだけで、
プレイヤーが入ったときに AudioBus にリバーブを足す仕組みを作っていきます。
【Unity】エリアごとに残響を切り替える!「ReverbZone」コンポーネント
ここでは以下のような前提で実装します。
- 「ゲーム全体の音量・ミックス」を管理する AudioBus コンポーネントを 1 つ用意する
- AudioBus が「今かかっているリバーブ設定」を持ち、ReverbZone がそこに「加算/上書き」する
- プレイヤーがエリアに入る/出るタイミングでリバーブを適用・解除する
AudioBus もこの記事内で完結するように、最小限の実装例を一緒に載せます。
Unity6 の AudioReverbFilter を利用しつつ、「バスっぽく」扱えるようにラップしておきます。
フルコード:AudioBus と ReverbZone
using UnityEngine;
/// <summary>
/// シーン内の共通オーディオバス。
/// リバーブなどの「環境エフェクト」を一元管理します。
/// カメラやAudioListenerがあるオブジェクトに付けておくと分かりやすいです。
/// </summary>
[RequireComponent(typeof(AudioListener))]
[RequireComponent(typeof(AudioReverbFilter))]
public class AudioBus : MonoBehaviour
{
// 現在のリバーブ設定(ReverbZone から上書きされる)
private AudioReverbFilter reverbFilter;
// デフォルト状態を覚えておき、ゾーンから出たときに戻す
private AudioReverbPreset defaultPreset;
private float defaultDryLevel;
private float defaultRoom;
private float defaultRoomHF;
private float defaultDecayTime;
// 「今どのゾーンから制御されているか」を追跡するための参照
private ReverbZone currentZone;
private void Awake()
{
reverbFilter = GetComponent<AudioReverbFilter>();
// AudioReverbFilter の初期値を保存
defaultPreset = reverbFilter.reverbPreset;
defaultDryLevel = reverbFilter.dryLevel;
defaultRoom = reverbFilter.room;
defaultRoomHF = reverbFilter.roomHF;
defaultDecayTime= reverbFilter.decayTime;
}
/// <summary>
/// ゾーンからリバーブ設定を適用する。
/// 別のゾーンがすでに適用中でも、優先度の高い方を採用する想定。
/// </summary>
public void ApplyReverbFromZone(ReverbZone zone, ReverbSettings settings)
{
// まだゾーンがない or 優先度が高いなら上書き
if (currentZone == null || zone.Priority >= currentZone.Priority)
{
currentZone = zone;
if (settings.usePreset)
{
// プリセットを使うモード
reverbFilter.reverbPreset = settings.preset;
}
else
{
// カスタムパラメータを使うモード
reverbFilter.reverbPreset = AudioReverbPreset.Off; // Offにしてパラメータ直指定
reverbFilter.dryLevel = settings.dryLevel;
reverbFilter.room = settings.room;
reverbFilter.roomHF = settings.roomHF;
reverbFilter.decayTime = settings.decayTime;
}
}
}
/// <summary>
/// ゾーンからの制御を解除する。
/// 解除要求を出したゾーンが「現在のゾーン」と一致する場合のみ、デフォルトへ戻す。
/// </summary>
public void ReleaseReverbFromZone(ReverbZone zone)
{
if (currentZone == zone)
{
currentZone = null;
RestoreDefaultReverb();
}
}
/// <summary>
/// デフォルトのリバーブ設定に戻す。
/// </summary>
private void RestoreDefaultReverb()
{
reverbFilter.reverbPreset = defaultPreset;
reverbFilter.dryLevel = defaultDryLevel;
reverbFilter.room = defaultRoom;
reverbFilter.roomHF = defaultRoomHF;
reverbFilter.decayTime = defaultDecayTime;
}
}
/// <summary>
/// ReverbZone が AudioBus に渡すための設定データ。
/// インスペクタから調整しやすいように Serializable にしています。
/// </summary>
[System.Serializable]
public class ReverbSettings
{
[Header("プリセットを使うかカスタム値を使うか")]
public bool usePreset = true;
[Header("プリセットモード")]
public AudioReverbPreset preset = AudioReverbPreset.Generic;
[Header("カスタムモード: プリセットOff時のみ有効")]
[Range(-10000f, 0f)]
public float dryLevel = 0f;
[Range(-10000f, 0f)]
public float room = 0f;
[Range(-10000f, 0f)]
public float roomHF = 0f;
[Range(0.1f, 20f)]
public float decayTime = 1.5f;
}
/// <summary>
/// プレイヤーなどがこのエリアに入ると、AudioBus にリバーブを適用するコンポーネント。
/// BoxCollider / SphereCollider などの「IsTrigger」をONにしたコライダーと一緒に使います。
/// </summary>
[RequireComponent(typeof(Collider))]
public class ReverbZone : MonoBehaviour
{
[Header("このゾーンの優先度(数値が大きいほど優先)")]
[SerializeField] private int priority = 0;
[Header("このゾーンで適用するリバーブ設定")]
[SerializeField] private ReverbSettings reverbSettings = new ReverbSettings();
[Header("このゾーンを有効にする対象タグ(例: Player)")]
[SerializeField] private string targetTag = "Player";
[Header("シーン内の AudioBus 参照(未指定なら自動検索)")]
[SerializeField] private AudioBus audioBus;
/// <summary>
/// 外部から優先度を参照できるようにプロパティ化。
/// </summary>
public int Priority => priority;
private void Reset()
{
// コライダーをトリガーにしておく(当たり判定ではなく侵入検知用)
var col = GetComponent<Collider>();
col.isTrigger = true;
}
private void Awake()
{
// AudioBus が未指定ならシーンから自動検索
if (audioBus == null)
{
audioBus = FindObjectOfType<AudioBus>();
if (audioBus == null)
{
Debug.LogWarning(
$"[ReverbZone] シーン内に AudioBus が見つかりませんでした。"{name}" のリバーブは適用されません。");
}
}
}
private void OnTriggerEnter(Collider other)
{
// タグが一致するオブジェクトのみ対象(プレイヤーだけにしたい場合など)
if (!string.IsNullOrEmpty(targetTag) && !other.CompareTag(targetTag))
{
return;
}
if (audioBus == null) return;
// AudioBus にこのゾーンのリバーブを適用
audioBus.ApplyReverbFromZone(this, reverbSettings);
}
private void OnTriggerExit(Collider other)
{
if (!string.IsNullOrEmpty(targetTag) && !other.CompareTag(targetTag))
{
return;
}
if (audioBus == null) return;
// ゾーンから出たら、AudioBus に解除を通知
audioBus.ReleaseReverbFromZone(this);
}
}
使い方の手順
-
AudioBus をシーンに配置する
- 空の GameObject を作成し、名前を
AudioBusなどにします。 - このオブジェクトに
AudioListenerが付いていない場合は追加します。
(通常はメインカメラに付いていますが、まとめたい場合はここでもOKです) - 上記コードの
AudioBusコンポーネントをアタッチします。 AudioReverbFilterはRequireComponentにより自動で付きます。- 初期状態でのリバーブを変えたい場合は、
AudioReverbFilterのインスペクタでプリセットを設定しておきましょう。
- 空の GameObject を作成し、名前を
-
プレイヤーのタグを設定する
- プレイヤーの GameObject を選択し、
TagをPlayerにします(別のタグ名でもOK)。 - 後で ReverbZone の
targetTagに同じタグ名を設定します。
- プレイヤーの GameObject を選択し、
-
洞窟・ホールなどのリバーブエリアを作る
例1:洞窟の入り口エリア- 空の GameObject を作成し、名前を
CaveReverbZoneにします。 BoxColliderを追加し、Is Triggerにチェックを入れます。- 上記コードの
ReverbZoneコンポーネントをアタッチします。 targetTagにPlayerを設定します。audioBusフィールドに、手順1で作った AudioBus オブジェクトをドラッグ&ドロップします
(未設定でも自動検索されますが、明示しておくと安全です)。reverbSettingsのusePresetをtrueにして、presetをCaveなどに設定すると、
「洞窟に入ったら残響が強くなる」エリアになります。
例2:大ホール(コンサートホール)のエリア
- 別の GameObject を作成し、名前を
HallReverbZoneにします。 - 大きめの
BoxColliderやSphereColliderを追加し、Is Triggerを ON にします。 ReverbZoneをアタッチし、priorityを1など洞窟より高めに設定します。
(重なったときにどちらを優先するか決めるためです)reverbSettingsでusePresetをtrueにして、presetをConcertHallに設定するか、usePresetをfalseにして、decayTimeを長め、roomを少し下げるなど、
自分好みのホール感を作ってもOKです。
- 空の GameObject を作成し、名前を
-
プレイして確認する
- プレイヤーが
CaveReverbZoneに入ると、AudioBus に洞窟用のリバーブが適用されます。 - そのまま
HallReverbZoneに入ると、優先度の高いホール用リバーブに切り替わります。 - どのゾーンからも出ると、AudioBus は「デフォルトのリバーブ設定」に戻ります。
- プレイヤーが
メリットと応用
ReverbZone コンポーネントを使うことで、音の環境変化を「エリアのコライダー」として扱えるようになります。
これはプレハブ管理やレベルデザインの観点でかなりおいしいポイントです。
- プレハブ化して量産できる
一度いい感じの洞窟リバーブを作ってしまえば、そのReverbZoneをプレハブ化して、
いろんなシーンの洞窟入り口にコピペするだけで同じ音響を再現できます。 - レベルデザイナーが視覚的に調整しやすい
コライダーのサイズや位置をいじるだけで「どこから残響がかかり始めるか」が変えられるので、
スクリプトを触らない人でも音のエリア調整がしやすくなります。 - AudioBus はシンプルなまま維持できる
「どんな種類のリバーブがあるか」「どのエリアでどれを使うか」といった知識を AudioBus に押し込まないことで、
AudioBus はあくまで「リバーブを適用するための窓口」に留まり、God クラス化を防げます。 - 複数ゾーンの優先度制御
洞窟の中に小さな部屋があるようなケースでも、priorityの値でどちらの音響を優先するか制御できます。
応用として、例えば「ゾーンに入ったら BGM のボリュームも変える」「環境音の AudioSource のミックスも変える」といった拡張も考えられます。
以下は、簡単な改造案として「ゾーンに入ったときに特定の環境音のボリュームもフェードさせる」処理の一例です。
// ReverbZone に追加できる簡易改造例:環境音のフェード制御
[SerializeField] private AudioSource ambientSource;
[SerializeField] [Range(0f, 1f)] private float ambientVolumeInZone = 0.5f;
[SerializeField] private float fadeDuration = 1.0f;
private Coroutine ambientFadeCoroutine;
/// <summary>
/// ゾーンに入ったときに環境音のボリュームをフェードさせる例。
/// (OnTriggerEnter から呼び出すイメージ)
/// </summary>
private void FadeAmbientInZone(bool inZone)
{
if (ambientSource == null) return;
if (ambientFadeCoroutine != null)
{
StopCoroutine(ambientFadeCoroutine);
}
ambientFadeCoroutine = StartCoroutine(FadeAmbientCoroutine(
targetVolume: inZone ? ambientVolumeInZone : 1f
));
}
private System.Collections.IEnumerator FadeAmbientCoroutine(float targetVolume)
{
float startVolume = ambientSource.volume;
float time = 0f;
while (time < fadeDuration)
{
time += Time.deltaTime;
float t = Mathf.Clamp01(time / fadeDuration);
ambientSource.volume = Mathf.Lerp(startVolume, targetVolume, t);
yield return null;
}
ambientSource.volume = targetVolume;
}
このように、小さなコンポーネント単位で「エリアに入ったら◯◯する」を積み上げていくと、
巨大な Update() に頼らない、見通しの良いオーディオシステムを作っていけます。
まずは ReverbZone をプレハブ化して、洞窟やホール、狭い通路などにポンポン配置してみてください。
