【Cocos Creator】アタッチするだけ!LowHealthAudio (瀕死音効)の実装方法【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】LowHealthAudio の実装:アタッチするだけで「瀕死時にBGMへローパスフィルター+音量調整」を自動適用する汎用スクリプト

本記事では、任意のノードにアタッチするだけで「HP が少なくなったときに BGM にローパスフィルターをかけ、瀕死感を演出する」汎用コンポーネント LowHealthAudio を実装します。

HP の値はインスペクタから直接設定・更新できるようにし、他の GameManager や HP スクリプトに依存しない完全独立型コンポーネントとして設計します。BGM 用の AudioSource と組み合わせることで、瀕死演出を簡単に導入できます。


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

要件の整理

  • HP(現在値・最大値)を監視し、一定割合以下になったら「瀕死状態」とみなす
  • 瀕死状態のとき、指定した AudioSource に対して
    • ローパスフィルター(低域のみ通る効果)を適用したようなサウンドにする
    • 音量を下げる/上げるなどの調整を行う
  • Cocos Creator 3.8 には標準でリアルタイムの EQ/ローパスフィルター API がないため、
    以下のような手法で「それっぽい瀕死音」を実現する:
    • 通常 BGM と「ローパス加工済み BGM(事前に DAW 等で作成)」の 2 つの AudioSource を用意し、HP に応じてミックス比率を変える
    • 瀕死時はローパス版をフェードインし、通常版をフェードアウトする
    • 同時に全体の音量を調整することで、こもった感じ+緊迫感を演出
  • HP は他スクリプトから参照せず、インスペクタで設定する数値として保持。
    • ゲーム内で HP を変化させたい場合は、他スクリプトから LowHealthAudiocurrentHp を直接書き換えればよい(依存は「任意」であり、本コンポーネント側からは何も要求しない)。
    • この記事ではエディタ上で値を変えたり、簡易テスト用オプションで挙動を確認できるようにする。
  • 外部シングルトンや GameManager に依存しない。
  • 必要な標準コンポーネント(AudioSource)は getComponent で取得を試み、未設定時にはエラーログを出力して防御的に実装する。

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

LowHealthAudio コンポーネントに用意する @property は以下の通りです。

  • maxHp: number
    • 最大 HP。
    • 0 より大きい値。
    • 現在 HP(currentHp)との比率で瀕死判定を行う。
  • currentHp: number
    • 現在 HP。
    • 0 ~ maxHp の範囲で運用することを想定。
    • ゲーム中に他スクリプトから変更してもよいし、テスト時はインスペクタから直接変更して挙動を確認できる。
  • lowHealthThresholdRatio: number
    • 瀕死判定の HP 割合(0〜1)。
    • 例: 0.3 なら「HP が 30% 未満になったら瀕死状態」。
  • normalAudioSource: AudioSource
    • 通常時に再生する BGM 用 AudioSource
    • 同じノードについている AudioSource を自動取得するが、インスペクタで別ノードのものを指定してもよい。
  • lowpassAudioSource: AudioSource
    • ローパスフィルタがかかった BGM(事前に加工しておく)の AudioSource
    • 通常 BGM と同じ曲のローパス版を用意すると自然な切り替えになる。
  • enableVolumeMix: boolean
    • 通常 BGM とローパス BGM の音量を自動でミックスするかどうか。
    • オンの場合、HP に応じて両方の AudioSourcevolume を自動調整する。
  • normalVolume: number
    • 通常時の BGM 音量(0〜1)。
    • HP が十分あるときに normalAudioSource.volume がこの値に近づく。
  • lowpassMaxVolume: number
    • 瀕死時にローパス BGM が到達する最大音量(0〜1)。
    • HP が閾値を大きく下回るときに lowpassAudioSource.volume がこの値に近づく。
  • globalVolumeScaleInLowHealth: number
    • 瀕死時に全体音量をどの程度に抑えるか(0〜1)。
    • 1.0 なら音量を変えない。0.5 なら全体を半分程度に抑える。
  • transitionSpeed: number
    • 通常状態 ↔ 瀕死状態の音量変化スピード。
    • 大きいほど切り替えが素早く、小さいほどゆっくりフェードする。
  • simulateHpWithKey: boolean
    • テスト用オプション。
    • オンにすると、ゲーム実行中に特定キー入力で HP を増減させて挙動確認できる。
  • hpDecreaseKey: KeyCode
    • HP を減らすキー。
    • デフォルトは KeyCode.KEY_Z など。
  • hpIncreaseKey: KeyCode
    • HP を増やすキー。
    • デフォルトは KeyCode.KEY_X など。
  • hpChangeStep: number
    • テスト用キー 1 回で増減する HP 量。
    • 例: 10 とすれば、Z キーで 10 減少、X キーで 10 増加。

