【Cocos Creator】アタッチするだけ!DuckAudio (ダッキング)の実装方法【TypeScript】

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

【Cocos Creator 3.8】DuckAudio の実装:アタッチするだけで「セリフ再生中だけBGMを自動で小さくする」汎用スクリプト

このコンポーネントは、重要なセリフやナレーションが再生されている間だけ、BGM の音量を自動で下げる(ダッキング)ための汎用スクリプトです。
BGM 用の AudioSource ノード、セリフ用の AudioSource ノードそれぞれにアタッチし、インスペクタで対象を指定するだけで動作します。
ゲーム側に特別なマネージャークラスを用意する必要はなく、このスクリプト単体で完結するように設計します。


コンポーネントの設計方針

実現したい機能

  • セリフ・ナレーション用 AudioSource が再生中の間だけ、指定した BGM AudioSource の音量を下げる。
  • セリフの再生開始時に、BGM の音量をなめらかにフェードして下げる。
  • セリフの再生終了後、BGM の音量を元の値までなめらかにフェードして戻す。
  • 複数のセリフ AudioSource を監視して、1つでも再生中ならダッキング維持。
  • 外部の GameManager やシングルトンに依存せず、すべてインスペクタで設定可能にする。

設置パターン

このコンポーネントは、「BGM を持っているノード」または「管理用の空ノード」にアタッチして使います。

  • BGM AudioSource … ダッキング対象の音量を下げる側。
  • Voice AudioSource 群 … セリフ・ナレーション。どれかが再生中ならダッキング。

コンポーネント内部では、以下のように動作します。

  1. onLoad で BGM AudioSource の参照を取得し、初期音量を記録。
  2. 毎フレーム update で、監視しているセリフ AudioSource が再生中かチェック。
  3. 再生中なら「目標音量 = ダッキング後の音量」、再生していなければ「目標音量 = 元の音量」。
  4. 現在の音量を、フェード時間に応じて目標音量に近づける(線形補間)。

インスペクタで設定可能なプロパティ

以下のプロパティを用意します。

  • BGM AudioSourcebgmSource
    • 型: 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);
        }
    }
}

コードのポイント解説

  • onLoad
    • bgmSource が設定されていなければ warn を出し、以降の処理は何もしない(防御的実装)。
    • bgmBaseVolumebgmDuckedVolumeclamp01 で 0〜1 に収める。
    • BGM の現在音量・目標音量を bgmBaseVolume に初期化し、実際の AudioSource.volume にも反映。
  • start
    • voiceSources に紛れ込んだ null をフィルタリングし、警告を出す。
  • update
    • enableUpdatetrue の場合、毎フレーム _checkAnyVoicePlaying() で監視対象 AudioSource の playing 状態を確認。
    • 状態が変化したタイミングで _targetVolumebgmDuckedVolumebgmBaseVolume に切り替える。
    • _updateVolume() で、現在音量を目標音量に向かってフェードさせる。
  • _updateVolume
    • 音量差分 diff がごく小さい場合はスナップして終了(不要な計算を抑制)。
    • 音量を下げる方向(ダッキング)では fadeOutTime、戻す方向では fadeInTime を使用。
    • フェード時間が 0 の場合は即時に目標音量へジャンプ。
    • 線形補間で _currentVolume を更新し、AudioSource.volume に反映。
  • setVoicePlaying
    • 自動監視を行わない構成で使いたい場合のための補助 API。
      本記事の基本的な使い方では enableUpdate = true のままにしておき、呼び出さなくてもよい。

使用手順と動作確認

1. スクリプトファイルの作成

  1. Assets パネルで右クリック → Create → TypeScript を選択します。
  2. ファイル名を DuckAudio.ts に変更します。
  3. 作成された DuckAudio.ts をダブルクリックして開き、
    中身をすべて削除して、前述のコードをそのまま貼り付けて保存します。

2. テスト用シーンの準備

