【Cocos Creator】アタッチするだけ!EarRinging (耳鳴り)の実装方法【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】EarRinging コンポーネントの実装:アタッチするだけで「爆発直後のキーンという耳鳴り状態」を再現する汎用スクリプト

このコンポーネントは、手榴弾などの爆発で大ダメージを受けた直後に「環境音やBGMを一時的にミュートし、キーンという耳鳴り音だけを鳴らす」演出を簡単に実装するためのものです。

任意のノードにアタッチしておき、ゲーム中でメソッドを呼ぶだけで「耳鳴り状態」を開始できます。既存の BGM や SE 管理スクリプトに依存せず、このコンポーネント単体で完結するように設計します。


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

機能要件の整理

  • 爆発などのイベント時に「耳鳴り状態」を開始できる。
  • 耳鳴り状態中は:
    • 指定した AudioSource(BGM や環境音など)を一時的にミュートする。
    • 耳鳴り用の AudioSource から「キーン」という音を再生する。
    • 時間経過とともに耳鳴り音のボリュームをフェードアウトし、同時に元の AudioSource のボリュームをフェードインする。
  • 耳鳴り状態の長さやフェード時間、ボリュームなどをインスペクタから調整できる。
  • 外部の GameManager やシングルトンに依存せず、このスクリプトだけで完結する。

外部依存をなくすためのアプローチ

  • 「耳鳴り音」を鳴らすための AudioSource を、このコンポーネントがアタッチされたノードに追加して利用する。
  • 「ミュート/フェードさせたい音」(BGM や環境音など)の AudioSource は、インスペクタで配列として指定する。
  • 耳鳴り状態の開始は、外部から triggerEarRinging() メソッドを呼び出すだけでよいようにする。
  • 必要な AudioSource が存在しない場合は getComponent でチェックし、エラーログを出して処理をスキップする防御的実装にする。

@property で設定可能なプロパティ設計

インスペクタで調整できるプロパティと役割は次の通りです。

  • earRingingAudioSource: AudioSource
    • 耳鳴り用の音を再生する AudioSource。
    • このコンポーネントと同じノード、または任意のノードの AudioSource を指定。
    • 事前に AudioClip(キーン音)を設定しておく。
  • targetAudioSources: AudioSource[]
    • 耳鳴り中に一時的にボリュームを下げたい(ほぼミュートしたい)AudioSource の配列。
    • BGM、環境音、効果音など自由に指定可能。
  • ringDuration: number
    • 耳鳴り状態の総時間(秒)
    • 例: 3.0 にすると、3秒かけて耳鳴りが収束し、元の音量が戻る。
  • fadeInDuration: number
    • 耳鳴り開始直後に、耳鳴り音を「0 → 最大音量」にフェードインする時間(秒)。
    • 小さくすると、いきなりキーンと鳴る。大きくすると、じわっと耳鳴りが立ち上がる。
  • fadeOutDuration: number
    • 耳鳴り終了に向けて、耳鳴り音を「最大音量 → 0」にフェードアウトする時間(秒)。
    • 通常は ringDuration と同じか、それより短めに設定。
  • earMaxVolume: number
    • 耳鳴り音の最大ボリューム。
    • 0.0〜1.0 の範囲で指定。1.0 が AudioSource の本来の最大音量。
  • targetMutedVolume: number
    • 耳鳴り中に、ターゲット AudioSource をどの程度まで下げるか。
    • 0.0 で完全ミュート。0.1〜0.2 などにすると、うっすら音が残る表現も可能。
  • autoRestoreOnDisable: boolean
    • コンポーネントが無効化(disable)されたときに、音量を強制的に元の状態へ戻すかどうか。
    • シーン遷移やノード破棄時に音量が戻らない事故を防ぐ目的。
  • logDebug: boolean
    • デバッグ用ログを出すかどうか。
    • 挙動確認時のみ true にしておくと便利。

TypeScriptコードの実装


import { _decorator, Component, AudioSource, clamp01, math } from 'cc';
const { ccclass, property } = _decorator;

/**
 * EarRinging
 * 爆発などの直後に、指定した AudioSource 群を一時的にミュートし、
 * 耳鳴り用 AudioSource をフェードイン/アウトさせるコンポーネント。
 *
 * 外部 GameManager などに依存せず、このスクリプト単体で完結する設計。
 */
@ccclass('EarRinging')
export class EarRinging extends Component {

