【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: numberuseStepが true のときにのみ使用。- ピッチの刻み幅。例:0.05 にすると 0.95, 1.00, 1.05, 1.10… のように変化。
- 0 以下の値は無効として扱い、ログを出して無視する。
logAppliedPitch: boolean- デバッグ用。適用されたピッチを
console.logに出すかどうか。 - 開発中に挙動を確認するときに便利。リリース時は OFF 推奨。
- デバッグ用。適用されたピッチを
applyOnStart: booleanstart()時に一度ピッチをランダム適用しておくかどうか。- 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()
- 同じノードにアタッチされている
AudioSourceをgetComponent(AudioSource)で取得。 - 見つからなければ
console.errorを出して、その後の処理では何もしない。 - 初期状態として
audioSource.pitch = basePitchに設定。
- 同じノードにアタッチされている
- start()
applyOnStartとenabledRandomPitchが 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. スクリプトファイルの作成
- エディタの Assets パネルで、任意のフォルダ(例:
assets/scripts)を右クリックします。 - Create → TypeScript を選択します。
- 新しく作成されたスクリプトに
RandomPitch.tsという名前を付けます。 - ダブルクリックして開き、既存のコードをすべて削除し、上記の
RandomPitchのコードを丸ごと貼り付けて保存します。
2. テスト用ノードと AudioSource の用意
- Hierarchy パネルで右クリックし、Create → Empty Node を選択して新しいノードを作成します。
- 名前は例として
SFX_Footstepなどにします。
- 名前は例として
- 作成したノードを選択し、Inspector パネルで Add Component → Audio → AudioSource を追加します。
- AudioSource のプロパティで、Clip に任意の効果音(例:足音、銃声など)を設定します。
- テストのため、Play On Load にチェックを入れておくと、シーン再生時に自動で音が鳴ります。
3. RandomPitch コンポーネントのアタッチ
- 同じノード(例:
SFX_Footstep)を選択したまま、Inspector パネルで Add Component → Custom → RandomPitch を選択します。Customの中にRandomPitchが見つからない場合は、一度保存してエディタを少し待つか、再起動すると認識されます。
- Inspector に RandomPitch セクションが表示されていることを確認します。
4. プロパティの設定例
まずは基本的な設定値から試してみましょう。
Enabled Random Pitch:ONBase Pitch:1.0Min Pitch Offset:-0.05Max Pitch Offset:0.05Use Step:OFFLog Applied Pitch:ON(挙動確認のため)Apply On Start:ON(Play On Load を使う場合)
この設定では、0.95〜1.05 の範囲でピッチがランダムに変化します。
人間の耳にはほどよい揺れとして感じられ、同じ音でも少しずつ違う印象になります。
5. シーン再生での確認(Play On Load 利用)
- エディタ右上の ▶︎(Play)ボタン を押してゲームを再生します。
- シーン開始と同時に、AudioSource(Play On Load)で効果音が鳴ります。
- コンソール(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 Pitch:ONBase Pitch:1.0Min Pitch Offset:-0.15Max Pitch Offset:0.15Use Step:ONStep Size:0.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 にアタッチして試してみてください。