以上のプロパティにより、HP 管理/瀕死閾値/音量バランス/テスト方法 をすべてインスペクタから調整できるようにします。


TypeScriptコードの実装

以下が完成した LowHealthAudio.ts の全コードです。


import { _decorator, Component, AudioSource, Node, KeyCode, input, Input, EventKeyboard, math } from 'cc';
const { ccclass, property } = _decorator;

/**
 * LowHealthAudio
 * HP の割合に応じて、通常 BGM とローパス版 BGM の音量をミックスし、
 * 瀕死時にローパス感のあるサウンド演出を行うコンポーネント。
 *
 * - 外部の GameManager や HP スクリプトに依存せず、インスペクタの数値で HP を管理。
 * - ゲーム中に他スクリプトから currentHp を書き換えることも可能。
 * - normalAudioSource / lowpassAudioSource をアタッチして使用する。
 */
@ccclass('LowHealthAudio')
export class LowHealthAudio extends Component {

    @property({
        tooltip: '最大HP。0より大きい値を指定してください。'
    })
    public maxHp: number = 100;

    @property({
        tooltip: '現在HP。0〜maxHpの範囲で運用します。ゲーム中に他スクリプトから変更しても構いません。'
    })
    public currentHp: number = 100;

    @property({
        tooltip: '瀕死判定となるHP割合。0.3ならHPが30%未満で瀕死状態になります。0〜1の範囲で指定してください。'
    })
    public lowHealthThresholdRatio: number = 0.3;

    @property({
        type: AudioSource,
        tooltip: '通常時に再生するBGM用AudioSource。未指定の場合は、このノードに付いているAudioSourceを自動取得します。'
    })
    public normalAudioSource: AudioSource | null = null;

    @property({
        type: AudioSource,
        tooltip: 'ローパスフィルタがかかったBGM用AudioSource。事前に別ファイルとして用意してアサインしてください。'
    })
    public lowpassAudioSource: AudioSource | null = null;

    @property({
        tooltip: 'HPに応じて通常BGMとローパスBGMの音量を自動ミックスするかどうか。'
    })
    public enableVolumeMix: boolean = true;

    @property({
        tooltip: '通常時のBGM音量(0〜1)。HPが十分あるときにnormalAudioSource.volumeがこの値に近づきます。'
    })
    public normalVolume: number = 1.0;

    @property({
        tooltip: '瀕死時にローパスBGMが到達する最大音量(0〜1)。HPが低いほどlowpassAudioSource.volumeがこの値に近づきます。'
    })
    public lowpassMaxVolume: number = 0.8;

    @property({
        tooltip: '瀕死時に全体音量をどの程度に抑えるか(0〜1)。1.0で変化なし、0.5で半分程度の音量になります。'
    })
    public globalVolumeScaleInLowHealth: number = 0.7;

    @property({
        tooltip: '通常状態と瀕死状態の音量変化スピード。大きいほど素早く切り替わります。'
    })
    public transitionSpeed: number = 5.0;

    @property({
        tooltip: 'テスト用:オンにすると、実行中にキー入力でHPを増減させて動作確認できます。'
    })
    public simulateHpWithKey: boolean = true;