    @property({
        type: AudioSource,
        tooltip: '耳鳴り用の AudioSource。\nここにキーン音の AudioClip を設定しておきます。'
    })
    public earRingingAudioSource: AudioSource | null = null;

    @property({
        type: [AudioSource],
        tooltip: '耳鳴り中にボリュームを下げる対象の AudioSource 配列。\nBGM や環境音などを指定します。'
    })
    public targetAudioSources: AudioSource[] = [];

    @property({
        tooltip: '耳鳴り状態の総時間(秒)。\nこの時間をかけて耳鳴りが収束し、元の音量に戻ります。'
    })
    public ringDuration: number = 3.0;

    @property({
        tooltip: '耳鳴り開始時のフェードイン時間(秒)。\n0 にするといきなり最大音量になります。'
    })
    public fadeInDuration: number = 0.2;

    @property({
        tooltip: '耳鳴り終了に向けたフェードアウト時間(秒)。\n通常は ringDuration と同じか、それより短くします。'
    })
    public fadeOutDuration: number = 2.5;

    @property({
        tooltip: '耳鳴り音の最大ボリューム(0〜1)。'
    })
    public earMaxVolume: number = 1.0;

    @property({
        tooltip: '耳鳴り中にターゲット AudioSource を下げるボリューム(0〜1)。\n0 で完全ミュート。'
    })
    public targetMutedVolume: number = 0.05;

    @property({
        tooltip: 'コンポーネントが無効化されたときに、音量を元に戻すかどうか。'
    })
    public autoRestoreOnDisable: boolean = true;

    @property({
        tooltip: 'デバッグ用ログを出力するかどうか。'
    })
    public logDebug: boolean = false;

    // 内部状態管理用
    private _isRinging: boolean = false;
    private _elapsed: number = 0;

    // 対象 AudioSource の元の音量を記録しておく
    private _originalTargetVolumes: number[] = [];

    // 耳鳴り AudioSource の元の音量
    private _originalEarVolume: number = 1.0;

    onLoad() {
        // 必要なコンポーネントの存在チェック
        if (!this.earRingingAudioSource) {
            // 同じノードに AudioSource があれば自動取得を試みる
            const found = this.getComponent(AudioSource);
            if (found) {
                this.earRingingAudioSource = found;
                if (this.logDebug) {
                    console.warn('[EarRinging] earRingingAudioSource が未設定だったため、同一ノードの AudioSource を自動で利用します。');
                }
            } else {
                console.error('[EarRinging] earRingingAudioSource が設定されておらず、同一ノードにも AudioSource が見つかりません。耳鳴り音を再生できません。');
            }
        }

        if (this.earRingingAudioSource) {
            this._originalEarVolume = this.earRingingAudioSource.volume;
        }

        // 対象 AudioSource の元のボリュームを記録
        this._cacheOriginalTargetVolumes();
    }

    start() {
        // 特に開始時に動作はしない(外部から triggerEarRinging() が呼ばれる想定)
    }

    update(deltaTime: number) {
        if (!this._isRinging) {
            return;
        }

        this._elapsed += deltaTime;

        // 安全のため、負の値や NaN を防ぐ
        const duration = Math.max(this.ringDuration, 0.0001);

        // 0〜1 の正規化時間
        const t = clamp01(this._elapsed / duration);

        // 耳鳴り音のフェードイン/アウト
        this._updateEarVolume(t);

        // ターゲット AudioSource のボリュームを補間
        this._updateTargetVolumes(t);

        // 終了判定
        if (this._elapsed >= duration) {
            this._finishRinging();
        }
    }

    /**
     * 耳鳴り状態を開始する。
     * 外部スクリプトから呼び出して使用する想定。
     */
    public triggerEarRinging(): void {
        if (!this.earRingingAudioSource) {
            console.error('[EarRinging] triggerEarRinging() が呼ばれましたが、earRingingAudioSource が設定されていません。');
            return;
        }

        if (this.logDebug) {
            console.log('[EarRinging] triggerEarRinging() 呼び出し。耳鳴り状態を開始します。');
        }

        // 既に耳鳴り中であればリスタート
        this._isRinging = true;
        this._elapsed = 0;

        // 元の音量を再キャッシュ(途中で変更されている可能性があるため)
        this._cacheOriginalTargetVolumes();
        this._originalEarVolume = this.earRingingAudioSource.volume;

        // 耳鳴り音を再生
        if (!this.earRingingAudioSource.playing) {
            this.earRingingAudioSource.play();
        }

        // 開始時点では耳鳴り音を 0 にしておく(フェードイン用)
        this.earRingingAudioSource.volume = 0;

        // ターゲット AudioSource は開始時点で元の音量から徐々に下げるため、
        // ここでは即時にはミュートせず、update() 内で補間する。
    }

