【Cocos Creator】アタッチするだけ!RandomPitch (ピッチ変化)の実装方法【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】RandomPitch の実装:アタッチするだけで AudioSource の pitch_scale を毎回ランダム変化させて、同じ効果音の単調さを消す汎用スクリプト

同じ効果音を何度も再生していると「機械的で単調な印象」になりがちです。
この記事では、AudioSource にアタッチするだけで、再生のたびに pitch_scale をランダムに揺らしてくれる汎用コンポーネント「RandomPitch」を実装します。

外部の GameManager などには一切依存せず、このスクリプト単体で完結します。
インスペクタから「ピッチの揺れ幅」「ランダム方式」などを調整できるようにし、効果音のバリエーション感を簡単に演出できるようにします。


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

1. 機能要件の整理

  • このコンポーネントは AudioSource コンポーネントを持つノード にアタッチして使用する。
  • AudioSource.play() などで再生されるたびにpitch(pitch_scale 相当)をランダムに変更する。
  • ランダムにするピッチは、基準ピッチ(basePitch) を中心に、最小値〜最大値の範囲で変化させる。
  • ランダムの方式を選べるようにする:
    • 範囲内で完全ランダム(毎回違う値)
    • 指定ステップごとの値にスナップ(0.05刻みなど)
  • オプションとして、再生中に途中でピッチが変わらないように「再生直前のみ変更」する方式にする。
  • AudioSource が存在しない場合は、エラーログを出して自動で何もしない防御的実装にする。

Cocos Creator 3.8 では、AudioSource.pitch プロパティが pitch_scale に相当します。
本コンポーネントでは、AudioSource の pitch を直接書き換えることで実現します。

2. 外部依存をなくす設計アプローチ

  • シングルトンや GameManager などの外部スクリプトには一切依存しない
  • 必要な設定値はすべて @property でインスペクタから指定可能にする。
  • AudioSource への参照も getComponent(AudioSource) で自動取得し、失敗したらログを出す。
  • 「このコンポーネントをアタッチしただけで動く」ことを前提とし、他ノードへのドラッグ&ドロップ参照は不要とする。

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

  • enabledRandomPitch: boolean
    • ピッチのランダム化を有効にするかどうか。
    • true の場合のみ、再生のたびにピッチをランダムに変更する。
    • 簡単にオン/オフ切り替えできるようにするトグル。
  • basePitch: number
    • ピッチの基準値。1.0 が通常速度。
    • 通常は 1.0 のままで良いが、「全体的に高めにしたい」「全体的に低めにしたい」場合に調整する。
  • minPitchOffset: number
    • 基準ピッチからの最小オフセット(マイナス方向)。
    • 例:basePitch = 1.0, minPitchOffset = -0.1, maxPitchOffset = 0.1 の場合、
      0.9〜1.1 の範囲でランダム。
    • 負の値を指定することで、低くなる方向の幅を表現する。
  • maxPitchOffset: number
    • 基準ピッチからの最大オフセット(プラス方向)。
    • minPitchOffset より大きい値である必要がある。
    • 例:basePitch = 1.0, min=-0.05, max=0.15 → 0.95〜1.15 の範囲。
  • useStep: boolean
    • ランダム値を「段階的な値」にスナップするかどうか。
    • ON にすると、stepSize ごとの刻み(例:0.05刻み)に丸める。
  • stepSize: number
    • useStep が true のときにのみ使用。
    • ピッチの刻み幅。例:0.05 にすると 0.95, 1.00, 1.05, 1.10… のように変化。
    • 0 以下の値は無効として扱い、ログを出して無視する。
  • logAppliedPitch: boolean
    • デバッグ用。適用されたピッチを console.log に出すかどうか。
    • 開発中に挙動を確認するときに便利。リリース時は OFF 推奨。
  • applyOnStart: boolean
    • start() 時に一度ピッチをランダム適用しておくかどうか。
    • AudioSource を「Play On Load」にしている場合などに有効。

これらのプロパティにより、「どのくらい揺らすか」「どのように揺らすか」をインスペクタから柔軟に調整できます。


TypeScriptコードの実装

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


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

/**
 * RandomPitch
 * 
 * AudioSource の pitch を、再生のたびにランダムに変化させるコンポーネント。
 * - 他スクリプトへの依存なし
 * - このコンポーネントを AudioSource を持つノードにアタッチするだけで機能
 */
@ccclass('RandomPitch')
export class RandomPitch extends Component {

    @property({
        tooltip: 'ピッチのランダム化を有効にします。OFF にすると基準ピッチのまま再生されます。'
    })
    public enabledRandomPitch: boolean = true;

    @property({
        tooltip: '基準ピッチ(1.0 が通常)。この値を中心にランダムなオフセットが加算されます。'
    })
    public basePitch: number = 1.0;

    @property({
        tooltip: '基準ピッチからの最小オフセット(マイナス方向)。例: -0.1 なら 0.9 まで低くなり得ます。'
    })
    public minPitchOffset: number = -0.05;

    @property({
        tooltip: '基準ピッチからの最大オフセット(プラス方向)。例: 0.1 なら 1.1 まで高くなり得ます。'
    })
    public maxPitchOffset: number = 0.05;