    @property({
        type: KeyCode,
        tooltip: 'テスト用:HPを減らすキー。'
    })
    public hpDecreaseKey: KeyCode = KeyCode.KEY_Z;

    @property({
        type: KeyCode,
        tooltip: 'テスト用:HPを増やすキー。'
    })
    public hpIncreaseKey: KeyCode = KeyCode.KEY_X;

    @property({
        tooltip: 'テスト用:キー1回で増減するHP量。'
    })
    public hpChangeStep: number = 10;

    // 内部状態
    private _isLowHealth: boolean = false;
    private _targetNormalVolume: number = 0;
    private _targetLowpassVolume: number = 0;

    onLoad() {
        // 必要なAudioSourceの存在チェックと自動取得
        if (!this.normalAudioSource) {
            const found = this.getComponent(AudioSource);
            if (found) {
                this.normalAudioSource = found;
            } else {
                console.error('[LowHealthAudio] normalAudioSourceが設定されておらず、このノードにもAudioSourceが見つかりません。通常BGM用のAudioSourceをアタッチしてください。', this.node);
            }
        }

        if (!this.lowpassAudioSource) {
            // lowpassAudioSourceは別ノードに置くことを想定しているため、自動取得は行わない
            console.warn('[LowHealthAudio] lowpassAudioSourceが設定されていません。ローパスBGMを使用しない場合はこの警告は無視できますが、瀕死演出の効果は限定的になります。', this.node);
        }

        // HPの初期値を安全な範囲にクランプ
        if (this.maxHp <= 0) {
            console.warn('[LowHealthAudio] maxHpが0以下です。1に補正します。');
            this.maxHp = 1;
        }
        this.currentHp = math.clamp(this.currentHp, 0, this.maxHp);

        // 初期状態の判定
        this._updateLowHealthFlag();

        // キー入力の登録(テスト用)
        if (this.simulateHpWithKey) {
            input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        }
    }

    start() {
        // AudioSourceの初期ボリューム設定
        if (this.normalAudioSource) {
            this.normalAudioSource.play();
        }
        if (this.lowpassAudioSource) {
            this.lowpassAudioSource.play();
        }

        // 初期のボリュームターゲットを計算
        this._recalculateTargetVolumes(true);
        this._applyVolumesImmediate();
    }

    onDestroy() {
        if (this.simulateHpWithKey) {
            input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        }
    }

    update(deltaTime: number) {
        // HPの割合に応じて瀕死フラグを更新
        this._updateLowHealthFlag();

        // ボリュームの目標値を再計算
        this._recalculateTargetVolumes(false);

        // 実際のAudioSource.volumeを補間しながら適用
        this._applyVolumesSmooth(deltaTime);
    }

    /**
     * HP割合から瀕死状態かどうかを判定し、フラグを更新する。
     */
    private _updateLowHealthFlag() {
        const ratio = this.maxHp > 0 ? (this.currentHp / this.maxHp) : 0;
        const wasLow = this._isLowHealth;
        this._isLowHealth = ratio < this.lowHealthThresholdRatio;

        // 状態が変わったときにログを出しておくとデバッグしやすい
        if (wasLow !== this._isLowHealth) {
            console.log(`[LowHealthAudio] Low health state changed: ${this._isLowHealth ? 'ENTER' : 'EXIT'} low health. (ratio=${ratio.toFixed(2)})`);
        }
    }