    /**
     * 耳鳴り状態が進行中かどうか。
     */
    public get isRinging(): boolean {
        return this._isRinging;
    }

    /**
     * 元のボリュームをキャッシュ。
     */
    private _cacheOriginalTargetVolumes(): void {
        this._originalTargetVolumes.length = 0;

        if (!this.targetAudioSources) {
            return;
        }

        for (let i = 0; i < this.targetAudioSources.length; i++) {
            const src = this.targetAudioSources[i];
            if (!src) {
                this._originalTargetVolumes.push(1.0);
                if (this.logDebug) {
                    console.warn(`[EarRinging] targetAudioSources[${i}] が null です。`);
                }
                continue;
            }
            this._originalTargetVolumes.push(src.volume);
        }
    }

    /**
     * 正規化時間 t (0〜1) に応じて耳鳴り AudioSource のボリュームを更新。
     */
    private _updateEarVolume(t: number): void {
        if (!this.earRingingAudioSource) {
            return;
        }

        // フェードインフェーズの割合
        const fadeIn = Math.max(this.fadeInDuration, 0);
        const fadeOut = Math.max(this.fadeOutDuration, 0);

        let volume = 0;

        // フェードイン
        if (fadeIn > 0 && this._elapsed <= fadeIn) {
            const ti = clamp01(this._elapsed / fadeIn);
            volume = math.lerp(0, this.earMaxVolume, ti);
        }
        // フェードアウト
        else if (fadeOut > 0 && this._elapsed >= (this.ringDuration - fadeOut)) {
            const remaining = this.ringDuration - this._elapsed;
            const to = clamp01(remaining / fadeOut); // 残り時間割合
            volume = math.lerp(0, this.earMaxVolume, to);
        }
        // 安定フェーズ(最大音量維持)
        else {
            volume = this.earMaxVolume;
        }

        // 0〜1 にクランプ
        volume = clamp01(volume);
        this.earRingingAudioSource.volume = volume;
    }

    /**
     * 正規化時間 t (0〜1) に応じてターゲット AudioSource のボリュームを更新。
     * t=0 で元の音量、t=1 で targetMutedVolume に近づく → その後の時間で元に戻す、というより、
     * 耳鳴り全体の間、基本的には targetMutedVolume 付近を維持し、
     * 終了に向けて元の音量へ戻していくイメージ。
     */
    private _updateTargetVolumes(t: number): void {
        if (!this.targetAudioSources) {
            return;
        }

        const fadeOut = Math.max(this.fadeOutDuration, 0);
        const duration = Math.max(this.ringDuration, 0.0001);

        for (let i = 0; i < this.targetAudioSources.length; i++) {
            const src = this.targetAudioSources[i];
            if (!src) {
                continue;
            }

            const original = this._originalTargetVolumes[i] ?? src.volume;

            let volume = original;

            // 耳鳴り開始時〜中盤までは、元の音量 → targetMutedVolume へ素早く落とす
            const fadeDownTime = Math.min(0.2, duration * 0.2); // 0.2秒 or 全体の20% くらいで落とす
            if (this._elapsed <= fadeDownTime) {
                const td = clamp01(this._elapsed / fadeDownTime);
                volume = math.lerp(original, this.targetMutedVolume, td);
            }
            // 終了に向けて元の音量へ戻す
            else if (fadeOut > 0 && this._elapsed >= (duration - fadeOut)) {
                const passed = this._elapsed - (duration - fadeOut);
                const tr = clamp01(passed / fadeOut);
                volume = math.lerp(this.targetMutedVolume, original, tr);
            }
            // それ以外の時間帯は targetMutedVolume を維持
            else {
                volume = this.targetMutedVolume;
            }

            src.volume = clamp01(volume);
        }
    }