    @property({
        tooltip: 'ピッチ値をステップ刻みにスナップするかどうか。ON の場合、stepSize ごとの値に丸められます。'
    })
    public useStep: boolean = false;

    @property({
        tooltip: 'ピッチのステップ幅。例: 0.05 にすると 0.95, 1.00, 1.05 ... のような刻みになります。',
        visible() {
            return this.useStep;
        }
    })
    public stepSize: number = 0.05;

    @property({
        tooltip: '適用されたピッチ値をログ出力します(デバッグ用)。'
    })
    public logAppliedPitch: boolean = false;

    @property({
        tooltip: 'start() のタイミングで一度ランダムピッチを適用します。Play On Load の AudioSource に便利です。'
    })
    public applyOnStart: boolean = true;

    /** 対象の AudioSource コンポーネント */
    private _audioSource: AudioSource | null = null;

    onLoad() {
        // 同じノード上の AudioSource を取得
        this._audioSource = this.getComponent(AudioSource);

        if (!this._audioSource) {
            console.error('[RandomPitch] AudioSource コンポーネントが見つかりません。このコンポーネントを使用するノードには AudioSource を追加してください。', this.node);
            return;
        }

        // 初期状態として、AudioSource の pitch を basePitch に合わせておく
        this._audioSource.pitch = this.basePitch;
    }

    start() {
        // AudioSource が見つからなかった場合は何もしない
        if (!this._audioSource) {
            return;
        }

        // オプションで、start 時に一度ランダムピッチを適用
        if (this.applyOnStart && this.enabledRandomPitch) {
            this.applyRandomPitch();
        }
    }

    /**
     * 外部からも呼べるように public にしておく。
     * AudioSource を再生する直前に呼び出すことで、
     * 「再生のたびに違うピッチ」を実現できる。
     */
    public applyRandomPitch(): void {
        if (!this._audioSource) {
            console.warn('[RandomPitch] AudioSource が存在しないため、ピッチを適用できません。', this.node);
            return;
        }

        if (!this.enabledRandomPitch) {
            // ランダム化が無効な場合は、常に basePitch を適用しておく
            this._audioSource.pitch = this.basePitch;
            return;
        }

        // min > max のような不正値を防御的に補正
        if (this.minPitchOffset > this.maxPitchOffset) {
            console.warn('[RandomPitch] minPitchOffset が maxPitchOffset より大きいため、値を入れ替えます。', this.node);
            const tmp = this.minPitchOffset;
            this.minPitchOffset = this.maxPitchOffset;
            this.maxPitchOffset = tmp;
        }

        // minPitchOffset ~ maxPitchOffset の範囲でランダム値を生成
        const offset = math.randomRange(this.minPitchOffset, this.maxPitchOffset);
        let pitch = this.basePitch + offset;

        // ステップ刻みにスナップする場合
        if (this.useStep) {
            if (this.stepSize > 0) {
                pitch = this._snapToStep(pitch, this.stepSize);
            } else {
                console.warn('[RandomPitch] stepSize が 0 以下のため、ステップスナップを無効化します。', this.node);
            }
        }

        // 安全のため、極端な値をクランプ(0.1 ~ 3.0 の範囲に制限)
        pitch = clamp(pitch, 0.1, 3.0);

        this._audioSource.pitch = pitch;

        if (this.logAppliedPitch) {
            console.log(`[RandomPitch] 適用ピッチ: ${pitch.toFixed(3)} (base=${this.basePitch.toFixed(3)}, offset=${(pitch - this.basePitch).toFixed(3)})`, this.node.name);
        }
    }

    /**
     * 指定した値を stepSize ごとの刻みにスナップする。
     * 例: value=1.037, stepSize=0.05 → 1.05
     */
    private _snapToStep(value: number, stepSize: number): number {
        const steps = Math.round(value / stepSize);
        return steps * stepSize;
    }
}

コードの要点解説

  • onLoad()
    • 同じノードにアタッチされている AudioSourcegetComponent(AudioSource) で取得。
    • 見つからなければ console.error を出して、その後の処理では何もしない。
    • 初期状態として audioSource.pitch = basePitch に設定。
  • start()
    • applyOnStartenabledRandomPitch が true の場合のみ、applyRandomPitch() を一度呼び出す。
    • 「Play On Load」設定の AudioSource に対して、最初からランダムピッチで再生させたい場合に有効。
  • applyRandomPitch()
    • 再生のたびに呼び出される想定のメソッド。public にしているので、必要なら他スクリプトからも呼べる。
    • enabledRandomPitch が false の場合は、basePitch をそのまま適用して終了。
    • minPitchOffset > maxPitchOffset のような不正値は検出して自動で入れ替え、ログで警告。
    • math.randomRange(min, max) を使ってオフセットを生成し、basePitch + offset をピッチとする。
    • useStep が true かつ stepSize > 0 の場合は、_snapToStep() でステップ刻みに丸める。
    • 最終的なピッチを clamp(pitch, 0.1, 3.0) で安全な範囲に制限。
    • logAppliedPitch が true のときは、適用されたピッチを console.log に出力。
  • _snapToStep()
    • 指定の stepSize ごとにスナップするユーティリティ。
    • 例:value=1.037, stepSize=0.05 → 1.05 に丸める。