    /**
     * 現在のHP割合に応じて、通常BGMとローパスBGMの目標音量を計算する。
     * @param forceInstant 即時適用用の初期計算かどうか
     */
    private _recalculateTargetVolumes(forceInstant: boolean) {
        if (!this.enableVolumeMix) {
            // ミックスを行わない場合は、瀕死時にローパスのみを鳴らす/通常時に通常のみ鳴らすなどの単純制御にしてもよいが、
            // ここではシンプルに「通常BGMのみを使用」する挙動とする。
            this._targetNormalVolume = this.normalVolume;
            this._targetLowpassVolume = 0;
            return;
        }

        const ratio = this.maxHp > 0 ? (this.currentHp / this.maxHp) : 0;
        const clampedRatio = math.clamp01(ratio);

        // 0〜1で「どれだけ瀕死に近いか」を表現する値を計算
        // thresholdより上では0、0に近づくほど1に近づくようなカーブにする。
        let lowHealthIntensity = 0;
        if (clampedRatio < this.lowHealthThresholdRatio) {
            // 例:閾値30%の場合、HP30%→0%、intensity 0→1
            const t = clampedRatio / Math.max(this.lowHealthThresholdRatio, 0.0001);
            lowHealthIntensity = 1 - math.clamp01(t);
        } else {
            lowHealthIntensity = 0;
        }

        // 通常BGMの目標音量:瀕死強度が増すほど下がる
        const baseNormal = this.normalVolume;
        const minNormal = this.normalVolume * 0.2; // 瀕死時の最小音量を20%程度に
        const normalVol = math.lerp(baseNormal, minNormal, lowHealthIntensity);

        // ローパスBGMの目標音量:瀕死強度が増すほど上がる
        const lowpassVol = this.lowpassMaxVolume * lowHealthIntensity;

        // 全体音量スケール(瀕死時に全体を少し下げる)
        const globalScale = math.lerp(1.0, this.globalVolumeScaleInLowHealth, lowHealthIntensity);

        this._targetNormalVolume = normalVol * globalScale;
        this._targetLowpassVolume = lowpassVol * globalScale;

        if (forceInstant) {
            // 初期適用時は即座に反映させるため、現在値もターゲットに合わせておく
            if (this.normalAudioSource) {
                this.normalAudioSource.volume = this._targetNormalVolume;
            }
            if (this.lowpassAudioSource) {
                this.lowpassAudioSource.volume = this._targetLowpassVolume;
            }
        }
    }

    /**
     * 初期化時にターゲット音量を即時適用する。
     */
    private _applyVolumesImmediate() {
        if (this.normalAudioSource) {
            this.normalAudioSource.volume = this._targetNormalVolume;
        }
        if (this.lowpassAudioSource) {
            this.lowpassAudioSource.volume = this._targetLowpassVolume;
        }
    }

    /**
     * update内で呼び出し、現在のAudioSource.volumeをターゲット値に向かって補間する。
     */
    private _applyVolumesSmooth(deltaTime: number) {
        if (!this.enableVolumeMix) {
            // ミックス無効時は即時適用で十分
            this._applyVolumesImmediate();
            return;
        }

        const lerpFactor = math.clamp01(this.transitionSpeed * deltaTime);

        if (this.normalAudioSource) {
            const current = this.normalAudioSource.volume;
            const target = this._targetNormalVolume;
            this.normalAudioSource.volume = math.lerp(current, target, lerpFactor);
        }

        if (this.lowpassAudioSource) {
            const current = this.lowpassAudioSource.volume;
            const target = this._targetLowpassVolume;
            this.lowpassAudioSource.volume = math.lerp(current, target, lerpFactor);
        }
    }

    /**
     * テスト用:キー入力でHPを増減させる。
     */
    private _onKeyDown(event: EventKeyboard) {
        if (!this.simulateHpWithKey) {
            return;
        }

        if (event.keyCode === this.hpDecreaseKey) {
            this.currentHp = math.clamp(this.currentHp - this.hpChangeStep, 0, this.maxHp);
            console.log(`[LowHealthAudio] HP decreased: ${this.currentHp}/${this.maxHp}`);
        } else if (event.keyCode === this.hpIncreaseKey) {
            this.currentHp = math.clamp(this.currentHp + this.hpChangeStep, 0, this.maxHp);
            console.log(`[LowHealthAudio] HP increased: ${this.currentHp}/${this.maxHp}`);
        }
    }
}

