【Cocos Creator 3.8】DuckAudio の実装:アタッチするだけで「セリフ再生中だけBGMを自動で小さくする」汎用スクリプト
このコンポーネントは、重要なセリフやナレーションが再生されている間だけ、BGM の音量を自動で下げる(ダッキング)ための汎用スクリプトです。
BGM 用の AudioSource ノード、セリフ用の AudioSource ノードそれぞれにアタッチし、インスペクタで対象を指定するだけで動作します。
ゲーム側に特別なマネージャークラスを用意する必要はなく、このスクリプト単体で完結するように設計します。
コンポーネントの設計方針
実現したい機能
- セリフ・ナレーション用 AudioSource が再生中の間だけ、指定した BGM AudioSource の音量を下げる。
- セリフの再生開始時に、BGM の音量をなめらかにフェードして下げる。
- セリフの再生終了後、BGM の音量を元の値までなめらかにフェードして戻す。
- 複数のセリフ AudioSource を監視して、1つでも再生中ならダッキング維持。
- 外部の GameManager やシングルトンに依存せず、すべてインスペクタで設定可能にする。
設置パターン
このコンポーネントは、「BGM を持っているノード」または「管理用の空ノード」にアタッチして使います。
- BGM AudioSource … ダッキング対象の音量を下げる側。
- Voice AudioSource 群 … セリフ・ナレーション。どれかが再生中ならダッキング。
コンポーネント内部では、以下のように動作します。
onLoadで BGM AudioSource の参照を取得し、初期音量を記録。- 毎フレーム
updateで、監視しているセリフ AudioSource が再生中かチェック。 - 再生中なら「目標音量 = ダッキング後の音量」、再生していなければ「目標音量 = 元の音量」。
- 現在の音量を、フェード時間に応じて目標音量に近づける(線形補間)。
インスペクタで設定可能なプロパティ
以下のプロパティを用意します。
- BGM AudioSource(
bgmSource)- 型:
AudioSource - 説明: ダッキング対象となる BGM の AudioSource。
- 設定: BGM を再生しているノードの AudioSource をドラッグ&ドロップ。
- 型:
- セリフ AudioSource リスト(
voiceSources)- 型:
AudioSource[] - 説明: セリフ・ナレーション用 AudioSource 群。1つでも再生中ならダッキング。
- 設定: セリフ用ノードの AudioSource を配列に登録。
- 型:
- BGM 元音量(
bgmBaseVolume)- 型:
number(0.0〜1.0) - 説明: BGM の通常時の音量。
※起動時にbgmSource.volumeに上書きされる。 - 用途: インスペクタ上で BGM の基本音量を管理したい場合に使用。
- 型:
- ダッキング時音量(
bgmDuckedVolume)- 型:
number(0.0〜1.0) - 説明: セリフ再生中に BGM を下げる音量。
例: 0.3 なら通常の 30% まで下げる。
- 型:
- フェード時間(ダウン)(
fadeOutTime)- 型:
number(秒) - 説明: セリフ再生開始時に、BGM を元音量からダッキング音量まで下げる時間。
- 例: 0.3〜0.5 秒程度が自然。
- 型:
- フェード時間(アップ)(
fadeInTime)- 型:
number(秒) - 説明: セリフ再生終了後に、BGM をダッキング音量から元音量まで戻す時間。
- 例: 0.3〜1.0 秒程度。
- 型:
- 常時監視を有効化(
enableUpdate)- 型:
boolean - 説明:
trueの場合updateで常に監視。
falseの場合は外部からsetVoicePlaying(true/false)を呼ぶ用途だが、本記事では基本的に true のまま使用。
- 型:
- デバッグログ(
debugLog)- 型:
boolean - 説明: 状態遷移(ダッキング開始・終了など)を Console にログ出力するかどうか。
- 型:
TypeScriptコードの実装
import { _decorator, Component, AudioSource, log, warn, clamp01 } from 'cc';
const { ccclass, property } = _decorator;
/**
* DuckAudio
* セリフ / ナレーション再生中だけ BGM の音量を自動で下げるコンポーネント。
* - 外部のマネージャーには依存せず、このスクリプト単体で完結します。
* - BGM 用 AudioSource と、監視対象のセリフ AudioSource 群をインスペクタで指定してください。
*/
@ccclass('DuckAudio')
export class DuckAudio extends Component {
@property({
type: AudioSource,
tooltip: 'ダッキング対象となる BGM の AudioSource。\nここで指定した AudioSource の volume を自動で制御します。',
})
public bgmSource: AudioSource | null = null;
@property({
type: [AudioSource],
tooltip: 'セリフ / ナレーション用の AudioSource リスト。\n1つでも再生中であれば BGM をダッキングします。',
})
public voiceSources: AudioSource[] = [];
@property({
tooltip: 'BGM の通常時の音量(0.0〜1.0)。\n起動時に bgmSource.volume に設定されます。',
min: 0,
max: 1,
slide: true,
})
public bgmBaseVolume: number = 1.0;
@property({
tooltip: 'ダッキング時の BGM 音量(0.0〜1.0)。\nセリフ再生中はこの音量まで下げます。',
min: 0,
max: 1,
slide: true,
})
public bgmDuckedVolume: number = 0.3;
@property({
tooltip: 'BGM を通常音量からダッキング音量まで下げるフェード時間(秒)。',
min: 0,
step: 0.01,
})
public fadeOutTime: number = 0.3;
@property({
tooltip: 'BGM をダッキング音量から通常音量まで戻すフェード時間(秒)。',
min: 0,
step: 0.01,
})
public fadeInTime: number = 0.5;
@property({
tooltip: 'true の場合、update() 内で常時 AudioSource の再生状態を監視します。',
})
public enableUpdate: boolean = true;
@property({
tooltip: '状態遷移時にデバッグログを出力します。',
})
public debugLog: boolean = false;
// 内部状態
private _currentVolume: number = 1.0;
private _targetVolume: number = 1.0;
private _isVoicePlaying: boolean = false;
private _bgmInitialVolumeSet: boolean = false;
onLoad() {
// BGM AudioSource が設定されているかチェック
if (!this.bgmSource) {
warn('[DuckAudio] bgmSource が設定されていません。このコンポーネントは動作しません。');
return;
}
// BGM 初期音量を設定
this.bgmBaseVolume = clamp01(this.bgmBaseVolume);
this.bgmDuckedVolume = clamp01(this.bgmDuckedVolume);
this._currentVolume = this.bgmBaseVolume;
this._targetVolume = this.bgmBaseVolume;
this.bgmSource.volume = this.bgmBaseVolume;
this._bgmInitialVolumeSet = true;
if (this.debugLog) {
log('[DuckAudio] onLoad: BGM base volume =', this.bgmBaseVolume);
}
}
start() {
// voiceSources に null が混ざっていないか軽くチェック
this.voiceSources = this.voiceSources.filter((v) => {
if (!v) {
warn('[DuckAudio] voiceSources に null の要素があります。インスペクタ設定を確認してください。');
return false;
}
return true;
});
}
update(deltaTime: number) {
if (!this.enableUpdate) {
// 自動監視を使わない場合は、外部から setVoicePlaying() を呼ぶこともできます。
this._updateVolume(deltaTime);
return;
}
if (!this.bgmSource || !this._bgmInitialVolumeSet) {
return;
}
// 1. セリフ AudioSource 群の再生状態をチェック
const anyVoicePlaying = this._checkAnyVoicePlaying();
// 状態が変わったときだけログを出したり、ターゲット音量を変えたりする
if (anyVoicePlaying !== this._isVoicePlaying) {
this._isVoicePlaying = anyVoicePlaying;
if (this._isVoicePlaying) {
// セリフ再生開始 → BGM をダッキング音量へ
this._targetVolume = this.bgmDuckedVolume;
if (this.debugLog) {
log('[DuckAudio] Voice started. Ducking BGM to', this.bgmDuckedVolume);
}
} else {
// セリフ再生終了 → BGM を通常音量へ
this._targetVolume = this.bgmBaseVolume;
if (this.debugLog) {
log('[DuckAudio] Voice ended. Restoring BGM to', this.bgmBaseVolume);
}
}
}
// 2. 現在音量をターゲット音量に近づける(フェード処理)
this._updateVolume(deltaTime);
}
/**
* 監視対象の AudioSource 群のうち、1つでも再生中なら true を返す。
*/
private _checkAnyVoicePlaying(): boolean {
for (let i = 0; i < this.voiceSources.length; i++) {
const src = this.voiceSources[i];
if (!src) {
continue;
}
// isPlaying は Cocos Creator 3.8 の AudioSource に存在するプロパティ
if (src.playing) {
return true;
}
}
return false;
}
/**
* 現在音量をターゲット音量に向けてフェードさせる。
*/
private _updateVolume(deltaTime: number) {
if (!this.bgmSource) {
return;
}
// 目標が現在とほぼ同じなら何もしない
const diff = this._targetVolume - this._currentVolume;
if (Math.abs(diff) < 0.0001) {
this._currentVolume = this._targetVolume;
this.bgmSource.volume = this._currentVolume;
return;
}
// どちらの方向に動かすかで利用するフェード時間を変える
const goingDown = diff < 0; // 音量を下げる方向
const fadeTime = goingDown ? this.fadeOutTime : this.fadeInTime;
// フェード時間が 0 の場合は即座に目標値にする
if (fadeTime <= 0) {
this._currentVolume = this._targetVolume;
this.bgmSource.volume = this._currentVolume;
return;
}
// 線形補間:1秒で (target - current) だけ変化させるイメージ
const t = deltaTime / fadeTime;
// clamp01 しておくと、t が大きすぎる場合も補正される
const clampedT = clamp01(t);
this._currentVolume = this._currentVolume + diff * clampedT;
this._currentVolume = clamp01(this._currentVolume);
this.bgmSource.volume = this._currentVolume;
}
/**
* 自動監視を無効にしている場合など、外部からセリフ再生状態を通知したいときに使用できます。
* 本記事の基本的な使い方では必須ではありません。
*/
public setVoicePlaying(isPlaying: boolean) {
if (!this.bgmSource) {
warn('[DuckAudio] bgmSource が設定されていないため setVoicePlaying() は効果がありません。');
return;
}
if (this._isVoicePlaying === isPlaying) {
return;
}
this._isVoicePlaying = isPlaying;
this._targetVolume = isPlaying ? this.bgmDuckedVolume : this.bgmBaseVolume;
if (this.debugLog) {
log('[DuckAudio] setVoicePlaying:', isPlaying, 'targetVolume =', this._targetVolume);
}
}
}
コードのポイント解説
onLoadbgmSourceが設定されていなければwarnを出し、以降の処理は何もしない(防御的実装)。bgmBaseVolumeとbgmDuckedVolumeをclamp01で 0〜1 に収める。- BGM の現在音量・目標音量を
bgmBaseVolumeに初期化し、実際の AudioSource.volume にも反映。
startvoiceSourcesに紛れ込んだnullをフィルタリングし、警告を出す。
updateenableUpdateがtrueの場合、毎フレーム_checkAnyVoicePlaying()で監視対象 AudioSource のplaying状態を確認。- 状態が変化したタイミングで
_targetVolumeをbgmDuckedVolumeかbgmBaseVolumeに切り替える。 _updateVolume()で、現在音量を目標音量に向かってフェードさせる。
_updateVolume- 音量差分
diffがごく小さい場合はスナップして終了(不要な計算を抑制)。 - 音量を下げる方向(ダッキング)では
fadeOutTime、戻す方向ではfadeInTimeを使用。 - フェード時間が 0 の場合は即時に目標音量へジャンプ。
- 線形補間で
_currentVolumeを更新し、AudioSource.volume に反映。
- 音量差分
setVoicePlaying- 自動監視を行わない構成で使いたい場合のための補助 API。
本記事の基本的な使い方ではenableUpdate = trueのままにしておき、呼び出さなくてもよい。
- 自動監視を行わない構成で使いたい場合のための補助 API。
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を
DuckAudio.tsに変更します。 - 作成された
DuckAudio.tsをダブルクリックして開き、
中身をすべて削除して、前述のコードをそのまま貼り付けて保存します。
2. テスト用シーンの準備
ここでは、簡単なテストシーンを例に説明します。
- BGM 用ノードの作成
- Hierarchy パネルで右クリック → Create → Empty Node を選択し、
BGMNodeという名前にします。 BGMNodeを選択し、Inspector の Add Component ボタンをクリック → Audio → AudioSource を追加します。- AudioSource コンポーネントの Clip に BGM 用の AudioClip(ループ音楽など)を設定します。
- 必要に応じて Loop を ON にします。
- Play On Awake を ON にしておくと、シーン開始時に自動再生されます。
- Hierarchy パネルで右クリック → Create → Empty Node を選択し、
- セリフ用ノードの作成
- Hierarchy パネルで右クリック → Create → Empty Node を選択し、
VoiceNode1という名前にします。 VoiceNode1を選択し、Inspector の Add Component → Audio → AudioSource を追加します。- AudioSource の Clip に、セリフ・ナレーション用の AudioClip を設定します。
- Loop は OFF のままで構いません。
- Play On Awake はテスト方法に応じて ON/OFF を選択します。
- 簡単な確認だけなら ON(シーン開始と同時に再生)。
- ボタンなどから再生したい場合は OFF にしておき、後で UI から再生します。
- 必要であれば、同様の手順で
VoiceNode2,VoiceNode3など、複数のセリフノードを用意しても構いません。
- Hierarchy パネルで右クリック → Create → Empty Node を選択し、
3. DuckAudio コンポーネントのアタッチ
ここでは、BGMNode に DuckAudio をアタッチする例で説明します。
- Hierarchy で
BGMNodeを選択します。 - Inspector の Add Component ボタンをクリックします。
- Custom → DuckAudio を選択して追加します。
- DuckAudio コンポーネントのプロパティを以下のように設定します。
- Bgm Source:
- 右側の丸アイコンをクリックし、
BGMNodeの AudioSource を選択するか、BGMNodeをドラッグ&ドロップします。
- 右側の丸アイコンをクリックし、
- Voice Sources:
- 配列の
+ボタンを押して要素を追加し、VoiceNode1の AudioSource をドラッグ&ドロップします。 - 複数のセリフノードがある場合は、要素を増やしてそれぞれの AudioSource を設定します。
- 配列の
- Bgm Base Volume:
- 通常時の BGM 音量。まずは 1.0 にしておきます。
- Bgm Ducked Volume:
- セリフ再生中の BGM 音量。例として 0.3 に設定します。
- Fade Out Time:
- BGM を下げる時間。例として 0.3 秒。
- Fade In Time:
- BGM を戻す時間。例として 0.5〜1.0 秒。
- Enable Update:
- ON のままにしておきます。これで自動監視が有効になります。
- Debug Log:
- 動作を確認したい場合は ON にすると、Console に「Voice started」「Voice ended」などのログが出力されます。
- Bgm Source:
4. 再生して動作確認
簡単な確認方法を 2 パターン紹介します。
パターンA:セリフも BGM も自動再生
BGMNodeの AudioSource の Play On Awake を ON。VoiceNode1の AudioSource の Play On Awake も ON。- エディタ右上の Play ボタンを押してゲームを実行します。
- シーン開始時に BGM が鳴り、同時にセリフも再生されます。
- セリフが再生されている間は、BGM の音量が DuckAudio によって
bgmDuckedVolumeまで下がっているはずです。 - セリフが終わると、指定した
fadeInTimeに従って BGM が元の音量に戻ります。
- セリフが再生されている間は、BGM の音量が DuckAudio によって
パターンB:ボタンからセリフを再生して確認
より実運用に近い形でテストしたい場合は、UI ボタンからセリフを再生します。
- Hierarchy で右クリック → Create → UI → Canvas を作成します。
- Canvas 配下に Button を作成し、「Play Voice」などのラベルを設定します。
- Button の Click Events に、セリフ再生用の簡単なスクリプトを紐づけます。
- 例として
PlayVoice.tsを作成し、VoiceNode1の AudioSource を再生するだけのスクリプトを書く、など。
- 例として
- ゲーム実行中にボタンを押すとセリフが再生され、その間だけ BGM が自動的に小さくなり、終了後に戻ることを確認します。
※この DuckAudio 自体は他のカスタムスクリプトに依存していないため、
ボタンから再生するロジックは完全に任意で、ゲームの実装スタイルに合わせて自由に作成できます。
まとめ
本記事では、Cocos Creator 3.8 / TypeScript で、
- セリフやナレーションが流れている間だけ BGM の音量を自動で下げる「DuckAudio」コンポーネント
を実装しました。
特徴を整理すると:
- 外部の GameManager やシングルトンに一切依存せず、このスクリプト単体で完結。
- BGM 用 AudioSource と、セリフ用 AudioSource 群をインスペクタで設定するだけで使える。
- フェード時間と音量を調整できるため、ゲームの雰囲気に合わせて自然なダッキングを実現できる。
- 複数のセリフ AudioSource をまとめて監視し、1つでも再生中ならダッキングを維持。
このコンポーネントをプロジェクトの BGM ノードに常備しておけば、
シーンごとに複雑な音量制御ロジックを書く必要がなくなり、セリフを追加するだけで自動的に BGM が聞きやすく調整される環境を構築できます。
他にも、効果音の種類ごとに別インスタンスの DuckAudio を用意し、
「UI 効果音中だけ BGM を少し下げる」「ボス演出中は BGM を大きく下げる」など、用途別のダッキングレイヤーを作ることも可能です。
まずはこの記事の通りに実装・接続して動かし、
自分のゲームに合わせて音量やフェード時間を微調整してみてください。




