【Cocos Creator 3.8】SpeedBuff の実装:アタッチするだけで「一定時間だけ移動速度を1.5倍」にできる汎用スクリプト
このガイドでは、任意の「移動用コンポーネント」の speed 変数を一定時間だけ1.5倍にする汎用コンポーネント SpeedBuff を実装します。
「プレイヤーがアイテムを取ったら数秒だけ速くなる」「スキル発動中だけダッシュ速度を上げたい」といった場面で、移動コンポーネント側を一切書き換えず、この SpeedBuff をアタッチして設定するだけで使えるようにすることが目的です。
コンポーネントの設計方針
1. 要件整理
- 対象ノードに付いている「移動系コンポーネント」の
speed変数を一定時間だけ倍率アップする。 - 時間が経過したら、必ず元の値に戻す(元値を忘れないようにキャッシュする)。
- 外部の GameManager やシングルトンに依存しない。このコンポーネント単体で完結する。
- 「どのコンポーネントの、どのプロパティを、どれくらいの倍率で、どれくらいの時間バフするか」をインスペクタから設定できるようにする。
- 防御的実装:
- 対象コンポーネントが見つからない、または対象プロパティが存在しない場合は、エラーログを出して処理を中断する。
- バフ中に対象コンポーネントが削除された場合も、エラーにならず安全に終了する。
2. 「移動系コンポーネント」の扱い方
移動ロジックはプロジェクトごとにバラバラなので、具体的なクラス名(例:PlayerMove や EnemyMover)には依存できません。
そこで:
- Component クラスをインスペクタで指定できるようにし(Cocos Creator の
@property(Component)を利用)、 - そのコンポーネントに
speedという数値プロパティがあることを前提に、anyとして扱う
という設計にします。
つまり、「speed というプロパティを持つ任意のコンポーネント」を対象にできる汎用バフコンポーネントになります。
3. インスペクタで設定可能なプロパティ設計
SpeedBuff に用意するプロパティと役割は以下の通りです。
targetComponent(Component | null)- 説明:バフ対象となる「移動系コンポーネント」の参照。
- 設定方法:インスペクタで、同じノードにアタッチされている任意のコンポーネント(例:
PlayerMover)をドラッグ&ドロップで指定。 - 役割:このコンポーネントの
speedプロパティを書き換える。
propertyName(string)- 説明:バフ対象となるプロパティ名。
- デフォルト:
"speed" - 役割:
targetComponent[propertyName]を読み書きする。 - 例:もし移動コンポーネントの変数名が
moveSpeedなら、ここを"moveSpeed"に変更することで対応可能。
multiplier(number)- 説明:バフ倍率。
- デフォルト:
1.5 - 役割:元の値にこの倍率を掛けた値を一時的に適用する。
- 例:2.0 にすれば「2倍速」、0.5 にすれば「スロー効果」にも使える。
duration(number)- 説明:バフの継続時間(秒)。
- デフォルト:
3.0秒。 - 役割:この秒数が経過したら、元の値に戻す。
autoStart(boolean)- 説明:
start()時に自動でバフを開始するかどうか。 - デフォルト:
true - 役割:
trueの場合、シーン開始と同時にバフが始まる。falseの場合は、他のスクリプトからbeginBuff()を呼んだ時だけバフが発動する。 - ※外部スクリプトから呼び出さなくても使えるように、デフォルトで
true。
- 説明:
loop(boolean)- 説明:バフ終了後に、一定時間待ってから再度自動的にバフをかけ直すかどうか。
- デフォルト:
false - 役割:
trueにすると「バフ→元に戻る→待機→再バフ…」を繰り返す。
loopInterval(number)- 説明:バフ終了から次のバフ開始までの待機時間(秒)。
- デフォルト:
1.0秒。 - 役割:
loop = trueのときのみ有効。
上記により、
- 「シーン開始から3秒だけ1.5倍速」
- 「2秒バフ → 1秒休み → 2秒バフ… の繰り返し」
といったパターンを、SpeedBuff 単体で柔軟に実現できます。
TypeScriptコードの実装
import { _decorator, Component, warn, error, director } from 'cc';
const { ccclass, property } = _decorator;
/**
* SpeedBuff
* 任意のコンポーネントの数値プロパティ(デフォルト: speed)を
* 一定時間だけ倍率アップし、時間経過後に元の値へ戻す汎用バフコンポーネント。
*/
@ccclass('SpeedBuff')
export class SpeedBuff extends Component {
@property({
type: Component,
tooltip: 'バフ対象となるコンポーネントを指定します。\n例: PlayerMover, EnemyMover など、speed 変数を持つコンポーネント。'
})
public targetComponent: Component | null = null;
@property({
tooltip: 'バフ対象となるプロパティ名です。\n通常は "speed" のままでOK。移動スクリプト側の変数名に合わせて変更してください。'
})
public propertyName: string = 'speed';
@property({
tooltip: '適用する倍率です。1.5 なら 1.5倍速、2.0 なら 2倍速になります。'
})
public multiplier: number = 1.5;
@property({
tooltip: 'バフの継続時間(秒)です。この時間が経過すると元の値に戻ります。'
})
public duration: number = 3.0;
@property({
tooltip: 'true の場合、コンポーネントの start() 時に自動でバフを開始します。'
})
public autoStart: boolean = true;
@property({
tooltip: 'true にすると、バフ終了後に一定時間待ってから自動的に再度バフをかけ直します。'
})
public loop: boolean = false;
@property({
tooltip: 'loop が true のときのみ有効です。\nバフ終了から次のバフ開始までの待機時間(秒)です。'
})
public loopInterval: number = 1.0;
// 内部状態管理用
private _originalValue: number | null = null;
private _isBuffActive: boolean = false;
private _elapsed: number = 0;
private _loopWaitElapsed: number = 0;
onLoad() {
// 防御的チェック:targetComponent が未設定なら警告を出す
if (!this.targetComponent) {
warn('[SpeedBuff] targetComponent が設定されていません。インスペクタで移動コンポーネントを指定してください。', this.node);
}
if (!this.propertyName || this.propertyName.trim().length === 0) {
warn('[SpeedBuff] propertyName が空です。通常は "speed" を指定してください。', this.node);
}
if (this.multiplier === 1.0) {
warn('[SpeedBuff] multiplier が 1.0 のため、実質的にバフ効果はありません。', this.node);
}
if (this.duration <= 0) {
warn('[SpeedBuff] duration が 0 以下です。正の値を指定してください。', this.node);
}
}
start() {
if (this.autoStart) {
this.beginBuff();
}
}
/**
* バフを開始する(外部スクリプトからも呼び出し可能)。
* すでにバフ中の場合は、一度元に戻してから再適用します。
*/
public beginBuff() {
const target = this.targetComponent as any;
if (!target) {
error('[SpeedBuff] beginBuff が呼ばれましたが、targetComponent が設定されていません。', this.node);
return;
}
if (!this.propertyName || !(this.propertyName in target)) {
error(`[SpeedBuff] 対象コンポーネントにプロパティ "${this.propertyName}" が存在しません。`, this.node);
return;
}
const currentValue = target[this.propertyName];
if (typeof currentValue !== 'number') {
error(`[SpeedBuff] プロパティ "${this.propertyName}" は数値ではありません。SpeedBuff は数値プロパティにのみ適用できます。`, this.node);
return;
}
// すでにバフ中なら一度元に戻す
if (this._isBuffActive) {
this._restoreOriginalValue();
}
this._originalValue = currentValue;
const buffedValue = this._originalValue * this.multiplier;
target[this.propertyName] = buffedValue;
this._isBuffActive = true;
this._elapsed = 0;
this._loopWaitElapsed = 0;
// 更新を有効化
this.enabled = true;
}
/**
* バフを強制終了し、元の値に戻します。
*/
public endBuff() {
this._restoreOriginalValue();
this._isBuffActive = false;
this._elapsed = 0;
this._loopWaitElapsed = 0;
// ループしない場合は update を止めておく
if (!this.loop) {
this.enabled = false;
}
}
update(deltaTime: number) {
if (!this._isBuffActive) {
// バフが終わっていて、ループ設定がある場合は待機時間をカウント
if (this.loop && this._originalValue !== null && this.duration > 0) {
this._loopWaitElapsed += deltaTime;
if (this._loopWaitElapsed >= this.loopInterval) {
this.beginBuff();
}
}
return;
}
if (this.duration <= 0) {
// duration が不正な場合は即座に元に戻す
this.endBuff();
return;
}
this._elapsed += deltaTime;
if (this._elapsed >= this.duration) {
this._restoreOriginalValue();
this._isBuffActive = false;
if (this.loop) {
// 次のループまでの待機を開始
this._loopWaitElapsed = 0;
} else {
// ループしないなら update を止める
this.enabled = false;
}
}
}
/**
* 破棄時に元の値へ戻す(シーン切り替えなどでも安全にするため)。
*/
onDestroy() {
this._restoreOriginalValue();
}
/**
* 内部用: 元の値に戻す処理。
* targetComponent がすでに破棄されている場合は何もしない。
*/
private _restoreOriginalValue() {
if (this._originalValue === null) {
return;
}
const target = this.targetComponent as any;
if (!target) {
// 対象コンポーネントがすでに破棄されている可能性があるので、ログだけ出して終了
warn('[SpeedBuff] 元の値に戻そうとしましたが、targetComponent が存在しません。', this.node);
this._originalValue = null;
return;
}
if (!(this.propertyName in target)) {
warn(`[SpeedBuff] 元の値に戻そうとしましたが、プロパティ "${this.propertyName}" が見つかりません。`, this.node);
this._originalValue = null;
return;
}
target[this.propertyName] = this._originalValue;
this._originalValue = null;
}
}
コードのポイント解説
onLoad()- インスペクタ設定の妥当性チェックを行い、問題がありそうな場合は
warn()で警告を出します。 - この段階ではまだバフ適用はしません(あくまで設定チェックのみ)。
- インスペクタ設定の妥当性チェックを行い、問題がありそうな場合は
start()autoStartがtrueの場合にbeginBuff()を呼び出し、シーン開始と同時にバフをかけます。
beginBuff()- 対象コンポーネントとプロパティの存在、型(数値かどうか)をチェック。
- すでにバフ中の場合は、一度元の値に戻してから再度バフを適用。
_originalValueに元の値を保存し、multiplierを掛けた値をセット。_isBuffActiveをtrueにし、update()で経過時間を監視できるようにします。
update(deltaTime)- バフ中(
_isBuffActive === true)なら、_elapsedに経過時間を加算し、durationを超えたら元の値に戻します。 - ループ設定(
loop === true)のときは、バフ終了後にloopInterval秒待ってから再度beginBuff()を呼び出します。 - ループしない場合は、バフ終了後に
this.enabled = falseにして無駄なupdate()呼び出しを止めます。
- バフ中(
onDestroy()- ノードやコンポーネントが破棄される際に、バフがかかったままにならないよう、
_restoreOriginalValue()を呼び出して元の値を戻します。
- ノードやコンポーネントが破棄される際に、バフがかかったままにならないよう、
_restoreOriginalValue()- 内部専用メソッド。
_originalValueがnullでないときだけ処理します。 - 対象コンポーネントやプロパティがすでに無い場合は、エラーではなく警告を出して安全に終了します。
- 内部専用メソッド。
使用手順と動作確認
1. スクリプトファイルの作成
- エディタ下部の Assets パネルで、スクリプトを置きたいフォルダ(例:
assets/scripts)を選択します。 - フォルダ上で右クリック → Create → TypeScript を選択します。
- 新規作成されたスクリプトの名前を
SpeedBuff.tsに変更します。 SpeedBuff.tsをダブルクリックして開き、既存のコードをすべて削除して、前章の TypeScript コードを丸ごと貼り付けて保存します。
2. テスト用の「移動コンポーネント」を用意する
SpeedBuff は「speed という数値プロパティを持つコンポーネント」に対して作用します。ここでは動作確認用に、非常にシンプルな移動スクリプトを作ります。
- 同じく Assets パネルで右クリック → Create → TypeScript を選択し、
SimpleMover.tsという名前のスクリプトを作成します。 - 以下のコードを
SimpleMover.tsに貼り付けます(このスクリプトは SpeedBuff から参照されますが、SpeedBuff 自体はこれに依存していません)。
import { _decorator, Component, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
/**
* SimpleMover
* X 方向に一定速度で移動するだけのテスト用コンポーネント。
* SpeedBuff からは speed プロパティが参照されます。
*/
@ccclass('SimpleMover')
export class SimpleMover extends Component {
@property({
tooltip: '1秒あたりの移動速度(単位: ユニット/秒)'
})
public speed: number = 2.0;
update(deltaTime: number) {
const pos = this.node.position;
const newPos = new Vec3(
pos.x + this.speed * deltaTime,
pos.y,
pos.z
);
this.node.setPosition(newPos);
}
}
このコンポーネントは、右方向に一定速度で移動するだけの簡単なものです。speed を変更すると移動速度が変わるので、SpeedBuff の効果を確認しやすくなります。
3. シーンにノードを配置してコンポーネントをアタッチ
- Hierarchy パネルで右クリック → Create → 3D Object → Cube(または 2D なら Create → UI → Sprite)を選択し、テスト用ノードを作成します。
- ここでは例として Node 名を
TestPlayerとします。
- ここでは例として Node 名を
- TestPlayer ノードを選択し、右側の Inspector パネルで Add Component ボタンをクリックします。
- 検索欄に
SimpleMoverと入力し、表示された SimpleMover を選択して追加します。 - 同様に、再度 Add Component → 検索欄に
SpeedBuffと入力し、SpeedBuff を追加します。
4. SpeedBuff のプロパティを設定する
TestPlayer ノードを選択した状態で、Inspector の SpeedBuff セクションを確認し、以下のように設定します。
- Target Component:
- 右側の小さな丸いアイコン(オブジェクトフィールド)をクリックし、一覧から SimpleMover を選択するか、
- Hierarchy から TestPlayer ノードを展開し、その中の SimpleMover コンポーネントをドラッグ&ドロップしてフィールドに入れます。
- Property Name:
speed(デフォルトのままでOK) - Multiplier:
1.5(1.5倍速にしたい場合) - Duration:
3.0(3秒間だけバフ) - Auto Start:チェック ON(シーン開始と同時にバフ開始)
- Loop:チェック OFF(まずは1回だけのバフで確認)
- Loop Interval:任意(Loop が OFF なら無視されます)
5. 動作確認(プレビュー)
- エディタ上部の Play ボタン(▶)をクリックしてプレビューを開始します。
- ゲームビューで TestPlayer ノードが右方向に移動する様子を観察します。
- 開始直後から 3秒間は、
SimpleMover.speedに対して1.5倍が適用されているため、通常より速く動きます。 - 3秒を過ぎると、SpeedBuff が元の
speed値を復元し、移動速度が元に戻るのを確認できます。
6. ループバフの確認
次に、バフが一定間隔で繰り返し発生する設定を試してみます。
- プレビューを停止します。
- TestPlayer の SpeedBuff 設定を以下のように変更します。
- Multiplier:
2.0(2倍速) - Duration:
1.0秒 - Loop:チェック ON
- Loop Interval:
1.0秒
- Multiplier:
- 再度プレビューを開始します。
- 移動速度が
- 1秒間だけ 2倍速 → 1秒間 通常速度 → 1秒間 2倍速 → …
のように 周期的に変化するのを確認できます。
7. 別のプロパティ名でも試してみる
実際のプロジェクトでは、移動速度の変数名が moveSpeed や runSpeed のように speed 以外であることも多いです。そういった場合も SpeedBuff はそのまま使えます。
SimpleMoverのspeedプロパティ名をmoveSpeedに変更したと仮定します。- SpeedBuff の Property Name を
moveSpeedに変更します。 - その他の設定はそのままでプレビューすると、同様にバフが適用されることが確認できます。
このように、SpeedBuff は「プロパティ名の文字列」と「コンポーネント参照」を変えるだけで、さまざまな移動系スクリプトに再利用できます。
まとめ
このガイドでは、Cocos Creator 3.8 / TypeScript 向けに、
- 任意のコンポーネントの数値プロパティ(デフォルト:
speed)を、 - 一定時間だけ倍率アップし、
- 時間経過後に確実に元の値へ戻す
という汎用バフコンポーネント SpeedBuff を実装しました。
特徴を整理すると:
- 完全に独立したコンポーネント:
- GameManager などの外部スクリプトに依存せず、SpeedBuff.ts 単体で完結。
- 必要な設定はすべてインスペクタのプロパティから行える設計。
- 高い再利用性:
- 「どのコンポーネントの」「どのプロパティ」を「どの倍率で」「どのくらいの時間」バフするかを柔軟に指定可能。
- プロパティ名を変えるだけで、
speed以外のmoveSpeedやrunSpeedにも適用できる。
- 防御的な実装:
- 対象コンポーネント未設定やプロパティ名のミスなどをエラーログで検出。
- ノード破棄時にも元の値を復元し、バフの「かけっぱなし」を防止。
- ゲーム開発の効率化:
- 既存の移動スクリプトを一切変更せず、「SpeedBuff を追加して設定するだけ」で速度バフ機能を付与できる。
- プレイヤー、敵、ギミックなど、さまざまなオブジェクトに同じロジックを共有できるため、実装と保守のコストが大幅に下がる。
このパターンは「攻撃力バフ」「防御力バフ」「クールタイム短縮」など、任意の数値プロパティに対する一時的な変更にもそのまま応用できます。SpeedBuff をベースに、プロジェクト固有のバフ/デバフコンポーネントを増やしていくことで、よりモジュール化された拡張しやすいゲームアーキテクチャを構築できるはずです。