    /**
     * 耳鳴り状態の終了処理。
     */
    private _finishRinging(): void {
        this._isRinging = false;

        // 耳鳴り音を停止し、元の音量に戻す
        if (this.earRingingAudioSource) {
            this.earRingingAudioSource.stop();
            this.earRingingAudioSource.volume = this._originalEarVolume;
        }

        // ターゲット AudioSource の音量を元に戻す
        this._restoreTargetVolumes();

        if (this.logDebug) {
            console.log('[EarRinging] 耳鳴り状態が終了しました。音量を元に戻しました。');
        }
    }

    /**
     * 対象 AudioSource の音量を元に戻す。
     */
    private _restoreTargetVolumes(): void {
        if (!this.targetAudioSources) {
            return;
        }

        for (let i = 0; i < this.targetAudioSources.length; i++) {
            const src = this.targetAudioSources[i];
            if (!src) {
                continue;
            }
            const original = this._originalTargetVolumes[i];
            if (typeof original === 'number') {
                src.volume = original;
            }
        }
    }

    /**
     * コンポーネントが無効化されたときの処理。
     * シーン遷移などで中途半端な状態にならないよう、必要なら音量を復元する。
     */
    onDisable() {
        if (!this.autoRestoreOnDisable) {
            return;
        }

        if (this._isRinging) {
            if (this.logDebug) {
                console.warn('[EarRinging] コンポーネントが無効化されました。耳鳴り状態を強制終了し、音量を復元します。');
            }
        }

        this._isRinging = false;

        // 耳鳴り音の停止と復元
        if (this.earRingingAudioSource) {
            this.earRingingAudioSource.stop();
            this.earRingingAudioSource.volume = this._originalEarVolume;
        }

        // ターゲットの音量復元
        this._restoreTargetVolumes();
    }
}

主要メソッドの動作解説

  • onLoad()
    • earRingingAudioSource が未設定の場合、同一ノードの AudioSource を自動取得。
    • 見つからなければ console.error を出して終了(耳鳴り音は鳴らないが、ゲームは落ちない)。
    • ターゲット AudioSource の元の音量をキャッシュ。
  • update(deltaTime)
    • _isRingingtrue の間だけ動作。
    • 経過時間 _elapsed を進め、0〜1 に正規化した t を使ってフェード処理。
    • _updateEarVolume(t) で耳鳴り音のフェードイン/アウト。
    • _updateTargetVolumes(t) でターゲット AudioSource のボリュームをミュート寄りにしてから元に戻す。
    • 時間が ringDuration を超えたら _finishRinging() を呼んで終了処理。
  • triggerEarRinging()
    • 外部から耳鳴り状態を開始するための公開メソッド。
    • 内部状態をリセットし、元音量を再キャッシュ。
    • 耳鳴り AudioSource を再生し、ボリューム 0 からスタート(フェードイン用)。
  • onDisable()
    • autoRestoreOnDisabletrue のとき、耳鳴り状態を強制終了し、すべての音量を元に戻す。
    • シーン遷移やノード破棄時に音量が中途半端になる事故を防ぐ。

使用手順と動作確認

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

  1. エディタ左下の Assets パネルで、スクリプトを置きたいフォルダ(例: assets/scripts)を選択します。
  2. フォルダ上で右クリック → CreateTypeScript を選択します。
  3. ファイル名を EarRinging.ts に変更します。
  4. 作成された EarRinging.ts をダブルクリックで開き、上記の TypeScript コードをすべて貼り付けて保存します。

2. テスト用シーンとノードの用意

  1. Hierarchy パネルで右クリック → CreateEmpty Node を選択し、名前を AudioRoot などに変更します。
  2. AudioRoot を選択し、InspectorAdd Component ボタンをクリックします。
  3. AudioAudioSource を追加し、これを BGM 用とします。
    • この AudioSource の Clip に、任意の BGM 用 AudioClip(ループ再生しても違和感のない音)を設定します。
    • Loop にチェックを入れ、Play On Load もオンにしておくと、シーン再生時に自動で鳴り始めます。
    • 名前は分かりやすく BGMSource などに変更しておくとよいです。
  4. 同様に、環境音などを試したい場合は、別ノードを作成して AudioSource を追加しておきます。

3. 耳鳴り用ノードと AudioSource の設定

  1. Hierarchy パネルで右クリック → CreateEmpty Node を選択し、名前を EarRingingController に変更します。
  2. EarRingingController を選択し、InspectorAdd ComponentAudioAudioSource を追加します。
  3. この AudioSource の Clip に、「キーン」という耳鳴り用の AudioClip を設定します。
    • Loop は通常オフで問題ありませんが、耳鳴り音が短い場合はループさせてもよいです。
    • Play On Load はオフにしてください(スクリプトから再生を制御するため)。