コードのポイント解説

  • onLoad
    • normalAudioSource が未設定なら、this.node.getComponent(AudioSource) で自動取得を試みます。
    • 見つからない場合は console.error を出力し、エディタでの設定ミスに気づけるようにしています。
    • lowpassAudioSource は別ノードに置くケースが多いので自動取得はせず、未設定時は console.warn を出します。
    • HP の初期値を 0〜maxHp にクランプし、maxHp <= 0 の場合は 1 に補正します。
    • テスト用のキー入力リスナーを登録します(simulateHpWithKey が true のとき)。
  • start
    • 通常 BGM とローパス BGM を再生開始します(どちらもループ設定推奨)。
    • 現在の HP から初期の音量ターゲットを計算し、即時適用します。
  • update
    • 毎フレーム HP 割合から瀕死状態かどうかを判定し、内部フラグ _isLowHealth を更新。
    • HP 割合に応じて通常 BGM とローパス BGM の目標音量を再計算します。
    • transitionSpeed に基づき math.lerp で現在の AudioSource.volume をターゲット値に近づけ、滑らかなフェードを実現します。
  • _recalculateTargetVolumes
    • HP 割合(0〜1)と lowHealthThresholdRatio から「どれだけ瀕死に近いか(0〜1)」を算出し、それをもとに
      • 通常 BGM の目標音量(瀕死になるほど小さく)
      • ローパス BGM の目標音量(瀕死になるほど大きく)
      • 全体音量スケール(瀕死になるほど globalVolumeScaleInLowHealth に近づく)

      を計算します。

  • _onKeyDown
    • テスト用機能です。simulateHpWithKey がオンのときだけ動作します。
    • hpDecreaseKey で HP を減らし、hpIncreaseKey で HP を増やします。
    • 変更後の HP はログに出力されるので、コンソールで確認できます。

使用手順と動作確認

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

  1. エディタ左下の Assets パネルで、任意のフォルダ(例: scripts)を右クリックします。
  2. Create → TypeScript を選択し、ファイル名を LowHealthAudio.ts にします。
  3. 自動生成されたスクリプトをダブルクリックして開き、内容をすべて削除してから、前章のコードを丸ごと貼り付けて保存します。

2. BGM 用の AudioSource 準備

ローパスフィルター自体は事前に音声ファイルとして用意します。ここでは例として:

  • bgm_normal.mp3:通常 BGM
  • bgm_lowpass.mp3:ローパス加工済み BGM
  1. Assets パネルに BGM ファイル(通常版とローパス版)をインポートします。
  2. Hierarchy パネルで、BGM 用のノードを 2 つ作成します。
    • 右クリック → Create → Empty Node
      • BGM_Normal
      • BGM_Lowpass

      の 2 つを作成。

  3. BGM_Normal ノードを選択し、InspectorAdd Component → Audio → AudioSource を追加します。
    • Clipbgm_normal.mp3 をドラッグ&ドロップ。
    • Loop にチェックを入れ、ループ再生にします。
    • Volume はとりあえず 1.0 のままで構いません(スクリプト側で調整します)。
  4. BGM_Lowpass ノードにも同様に AudioSource を追加し、
    • Clipbgm_lowpass.mp3 を設定。
    • Loop をオン。
    • Volume は 0.0〜0.3 程度にしておき、スクリプト側で上書きされる前提で扱います。

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

