【Cocos Creator】アタッチするだけ!DistanceVolume (距離減衰)の実装方法【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】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 <= minDistancebaseVolume の範囲外など、明らかにおかしい設定値はここで自動補正し、警告を出す。
  • start()
    • ゲーム開始時に 1 回だけ _updateVolume() を呼び出し、初期状態の音量を正しく反映する。
  • update(deltaTime)
    • AudioSource または listenerNode が無い場合は何もしない。
    • updateInterval <= 0 の場合は毎フレーム _updateVolume() を呼ぶ。
    • それ以外は _timeAccumulator に経過時間を足し、updateInterval を超えたタイミングでのみ更新する。
  • _updateVolume()
    • this.node.worldPositionlistenerNode.worldPosition を使ってワールド座標の距離を取得。
    • 距離を minDistancemaxDistance の範囲で 0〜1 に正規化し、t とする。
    • カーブ種別に応じて減衰係数 attenuation を計算:
      • Linear: 1 - t
      • Quadratic: 1 - t * t
    • 最終的な音量は baseVolume * attenuation として AudioSource.volume に適用。
    • debugLog が true のときは距離や音量をログ出力して調整しやすくする。

使用手順と動作確認

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

  1. Assets パネルで右クリック → Create → TypeScript を選択します。
  2. ファイル名を DistanceVolume.ts に変更します。
  3. 作成された DistanceVolume.ts をダブルクリックしてエディタ(VS Code など)で開き、
    既存の中身をすべて削除して、前述の TypeScript コード全体 を貼り付けて保存します。

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

(1) リスナー(プレイヤー)ノードの作成

  1. Hierarchy パネルで右クリック → Create → 3D Object → Node(2D なら Create → 2D Object → Node)を選択し、ノードを作成します。
  2. 名前を Player などに変更します。
  3. このノードは単に「距離の基準位置」として使うだけなので、特別なコンポーネントは不要です。
    既にプレイヤーキャラクターのノードがある場合は、そのノードを使っても構いません。

(2) サウンド発生源ノードの作成と AudioSource の追加

  1. Hierarchy パネルで右クリック → Create → 3D Object → Node(2D なら Create → 2D Object → Node)を選択し、ノードを作成します。
  2. 名前を AmbientSound などに変更します。
  3. AmbientSound ノードを選択し、Inspector パネルで Add Component ボタンをクリックします。
  4. Audio → AudioSource を選択して追加します。
  5. AudioSource の Clip に、距離減衰させたい音声(ループする環境音など)を設定します。
  6. 必要に応じて Loop を ON にします。
  7. AudioSource の Volume1.0 のままでも構いませんが、
    今回はスクリプト側で baseVolume を使うので、AudioSource.volume は 1.0 にしておくことを推奨します。

3. DistanceVolume コンポーネントのアタッチと設定

  1. Hierarchy で AmbientSound ノードを選択します。
  2. Inspector の下部にある Add Component ボタンをクリックします。
  3. Custom カテゴリから DistanceVolume を選択してアタッチします。
  4. 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.050.1 に設定してもよい。
    • Debug Log: 動作確認時に true にすると、距離や音量が Console に表示される。

4. 動作確認の手順

  1. シーンビューで Player ノードと AmbientSound ノードの位置を確認します。
    • 例: Player の Position を (0, 0, 0)、AmbientSound を (0, 0, 5) などに配置。
  2. Play ボタンを押してゲームを実行します。
  3. 実行中に Hierarchy で Player ノードを選択し、Inspector の Position を変更してみます。
    • Player を AmbientSound に近づける(距離 < minDistance)と音量が最大になります。
    • Player を遠ざけて maxDistance 以上にすると音が聞こえなくなります。
  4. Debug Log を ON にしている場合は、Console タブに
    [DistanceVolume] distance=..., volume=... のログが流れるので、距離と音量の関係を数値で確認できます。
  5. 減衰の感触を調整したい場合は:
    • minDistance を大きくすると、「近づいたときに最大音量になる範囲」が広がります。
    • maxDistance を大きくすると、「音が完全に聞こえなくなるまでの距離」が伸びます。
    • curveType を Quadratic にすると、近くでは音が高く保たれ、遠くなると急激に減衰します。
    • baseVolume で全体的な音量の上限を調整します。

まとめ

DistanceVolume コンポーネントは、

  • AudioSource を持つ任意のノードにアタッチし、
  • リスナー(プレイヤー)ノードをインスペクタで指定するだけで、
  • 距離に応じて音量を自動調整する「距離減衰」を実現

できる、完全に独立した汎用スクリプトです。

他の GameManager やシングルトンに一切依存せず、シーン内のどの音源にも簡単に再利用できるため、

  • 環境音(焚き火、滝、街のざわめき)
  • エリア BGM(特定のエリアに近づくと自然にフェードイン/アウト)
  • 3D 空間での立体的な音表現

などにそのまま活用できます。

また、減衰カーブや距離パラメータをすべてインスペクタから調整可能にしているため、サウンドデザイナーやレベルデザイナーがコードに触れずに微調整できるのも大きな利点です。

このコンポーネントをプロジェクトの「標準音源コンポーネント」として用意しておけば、
新しい環境音を追加するときも「AudioSource を付けて DistanceVolume をアタッチし、Listener を指定するだけ」で距離減衰が効くようになり、
ゲーム内のサウンド演出を効率的かつ一貫性のあるものにできます。

必要に応じて、このコンポーネントをベースに「ステレオパンの距離依存」「高さによるフィルタ」などを追加していくのも良い拡張パターンです。

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