4. EarRinging コンポーネントのアタッチとプロパティ設定

  1. EarRingingController ノードを選択した状態で、InspectorAdd ComponentCustomEarRinging を選択してアタッチします。
  2. EarRinging コンポーネントのプロパティを設定します。
    • Ear Ringing Audio Source:
      • 自動で同一ノードの AudioSource がセットされていなければ、EarRingingController の AudioSource をドラッグ&ドロップで指定します。
    • Target Audio Sources:
      • サイズ(Size)を 1 に設定し、要素 0AudioRoot(または BGM 用ノード)の AudioSource をドラッグ&ドロップします。
      • 環境音なども一緒にミュートしたい場合は、Size を増やしてそれぞれの AudioSource を追加します。
    • Ring Duration: 3.04.0 秒程度から試してみてください。
    • Fade In Duration: 0.10.3 秒程度。0 にすると即座に最大音量になります。
    • Fade Out Duration: 2.03.0 秒程度。耳鳴りが徐々に引いていく感覚を出します。
    • Ear Max Volume: 1.0(うるさければ 0.60.8 程度に下げる)。
    • Target Muted Volume: 0.0 で完全ミュート。0.050.1 くらいにすると、かすかに音が残るリアルな表現になります。
    • Auto Restore On Disable: 通常は true のままで OK。
    • Log Debug: 動作確認中は true にしてログを見ながら調整すると便利です。

5. 耳鳴り状態をトリガーする簡易テスト

シンプルなテストとして、キー入力で耳鳴りを発動させてみます。

  1. Hierarchy パネルで右クリック → CreateEmpty Node を選択し、名前を TestInput に変更します。
  2. TestInput.ts という TypeScript スクリプトを作成し、以下のような簡易コードを貼り付けます。

import { _decorator, Component, input, Input, KeyCode, EventKeyboard } from 'cc';
import { EarRinging } from './EarRinging';
const { ccclass, property } = _decorator;

@ccclass('TestInput')
export class TestInput extends Component {

    @property({ type: EarRinging, tooltip: '耳鳴りを制御する EarRinging コンポーネント。' })
    public earRinging: EarRinging | null = null;

    onLoad() {
        input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
    }

    onDestroy() {
        input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
    }

    private onKeyDown(event: EventKeyboard) {
        if (event.keyCode === KeyCode.SPACE) {
            if (this.earRinging) {
                this.earRinging.triggerEarRinging();
            }
        }
    }
}
  1. TestInput ノードに TestInput コンポーネントをアタッチします。
  2. TestInputEar Ringing プロパティに、EarRingingController ノード上の EarRinging コンポーネントをドラッグ&ドロップで指定します。
  3. シーンを再生し、BGM が鳴っている状態でキーボードの Space キーを押すと、耳鳴り状態が発動し、BGM がミュートされてキーンという音だけが鳴り、時間経過とともに元に戻ることを確認できます。

※ 実際のゲームでは、爆発や大ダメージを受けたタイミングで triggerEarRinging() を呼び出すように実装すれば、そのまま演出として利用できます。


まとめ

  • EarRinging コンポーネントは、Cocos Creator 3.8.7 と TypeScript で実装した「耳鳴り演出」専用の汎用スクリプトです。
  • 任意のノードにアタッチし、耳鳴り用の AudioSource と、ミュートしたい AudioSource 群をインスペクタで指定するだけで、外部の GameManager やサウンド管理スクリプトに依存せず動作します。
  • 耳鳴りの長さフェードイン/アウト時間耳鳴り音量元の音のミュート量などをすべてプロパティで調整できるため、リアル寄りの表現から誇張した演出まで幅広く対応できます。
  • このコンポーネントをプロジェクトの「共通演出」として用意しておけば、爆発武器やトラップ、巨大ボスの攻撃など、どのシーン・どのギミックからでも簡単に耳鳴り演出を呼び出せるようになり、ゲーム全体の演出クオリティと開発効率を大きく向上させられます。

あとは、実際のゲームに合わせて AudioClip やプロパティ値を微調整し、自分の作品に最適な「キーン…」を作り込んでみてください。

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をコピーしました!