【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 に応じてミックス比率を変える - 瀕死時はローパス版をフェードインし、通常版をフェードアウトする
- 同時に全体の音量を調整することで、こもった感じ+緊迫感を演出
- 通常 BGM と「ローパス加工済み BGM(事前に DAW 等で作成)」の 2 つの
- HP は他スクリプトから参照せず、インスペクタで設定する数値として保持。
- ゲーム内で HP を変化させたい場合は、他スクリプトから
LowHealthAudioのcurrentHpを直接書き換えればよい(依存は「任意」であり、本コンポーネント側からは何も要求しない)。 - この記事ではエディタ上で値を変えたり、簡易テスト用オプションで挙動を確認できるようにする。
- ゲーム内で HP を変化させたい場合は、他スクリプトから
- 外部シングルトンや 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を自動取得するが、インスペクタで別ノードのものを指定してもよい。
- 通常時に再生する BGM 用
- lowpassAudioSource: AudioSource
- ローパスフィルタがかかった BGM(事前に加工しておく)の
AudioSource。 - 通常 BGM と同じ曲のローパス版を用意すると自然な切り替えになる。
- ローパスフィルタがかかった BGM(事前に加工しておく)の
- enableVolumeMix: boolean
- 通常 BGM とローパス BGM の音量を自動でミックスするかどうか。
- オンの場合、HP に応じて両方の
AudioSourceのvolumeを自動調整する。
- 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をターゲット値に近づけ、滑らかなフェードを実現します。
- 毎フレーム HP 割合から瀕死状態かどうかを判定し、内部フラグ
- _recalculateTargetVolumes
- HP 割合(0〜1)と
lowHealthThresholdRatioから「どれだけ瀕死に近いか(0〜1)」を算出し、それをもとに- 通常 BGM の目標音量(瀕死になるほど小さく)
- ローパス BGM の目標音量(瀕死になるほど大きく)
- 全体音量スケール(瀕死になるほど
globalVolumeScaleInLowHealthに近づく)
を計算します。
- HP 割合(0〜1)と
- _onKeyDown
- テスト用機能です。
simulateHpWithKeyがオンのときだけ動作します。 hpDecreaseKeyで HP を減らし、hpIncreaseKeyで HP を増やします。- 変更後の HP はログに出力されるので、コンソールで確認できます。
- テスト用機能です。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ左下の Assets パネルで、任意のフォルダ(例:
scripts)を右クリックします。 - Create → TypeScript を選択し、ファイル名を
LowHealthAudio.tsにします。 - 自動生成されたスクリプトをダブルクリックして開き、内容をすべて削除してから、前章のコードを丸ごと貼り付けて保存します。
2. BGM 用の AudioSource 準備
ローパスフィルター自体は事前に音声ファイルとして用意します。ここでは例として:
bgm_normal.mp3:通常 BGMbgm_lowpass.mp3:ローパス加工済み BGM
- Assets パネルに BGM ファイル(通常版とローパス版)をインポートします。
- Hierarchy パネルで、BGM 用のノードを 2 つ作成します。
- 右クリック → Create → Empty Node で
BGM_NormalBGM_Lowpass
の 2 つを作成。
- 右クリック → Create → Empty Node で
BGM_Normalノードを選択し、Inspector の Add Component → Audio → AudioSource を追加します。- Clip に
bgm_normal.mp3をドラッグ&ドロップ。 - Loop にチェックを入れ、ループ再生にします。
- Volume はとりあえず 1.0 のままで構いません(スクリプト側で調整します)。
- Clip に
BGM_Lowpassノードにも同様に AudioSource を追加し、- Clip に
bgm_lowpass.mp3を設定。 - Loop をオン。
- Volume は 0.0〜0.3 程度にしておき、スクリプト側で上書きされる前提で扱います。
- Clip に
3. LowHealthAudio コンポーネントのアタッチ
LowHealthAudio はどのノードに付けても構いませんが、ここでは分かりやすく BGM_Normal ノードに付ける例で説明します。
- Hierarchy で
BGM_Normalノードを選択します。 - Inspector の Add Component → Custom → LowHealthAudio を選択してアタッチします。
- Inspector に表示された LowHealthAudio の各プロパティを設定します:
- Max Hp:
100 - Current Hp:
100(初期は全快) - Low Health Threshold Ratio:
0.3- HP 30% 未満で瀕死状態と判定されます。
- Normal Audio Source:
- 自動的に
BGM_NormalのAudioSourceが入っているはずです。
- 自動的に
- 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
- Max Hp:
4. 動作確認(シミュレーションビュー)
- エディタ右上の Play ボタンを押してゲームを実行します。
- ゲームビューが立ち上がると、
BGM_NormalとBGM_Lowpassの両方が再生されますが、初期状態では通常 BGM がメインで聞こえ、ローパス BGM はほぼ聞こえない状態になっています。 - ゲームビューがアクティブな状態で、キーボードの Z キー を何度か押してみてください。
- そのたびに HP が
Hp Change Step分だけ減少し、コンソールに[LowHealthAudio] HP decreased: xx/100のようなログが表示されます。 - HP が
Max Hp × Low Health Threshold Ratio(例では 30)を下回ると、徐々に通常 BGM の音量が下がり、ローパス BGM の音量が上がっていきます。 - 同時に、全体の音量もやや下がり、こもったような瀕死演出がかかります。
- そのたびに HP が
- 今度は X キー を押して HP を回復させてみてください。
- HP が閾値を上回ると、今度は逆に通常 BGM がフェードインし、ローパス BGM がフェードアウトしていきます。
- HP 変化の度合いや切り替わりのスピードが激しすぎる/遅すぎると感じたら、
- Transition Speed
- Low Health Threshold Ratio
- Lowpass Max Volume
- Global Volume Scale In Low Health
を調整しながら、自分のゲームに合うバランスを探ってください。
5. 実際のゲームへの組み込み
実運用では、ゲームの HP 管理スクリプトから LowHealthAudio の currentHp を更新するだけで瀕死演出が自動でかかるようになります。
- 例:他スクリプトからの連携(依存は任意)
- プレイヤーの HP を管理するスクリプト側で、ダメージ処理時に
lowHealthAudio.currentHp = playerHp;
のように更新すれば、LowHealthAudio が自動的に音を調整してくれます。
- LowHealthAudio 側は何も要求していないので、あくまで「使いたい側が参照するだけ」の緩い結合です。
- プレイヤーの HP を管理するスクリプト側で、ダメージ処理時に
まとめ
本記事では、Cocos Creator 3.8 / TypeScript で、
- HP を監視して瀕死時に BGM へローパスフィルター風の演出をかける
- 他のスクリプトやシングルトンに一切依存しない
- インスペクタから HP や閾値、音量バランス、テスト方法まで柔軟に調整できる
という要件を満たす汎用コンポーネント LowHealthAudio を実装しました。
このコンポーネントを使うことで:
- 「瀕死時に音をこもらせたい」「緊迫感を音で表現したい」といった要望を、スクリプト 1 つをアタッチするだけで満たせます。
- ゲームごとに異なる BGM や HP 仕様に対しても、プロパティを調整するだけで再利用できます。
- ローパス加工済み BGM を差し替えるだけで、簡単に演出の雰囲気を変えられます。
応用としては:
- 瀕死時に SE の音量もまとめて下げるコンポーネントと組み合わせる
- HP だけでなく、時間制限やスタミナなど別のパラメータで瀕死演出をかけるように拡張する
- ローパス版 BGM を複数用意し、ボス戦中のみ別のローパス演出に切り替える
といったバリエーションも考えられます。
まずは本記事の LowHealthAudio.ts をそのままプロジェクトに組み込み、BGM と HP 設定を差し替えながら、自分のゲームに合った「瀕死音効」を作り込んでみてください。