LowHealthAudio はどのノードに付けても構いませんが、ここでは分かりやすく BGM_Normal ノードに付ける例で説明します。

  1. HierarchyBGM_Normal ノードを選択します。
  2. InspectorAdd Component → Custom → LowHealthAudio を選択してアタッチします。
  3. Inspector に表示された LowHealthAudio の各プロパティを設定します:
    • Max Hp: 100
    • Current Hp: 100(初期は全快)
    • Low Health Threshold Ratio: 0.3
      • HP 30% 未満で瀕死状態と判定されます。
    • Normal Audio Source:
      • 自動的に BGM_NormalAudioSource が入っているはずです。
    • Lowpass Audio Source:
      • ここに BGM_Lowpass ノードの AudioSource をドラッグ&ドロップします。
    • Enable Volume Mix: ON
    • Normal Volume: 1.0
    • Lowpass Max Volume: 0.8(お好みで調整)
    • Global Volume Scale In Low Health: 0.7(瀕死時は全体を 70% くらいに)
    • Transition Speed: 5.0(切り替えの速さ。3〜10あたりで好みに調整)
    • Simulate Hp With Key: ON(まずはテスト用にオンにしておくと便利)
    • Hp Decrease Key: KEY_Z
    • Hp Increase Key: KEY_X
    • Hp Change Step: 10

4. 動作確認(シミュレーションビュー)

  1. エディタ右上の Play ボタンを押してゲームを実行します。
  2. ゲームビューが立ち上がると、BGM_NormalBGM_Lowpass の両方が再生されますが、初期状態では通常 BGM がメインで聞こえ、ローパス BGM はほぼ聞こえない状態になっています。
  3. ゲームビューがアクティブな状態で、キーボードの Z キー を何度か押してみてください。
    • そのたびに HP が Hp Change Step 分だけ減少し、コンソールに [LowHealthAudio] HP decreased: xx/100 のようなログが表示されます。
    • HP が Max Hp × Low Health Threshold Ratio(例では 30)を下回ると、徐々に通常 BGM の音量が下がり、ローパス BGM の音量が上がっていきます。
    • 同時に、全体の音量もやや下がり、こもったような瀕死演出がかかります。
  4. 今度は X キー を押して HP を回復させてみてください。
    • HP が閾値を上回ると、今度は逆に通常 BGM がフェードインし、ローパス BGM がフェードアウトしていきます。
  5. HP 変化の度合いや切り替わりのスピードが激しすぎる/遅すぎると感じたら、
    • Transition Speed
    • Low Health Threshold Ratio
    • Lowpass Max Volume
    • Global Volume Scale In Low Health

    を調整しながら、自分のゲームに合うバランスを探ってください。

5. 実際のゲームへの組み込み

実運用では、ゲームの HP 管理スクリプトから LowHealthAudiocurrentHp を更新するだけで瀕死演出が自動でかかるようになります。

  • 例:他スクリプトからの連携(依存は任意)
    • プレイヤーの HP を管理するスクリプト側で、ダメージ処理時に
      • lowHealthAudio.currentHp = playerHp;

      のように更新すれば、LowHealthAudio が自動的に音を調整してくれます。

    • LowHealthAudio 側は何も要求していないので、あくまで「使いたい側が参照するだけ」の緩い結合です。

まとめ

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

  • HP を監視して瀕死時に BGM へローパスフィルター風の演出をかける
  • 他のスクリプトやシングルトンに一切依存しない
  • インスペクタから HP や閾値、音量バランス、テスト方法まで柔軟に調整できる

という要件を満たす汎用コンポーネント LowHealthAudio を実装しました。

このコンポーネントを使うことで:

  • 「瀕死時に音をこもらせたい」「緊迫感を音で表現したい」といった要望を、スクリプト 1 つをアタッチするだけで満たせます。
  • ゲームごとに異なる BGM や HP 仕様に対しても、プロパティを調整するだけで再利用できます。
  • ローパス加工済み BGM を差し替えるだけで、簡単に演出の雰囲気を変えられます。

応用としては:

  • 瀕死時に SE の音量もまとめて下げるコンポーネントと組み合わせる
  • HP だけでなく、時間制限やスタミナなど別のパラメータで瀕死演出をかけるように拡張する
  • ローパス版 BGM を複数用意し、ボス戦中のみ別のローパス演出に切り替える

といったバリエーションも考えられます。

まずは本記事の LowHealthAudio.ts をそのままプロジェクトに組み込み、BGM と HP 設定を差し替えながら、自分のゲームに合った「瀕死音効」を作り込んでみてください。

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