【Cocos Creator 3.8】DistanceVolume の実装:アタッチするだけで「プレイヤーとの距離に応じて音量が自動減衰する」汎用スクリプト
このコンポーネントは、リスナー(プレイヤー)とサウンド発生源の距離に応じて AudioSource の音量を自動調整するための汎用スクリプトです。
BGM ではなく「環境音」「効果音」「エリア内のループ音」などに付けておくだけで、プレイヤーが近づくと音が大きく、離れると小さくなる「距離減衰」を簡単に実現できます。
コンポーネントの設計方針
1. 要件整理
- このスクリプトを 音を鳴らすノード(AudioSource を持つノード)にアタッチすると、指定した「リスナー(プレイヤー)ノード」との距離に応じて音量を自動調整する。
- 外部の GameManager や Singleton に依存しない。リスナーは インスペクタの @property で Node を指定する。
- AudioSource がアタッチされていない場合は、実行時に警告ログを出して何もしない(防御的実装)。
- 距離減衰のカーブは、よく使う 2 パターンを用意する:
- 線形 (Linear): 最小距離で最大音量、最大距離で 0 に向かって直線的に減衰。
- 二乗 (Quadratic): 近くでは音量が高く保たれ、遠くで急激に減衰。
- 最小距離より内側では音量は最大(1.0)、最大距離より外側では 0 にする。
- AudioSource の baseVolume(基準音量)に対して倍率をかける方式で、元の音量設定を尊重する。
2. 外部依存をなくす設計アプローチ
- リスナー(プレイヤー)ノードは @property(Node) でインスペクタから指定。
- このコンポーネント自身がアタッチされるノードに AudioSource があることを前提とするが、
onLoad()でgetComponent(AudioSource)を試みる。- 取得できなければ
console.errorでエラーを出し、update 内での処理はスキップする。
- 減衰の計算に必要な最小距離・最大距離・カーブ種別などはすべて @property で公開し、インスペクタから調整できるようにする。
- フレームごとの計算コストを抑えるため、2D/3D どちらでも使えるように「世界座標の距離」だけを使う汎用実装にする。
3. インスペクタで設定可能なプロパティ設計
- listenerNode: Node | null
- 役割: 距離を測定する「リスナー(プレイヤー)」ノード。
- 設定方法: プレイヤーやカメラなど、音量基準となるノードをドラッグ&ドロップ。
- minDistance: number
- 役割: この距離以下に近づいたとき、音量は常に最大(baseVolume)になる。
- 初期値:
1.0 - 例: 1.0 にすると、リスナーと 1m 以内なら最大音量。
- maxDistance: number
- 役割: この距離以上離れたとき、音量は 0 になる。
- 初期値:
10.0 - 注意:
maxDistance > minDistanceである必要がある。そうでない場合は自動で補正し、警告を出す。
- baseVolume: number
- 役割: 減衰前の基準音量(0~1)。この値に距離減衰係数を掛けたものが最終的な AudioSource.volume になる。
- 初期値:
1.0 - 例: 0.5 にすると、最大でも 0.5 を上限として距離減衰が行われる。
- curveType: number (enum)
- 役割: 距離減衰カーブの種類を選択する。
- 選択肢:
0: Linear… 線形減衰。1: Quadratic… 二乗減衰(より自然な感じになりやすい)。
- 初期値:
0 (Linear)
- updateInterval: number
- 役割: 音量更新の間隔(秒)。0 以下の場合は毎フレーム更新。
- 初期値:
0.0(毎フレーム更新) - 例: 0.1 にすると 0.1 秒ごとに音量を更新し、パフォーマンスを少し節約できる。
- debugLog: boolean
- 役割: 有効にすると、距離と計算された音量をログ出力する(調整用)。
- 初期値:
false
TypeScriptコードの実装
import { _decorator, Component, Node, AudioSource, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
/**
* 距離に応じて AudioSource の音量を自動調整するコンポーネント。
* このコンポーネントを AudioSource を持つノードにアタッチし、
* listenerNode にプレイヤー(またはカメラ)ノードを指定してください。
*/
enum DistanceVolumeCurveType {
Linear = 0,
Quadratic = 1,
}
@ccclass('DistanceVolume')
export class DistanceVolume extends Component {
@property({
type: Node,
tooltip: '距離の基準となるリスナー(プレイヤーやカメラ)のノード。\nここで指定したノードとの距離に応じて音量が変化します。'
})
public listenerNode: Node | null = null;
@property({
tooltip: '最小距離(メートル相当)。\nこの距離以下では常に最大音量(baseVolume)になります。'
})
public minDistance: number = 1.0;
@property({
tooltip: '最大距離(メートル相当)。\nこの距離以上では音量が 0 になります。'
})
public maxDistance: number = 10.0;
@property({
tooltip: '基準音量(0〜1)。\n距離減衰前の最大音量です。この値に距離減衰係数を掛けたものが最終的な音量になります。'
})
public baseVolume: number = 1.0;
@property({
tooltip: '減衰カーブの種類。\nLinear: 線形減衰\nQuadratic: 二乗減衰(近くでは高く保たれ、遠くで急激に減衰)'
})
public curveType: DistanceVolumeCurveType = DistanceVolumeCurveType.Linear;
@property({
tooltip: '音量更新の間隔(秒)。\n0 以下の場合は毎フレーム更新します。\n値を大きくするとパフォーマンスは改善しますが、音量変化がカクつく可能性があります。'
})
public updateInterval: number = 0.0;
@property({
tooltip: 'デバッグログを有効にすると、距離と計算された音量をコンソールに出力します。'
})
public debugLog: boolean = false;
private _audioSource: AudioSource | null = null;
private _timeAccumulator: number = 0;
onLoad() {
// 必要な AudioSource コンポーネントを取得
this._audioSource = this.getComponent(AudioSource);
if (!this._audioSource) {
console.error('[DistanceVolume] AudioSource コンポーネントが見つかりません。このスクリプトを使用するノードに AudioSource を追加してください。', this.node);
}
// プロパティの防御的補正
if (this.maxDistance <= this.minDistance) {
console.warn('[DistanceVolume] maxDistance は minDistance より大きい必要があります。自動的に補正します。', this.node);
this.maxDistance = this.minDistance + 0.01;
}
if (this.baseVolume < 0) {
console.warn('[DistanceVolume] baseVolume は 0 以上である必要があります。0 に補正します。', this.node);
this.baseVolume = 0;
} else if (this.baseVolume > 1) {
console.warn('[DistanceVolume] baseVolume は 1 以下である必要があります。1 に補正します。', this.node);
this.baseVolume = 1;
}
}
start() {
// 開始時に一度計算しておく
this._updateVolume();
}
update(deltaTime: number) {
// AudioSource または listenerNode が無い場合は何もしない
if (!this._audioSource || !this.listenerNode) {
return;
}
// 更新間隔が 0 以下なら毎フレーム更新
if (this.updateInterval <= 0) {
this._updateVolume();
return;
}
// 一定間隔でのみ更新する
this._timeAccumulator += deltaTime;
if (this._timeAccumulator >= this.updateInterval) {
this._timeAccumulator = 0;
this._updateVolume();
}
}
/**
* 距離に応じて AudioSource.volume を更新するメイン処理
*/
private _updateVolume() {
if (!this._audioSource || !this.listenerNode) {
return;
}
// ワールド座標で距離を計算(2D/3D 共通)
const sourcePos = this.node.worldPosition;
const listenerPos = this.listenerNode.worldPosition;
const distance = Vec3.distance(sourcePos, listenerPos);
// 距離を 0〜1 の範囲に正規化
// 0: minDistance, 1: maxDistance
let t: number;
if (distance <= this.minDistance) {
t = 0;
} else if (distance >= this.maxDistance) {
t = 1;
} else {
t = (distance - this.minDistance) / (this.maxDistance - this.minDistance);
}
// t から減衰係数(0〜1)を計算
let attenuation: number;
switch (this.curveType) {
case DistanceVolumeCurveType.Quadratic:
// t=0 で 1, t=1 で 0 になるような二乗カーブ (1 - t^2)
attenuation = 1 - t * t;
break;
case DistanceVolumeCurveType.Linear:
default:
// 線形カーブ (1 - t)
attenuation = 1 - t;
break;
}
// 最終的な音量 = baseVolume * attenuation
const finalVolume = this.baseVolume * attenuation;
this._audioSource.volume = finalVolume;
if (this.debugLog) {
console.log(`[DistanceVolume] distance=${distance.toFixed(2)}, t=${t.toFixed(2)}, attenuation=${attenuation.toFixed(2)}, volume=${finalVolume.toFixed(2)}`);
}
}
}
コードの要点解説
- onLoad()
this.getComponent(AudioSource)で同じノードの AudioSource を取得。- 見つからない場合は
console.errorを出し、以降の処理はスキップされる(防御的実装)。 maxDistance <= minDistanceやbaseVolumeの範囲外など、明らかにおかしい設定値はここで自動補正し、警告を出す。
- start()
- ゲーム開始時に 1 回だけ
_updateVolume()を呼び出し、初期状態の音量を正しく反映する。
- ゲーム開始時に 1 回だけ
- update(deltaTime)
- AudioSource または listenerNode が無い場合は何もしない。
updateInterval <= 0の場合は毎フレーム_updateVolume()を呼ぶ。- それ以外は
_timeAccumulatorに経過時間を足し、updateIntervalを超えたタイミングでのみ更新する。
- _updateVolume()
this.node.worldPositionとlistenerNode.worldPositionを使ってワールド座標の距離を取得。- 距離を
minDistance〜maxDistanceの範囲で 0〜1 に正規化し、tとする。 - カーブ種別に応じて減衰係数
attenuationを計算:- Linear:
1 - t - Quadratic:
1 - t * t
- Linear:
- 最終的な音量は
baseVolume * attenuationとして AudioSource.volume に適用。 debugLogが true のときは距離や音量をログ出力して調整しやすくする。
使用手順と動作確認
1. スクリプトファイルの作成
- Assets パネルで右クリック → Create → TypeScript を選択します。
- ファイル名を DistanceVolume.ts に変更します。
- 作成された
DistanceVolume.tsをダブルクリックしてエディタ(VS Code など)で開き、
既存の中身をすべて削除して、前述の TypeScript コード全体 を貼り付けて保存します。
2. テスト用シーンの準備
(1) リスナー(プレイヤー)ノードの作成
- Hierarchy パネルで右クリック → Create → 3D Object → Node(2D なら Create → 2D Object → Node)を選択し、ノードを作成します。
- 名前を Player などに変更します。
- このノードは単に「距離の基準位置」として使うだけなので、特別なコンポーネントは不要です。
既にプレイヤーキャラクターのノードがある場合は、そのノードを使っても構いません。
(2) サウンド発生源ノードの作成と AudioSource の追加
- Hierarchy パネルで右クリック → Create → 3D Object → Node(2D なら Create → 2D Object → Node)を選択し、ノードを作成します。
- 名前を AmbientSound などに変更します。
- AmbientSound ノードを選択し、Inspector パネルで Add Component ボタンをクリックします。
- Audio → AudioSource を選択して追加します。
- AudioSource の Clip に、距離減衰させたい音声(ループする環境音など)を設定します。
- 必要に応じて Loop を ON にします。
- AudioSource の Volume は
1.0のままでも構いませんが、
今回はスクリプト側でbaseVolumeを使うので、AudioSource.volume は 1.0 にしておくことを推奨します。
3. DistanceVolume コンポーネントのアタッチと設定
- Hierarchy で AmbientSound ノードを選択します。
- Inspector の下部にある Add Component ボタンをクリックします。
- Custom カテゴリから DistanceVolume を選択してアタッチします。
- Inspector に表示される DistanceVolume の各プロパティを設定します:
- Listener Node: Hierarchy から Player ノードをドラッグ&ドロップ。
- Min Distance: 例として
1.0に設定。 - Max Distance: 例として
10.0に設定。 - Base Volume: 例として
1.0のままにする(音が大きすぎる場合は 0.5 などに下げる)。 - Curve Type:
- まずは Linear(0)で試してみる。
- より自然な減衰にしたい場合は Quadratic(1)に変更。
- Update Interval:
- テスト時は
0.0(毎フレーム更新)で OK。 - 最適化したい場合は
0.05や0.1に設定してもよい。
- テスト時は
- Debug Log: 動作確認時に
trueにすると、距離や音量が Console に表示される。
4. 動作確認の手順
- シーンビューで Player ノードと AmbientSound ノードの位置を確認します。
- 例: Player の Position を
(0, 0, 0)、AmbientSound を(0, 0, 5)などに配置。
- 例: Player の Position を
- Play ボタンを押してゲームを実行します。
- 実行中に Hierarchy で Player ノードを選択し、Inspector の Position を変更してみます。
- Player を AmbientSound に近づける(距離 < minDistance)と音量が最大になります。
- Player を遠ざけて
maxDistance以上にすると音が聞こえなくなります。
- Debug Log を ON にしている場合は、Console タブに
[DistanceVolume] distance=..., volume=...のログが流れるので、距離と音量の関係を数値で確認できます。 - 減衰の感触を調整したい場合は:
- minDistance を大きくすると、「近づいたときに最大音量になる範囲」が広がります。
- maxDistance を大きくすると、「音が完全に聞こえなくなるまでの距離」が伸びます。
- curveType を Quadratic にすると、近くでは音が高く保たれ、遠くなると急激に減衰します。
- baseVolume で全体的な音量の上限を調整します。
まとめ
DistanceVolume コンポーネントは、
- AudioSource を持つ任意のノードにアタッチし、
- リスナー(プレイヤー)ノードをインスペクタで指定するだけで、
- 距離に応じて音量を自動調整する「距離減衰」を実現
できる、完全に独立した汎用スクリプトです。
他の GameManager やシングルトンに一切依存せず、シーン内のどの音源にも簡単に再利用できるため、
- 環境音(焚き火、滝、街のざわめき)
- エリア BGM(特定のエリアに近づくと自然にフェードイン/アウト)
- 3D 空間での立体的な音表現
などにそのまま活用できます。
また、減衰カーブや距離パラメータをすべてインスペクタから調整可能にしているため、サウンドデザイナーやレベルデザイナーがコードに触れずに微調整できるのも大きな利点です。
このコンポーネントをプロジェクトの「標準音源コンポーネント」として用意しておけば、
新しい環境音を追加するときも「AudioSource を付けて DistanceVolume をアタッチし、Listener を指定するだけ」で距離減衰が効くようになり、
ゲーム内のサウンド演出を効率的かつ一貫性のあるものにできます。
必要に応じて、このコンポーネントをベースに「ステレオパンの距離依存」「高さによるフィルタ」などを追加していくのも良い拡張パターンです。