使用手順と動作確認

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

  1. エディタの Assets パネルで、任意のフォルダ(例:assets/scripts)を右クリックします。
  2. Create → TypeScript を選択します。
  3. 新しく作成されたスクリプトに RandomPitch.ts という名前を付けます。
  4. ダブルクリックして開き、既存のコードをすべて削除し、上記の RandomPitch のコードを丸ごと貼り付けて保存します。

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

  1. Hierarchy パネルで右クリックし、Create → Empty Node を選択して新しいノードを作成します。
    • 名前は例として SFX_Footstep などにします。
  2. 作成したノードを選択し、Inspector パネルで Add Component → Audio → AudioSource を追加します。
  3. AudioSource のプロパティで、Clip に任意の効果音(例:足音、銃声など)を設定します。
  4. テストのため、Play On Load にチェックを入れておくと、シーン再生時に自動で音が鳴ります。

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

  1. 同じノード(例:SFX_Footstep)を選択したまま、Inspector パネルで Add Component → Custom → RandomPitch を選択します。
    • Custom の中に RandomPitch が見つからない場合は、一度保存してエディタを少し待つか、再起動すると認識されます。
  2. Inspector に RandomPitch セクションが表示されていることを確認します。

4. プロパティの設定例

まずは基本的な設定値から試してみましょう。

  • Enabled Random PitchON
  • Base Pitch1.0
  • Min Pitch Offset-0.05
  • Max Pitch Offset0.05
  • Use StepOFF
  • Log Applied PitchON(挙動確認のため)
  • Apply On StartON(Play On Load を使う場合)

この設定では、0.95〜1.05 の範囲でピッチがランダムに変化します。
人間の耳にはほどよい揺れとして感じられ、同じ音でも少しずつ違う印象になります。

5. シーン再生での確認(Play On Load 利用)

  1. エディタ右上の ▶︎(Play)ボタン を押してゲームを再生します。
  2. シーン開始と同時に、AudioSource(Play On Load)で効果音が鳴ります。
  3. コンソール(Console パネル)を開き、以下のようなログが出ていることを確認します:
    [RandomPitch] 適用ピッチ: 1.032 (base=1.000, offset=0.032) SFX_Footstep
    [RandomPitch] 適用ピッチ: 0.968 (base=1.000, offset=-0.032) SFX_Footstep
    

この状態では、シーン開始時の 1 回だけランダムピッチで再生されます。
ゲーム中に何度も鳴らしたい場合は、次のような使い方が実用的です。

6. 再生のたびにランダム化する実用パターン

RandomPitch は applyRandomPitch() を public にしているので、効果音を再生する直前に毎回呼び出すことができます。

例として、別スクリプトから次のように呼び出せます(このコードは説明用であり、RandomPitch 自体は外部に依存していません):


// 例: 別スクリプトの中から
const sfxNode = this.node.getChildByName('SFX_Footstep');
if (sfxNode) {
    const audio = sfxNode.getComponent(AudioSource);
    const randomPitch = sfxNode.getComponent(RandomPitch);

    if (audio && randomPitch) {
        randomPitch.applyRandomPitch(); // 再生直前にランダムピッチを適用
        audio.play();
    }
}

このように、「鳴らす直前に applyRandomPitch() → play()」という順序にすることで、
足音・攻撃音・被弾音など、同じクリップでも毎回少し違う音として再生できます。

7. ステップ刻みで変化させる例

より「ゲームっぽい」段階的な変化にしたい場合は、ステップ機能を使います。

  • Enabled Random PitchON
  • Base Pitch1.0
  • Min Pitch Offset-0.15
  • Max Pitch Offset0.15
  • Use StepON
  • Step Size0.05

この場合、ピッチは 0.85, 0.90, 0.95, 1.00, 1.05, 1.10, 1.15 のような刻みで変化し、
カジュアルゲームやレトロ風ゲームなどで心地よい「段階感」を出すことができます。


まとめ

この記事では、Cocos Creator 3.8 / TypeScript で、

  • AudioSource にアタッチするだけで再生のたびに pitch_scale(pitch)をランダム変化させる
  • 外部スクリプトに一切依存しない独立した汎用コンポーネント「RandomPitch」

を実装しました。

このコンポーネントを導入することで、

  • 足音・銃声・ボタン音など、同じ効果音を多用しても単調になりにくい
  • インスペクタから揺れ幅やステップ刻みを調整するだけで、ゲーム全体のサウンドの「生っぽさ」を簡単に向上できる
  • プロジェクト内のどの AudioSource にも、ドラッグ&ドロップで再利用できる

といったメリットが得られます。

特に、効果音のバリエーションを大量に用意できない小規模プロジェクトでは、1 つの効果音+RandomPitch だけでかなりリッチな印象を作ることができます。
ぜひプロジェクトの共通コンポーネントとして組み込み、さまざまな AudioSource にアタッチして試してみてください。

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