ここでは、簡単なテストシーンを例に説明します。

  1. BGM 用ノードの作成
    1. Hierarchy パネルで右クリック → Create → Empty Node を選択し、BGMNode という名前にします。
    2. BGMNode を選択し、Inspector の Add Component ボタンをクリック → Audio → AudioSource を追加します。
    3. AudioSource コンポーネントの Clip に BGM 用の AudioClip(ループ音楽など)を設定します。
    4. 必要に応じて Loop を ON にします。
    5. Play On Awake を ON にしておくと、シーン開始時に自動再生されます。
  2. セリフ用ノードの作成
    1. Hierarchy パネルで右クリック → Create → Empty Node を選択し、VoiceNode1 という名前にします。
    2. VoiceNode1 を選択し、Inspector の Add ComponentAudio → AudioSource を追加します。
    3. AudioSource の Clip に、セリフ・ナレーション用の AudioClip を設定します。
    4. Loop は OFF のままで構いません。
    5. Play On Awake はテスト方法に応じて ON/OFF を選択します。
      • 簡単な確認だけなら ON(シーン開始と同時に再生)。
      • ボタンなどから再生したい場合は OFF にしておき、後で UI から再生します。
    6. 必要であれば、同様の手順で VoiceNode2, VoiceNode3 など、複数のセリフノードを用意しても構いません。

3. DuckAudio コンポーネントのアタッチ

ここでは、BGMNode に DuckAudio をアタッチする例で説明します。

  1. Hierarchy で BGMNode を選択します。
  2. Inspector の Add Component ボタンをクリックします。
  3. Custom → DuckAudio を選択して追加します。
  4. 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.51.0 秒。
    • Enable Update:
      • ON のままにしておきます。これで自動監視が有効になります。
    • Debug Log:
      • 動作を確認したい場合は ON にすると、Console に「Voice started」「Voice ended」などのログが出力されます。

4. 再生して動作確認

簡単な確認方法を 2 パターン紹介します。

パターンA:セリフも BGM も自動再生

  1. BGMNode の AudioSource の Play On Awake を ON。
  2. VoiceNode1 の AudioSource の Play On Awake も ON。
  3. エディタ右上の Play ボタンを押してゲームを実行します。
  4. シーン開始時に BGM が鳴り、同時にセリフも再生されます。
    • セリフが再生されている間は、BGM の音量が DuckAudio によって bgmDuckedVolume まで下がっているはずです。
    • セリフが終わると、指定した fadeInTime に従って BGM が元の音量に戻ります。

パターンB:ボタンからセリフを再生して確認

より実運用に近い形でテストしたい場合は、UI ボタンからセリフを再生します。

  1. Hierarchy で右クリック → Create → UI → Canvas を作成します。
  2. Canvas 配下に Button を作成し、「Play Voice」などのラベルを設定します。
  3. Button の Click Events に、セリフ再生用の簡単なスクリプトを紐づけます。
    • 例として PlayVoice.ts を作成し、VoiceNode1 の AudioSource を再生するだけのスクリプトを書く、など。
  4. ゲーム実行中にボタンを押すとセリフが再生され、その間だけ BGM が自動的に小さくなり、終了後に戻ることを確認します。

※この DuckAudio 自体は他のカスタムスクリプトに依存していないため、
ボタンから再生するロジックは完全に任意で、ゲームの実装スタイルに合わせて自由に作成できます。


まとめ

本記事では、Cocos Creator 3.8 / TypeScript で、

  • セリフやナレーションが流れている間だけ BGM の音量を自動で下げる「DuckAudio」コンポーネント

を実装しました。

特徴を整理すると:

  • 外部の GameManager やシングルトンに一切依存せず、このスクリプト単体で完結
  • BGM 用 AudioSource と、セリフ用 AudioSource 群をインスペクタで設定するだけで使える。
  • フェード時間と音量を調整できるため、ゲームの雰囲気に合わせて自然なダッキングを実現できる。
  • 複数のセリフ AudioSource をまとめて監視し、1つでも再生中ならダッキングを維持。

このコンポーネントをプロジェクトの BGM ノードに常備しておけば、
シーンごとに複雑な音量制御ロジックを書く必要がなくなり、セリフを追加するだけで自動的に BGM が聞きやすく調整される環境を構築できます。

他にも、効果音の種類ごとに別インスタンスの DuckAudio を用意し、
「UI 効果音中だけ BGM を少し下げる」「ボス演出中は BGM を大きく下げる」など、用途別のダッキングレイヤーを作ることも可能です。

まずはこの記事の通りに実装・接続して動かし、
自分のゲームに合わせて音量やフェード時間を微調整してみてください。

Godot 4ゲーム制作 実践ドリル 100本ノック

新品価格
¥1,250から
(2025/12/13 21:27時点)

Godot4& GDScriptではじめる 2Dゲーム開発レシピ

新品価格
¥590から
(2025/12/13 21:46時点)

Unity 6 C#スクリプト 100本ノック

新品価格
¥1,230から
(2025/12/29 10:28時点)

Cocos Creator100本ノックTypeScriptで書く!

新品価格
¥1,250から
(2025/12/29 10:32時点)

URLをコピーしました!