【Cocos Creator】アタッチするだけ!SlowMotion (スロー化)の実装方法【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】SlowMotion の実装:アタッチするだけでゲーム全体をスロー化・復帰できる汎用タイムスケール制御スクリプト

このガイドでは、Engine.timeScale を操作して、ゲーム全体の時間の流れを一時的にスローにしたり、元に戻したりできる汎用コンポーネント SlowMotion を実装します。
任意のノードにアタッチするだけで、キー入力・自動発火・外部トリガー呼び出しの3パターンでスロー演出を簡単に仕込めるようにします。


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

1. 機能要件の整理

  • Engine.timeScale を変更して、ゲーム全体の更新速度をスロー化/通常速度へ戻す。
  • スロー化の目標 timeScale 値と、元の timeScaleを自動で記憶・復帰する。
  • スロー状態を一定時間だけ維持し、自動的に元に戻す機能を持たせる。
  • スロー化・復帰時に、補間(フェード)しながら滑らかに timeScale を変化させられるようにする。
  • 外部依存なし:GameManager や他のカスタムスクリプトに依存せず、このコンポーネント単体で完結する。
  • 必要な設定はすべて Inspector の @property で調整できるようにする。

2. トリガー方法の設計

SlowMotion を「どうやって発動させるか」を 3 パターン用意します。

  1. キー入力トリガー
    指定したキー(例: スペースキー)を押したときにスロー化/復帰を行う。
  2. 自動トリガー
    シーン開始から指定時間後に一度だけ自動でスロー演出を行う。
  3. 外部スクリプトからの呼び出し
    SlowMotion コンポーネントの triggerSlow() / restoreNow() を他のスクリプトから呼べるようにする。
    ※このコンポーネント自体は他のスクリプトに依存しませんが、「呼び出される側」としての API を用意します。

3. Inspector で設定可能なプロパティ設計

以下のようなプロパティを用意します。

  • スロー関連
    • enabledOnStart: boolean
      コンポーネントを有効にしておくかどうか。false の場合、スクリプトは何もしない(API 呼び出しは可能)。
    • slowTimeScale: number
      スロー時の Engine.timeScale。例: 0.2 なら 20% の速度。
    • slowDuration: number
      スロー状態を維持する時間(秒)。0 以下の場合、「自動復帰なし(手動で復帰)」とする。
    • useUnscaledTime: boolean
      スロー継続時間や補間に 実時間(unscaledDeltaTime) を使うかどうか。
      true にすると、スロー中でも「演出時間」が正確に保たれる。
  • 補間(フェード)関連
    • enableSmoothTransition: boolean
      スロー化/復帰時に timeScale を徐々に変化させるかどうか。
    • slowInDuration: number
      通常 → スロー へ移行するのにかける時間(秒)。
    • slowOutDuration: number
      スロー → 通常 へ戻るのにかける時間(秒)。
  • キー入力トリガー関連
    • enableKeyTrigger: boolean
      キー入力でスローを発動できるようにするか。
    • toggleMode: boolean
      true: キーを押すたびに「スロー開始/復帰」をトグルする。
      false: キーを押すたびに「スロー開始のみ」。復帰は自動 or API。
    • triggerKeyCode: number
      使用するキーコード。Cocos の KeyCode enum の値を指定(例: KeyCode.SPACE)。
      Inspector では数値として設定します。
  • 自動トリガー関連
    • enableAutoTrigger: boolean
      シーン開始から一定時間後に自動でスローを発動するか。
    • autoTriggerDelay: number
      シーン開始からスロー発動までの遅延時間(秒)。

内部状態(@property にはしない)

  • 元の timeScale の保存用: _originalTimeScale: number
  • 現在の目標 timeScale: _targetTimeScale: number
  • 現在の補間時間管理: _transitionElapsed: number, _transitionDuration: number
  • スロー中かどうか: _isInSlowMotion: boolean
  • スロー継続時間カウンタ: _slowElapsed: number
  • 自動トリガー用カウンタ: _autoTriggerElapsed: number

TypeScriptコードの実装


import { _decorator, Component, game, KeyCode, input, Input, EventKeyboard } from 'cc';
const { ccclass, property } = _decorator;

/**
 * SlowMotion
 * Engine.timeScale を制御してゲーム全体をスロー化/復帰する汎用コンポーネント
 */
@ccclass('SlowMotion')
export class SlowMotion extends Component {

    // === 基本設定 ===
    @property({
        tooltip: 'コンポーネントを有効にするかどうか。false の場合、キー入力や自動トリガーは無効になりますが、スクリプトからの API 呼び出しは可能です。'
    })
    public enabledOnStart: boolean = true;

    @property({
        tooltip: 'スロー時の Engine.timeScale 値。0 < 値 < 1 推奨(例: 0.2 で 20% 速度)。'
    })
    public slowTimeScale: number = 0.2;

    @property({
        tooltip: 'スロー状態を維持する時間(秒)。0 以下の場合は自動復帰しません(手動で restoreNow() を呼び出す必要があります)。'
    })
    public slowDuration: number = 0.5;

    @property({
        tooltip: 'スロー継続時間や補間に unscaledDeltaTime(スローの影響を受けない実時間)を使用するかどうか。true 推奨。'
    })
    public useUnscaledTime: boolean = true;

    // === 補間(フェード)設定 ===
    @property({
        tooltip: 'スロー化/復帰時に Engine.timeScale を徐々に変化させるかどうか。'
    })
    public enableSmoothTransition: boolean = true;

    @property({
        tooltip: '通常速度 → スロー速度 への移行にかける時間(秒)。0 の場合は即時に切り替わります。'
    })
    public slowInDuration: number = 0.1;

    @property({
        tooltip: 'スロー速度 → 通常速度 への復帰にかける時間(秒)。0 の場合は即時に切り替わります。'
    })
    public slowOutDuration: number = 0.1;

    // === キー入力トリガー設定 ===
    @property({
        tooltip: 'キー入力でスローを発動できるようにするかどうか。'
    })
    public enableKeyTrigger: boolean = true;

    @property({
        tooltip: 'true: キーを押すたびにスロー開始/復帰をトグルします。\nfalse: キーを押すたびにスロー開始のみ行います(復帰は自動または restoreNow() で)。'
    })
    public toggleMode: boolean = true;

    @property({
        tooltip: 'スローをトリガーするキーコード。KeyCode.SPACE = 32 など、KeyCode 列挙体の値を設定してください。'
    })
    public triggerKeyCode: number = KeyCode.SPACE;

    // === 自動トリガー設定 ===
    @property({
        tooltip: 'シーン開始から一定時間後に自動でスローを発動するかどうか。'
    })
    public enableAutoTrigger: boolean = false;

    @property({
        tooltip: 'シーン開始からスロー発動までの遅延時間(秒)。'
    })
    public autoTriggerDelay: number = 1.0;

    // === 内部状態 ===
    private _originalTimeScale: number = 1.0;
    private _targetTimeScale: number = 1.0;
    private _transitionElapsed: number = 0;
    private _transitionDuration: number = 0;
    private _isInSlowMotion: boolean = false;
    private _slowElapsed: number = 0;
    private _autoTriggerElapsed: number = 0;
    private _autoTriggered: boolean = false;

    onLoad() {
        // 現在の timeScale を保存
        this._originalTimeScale = game.timeScale;
        this._targetTimeScale = game.timeScale;

        if (!this.enabledOnStart) {
            // 無効開始の場合でも、API は使用可能
            return;
        }

        // キー入力イベント登録
        if (this.enableKeyTrigger) {
            input.on(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        }
    }

    onDestroy() {
        // 入力イベントの解除
        if (this.enableKeyTrigger) {
            input.off(Input.EventType.KEY_DOWN, this._onKeyDown, this);
        }
    }

    start() {
        // start 時点での timeScale を再確認(他の処理で変えられている可能性に備える)
        this._originalTimeScale = game.timeScale;
        this._targetTimeScale = game.timeScale;
    }

    update(deltaTime: number) {
        // コンポーネントが無効なら何もしない(ただし、API 経由の強制操作は可能)
        if (!this.enabledOnStart) {
            return;
        }

        const dt = this.useUnscaledTime ? game.unscaledDeltaTime : deltaTime;

        // 自動トリガー処理
        if (this.enableAutoTrigger && !this._autoTriggered) {
            this._autoTriggerElapsed += dt;
            if (this._autoTriggerElapsed >= this.autoTriggerDelay) {
                this._autoTriggered = true;
                this.triggerSlow();
            }
        }

        // スロー継続時間の管理
        if (this._isInSlowMotion && this.slowDuration > 0) {
            this._slowElapsed += dt;
            if (this._slowElapsed >= this.slowDuration) {
                // 規定時間が経過したら自動で復帰
                this.restoreSlow();
            }
        }

        // timeScale の補間処理
        this._updateTimeScaleTransition(dt);
    }

    /**
     * timeScale の補間更新処理
     */
    private _updateTimeScaleTransition(dt: number) {
        if (this._transitionDuration <= 0) {
            // 補間なし
            game.timeScale = this._targetTimeScale;
            return;
        }

        if (this._transitionElapsed < this._transitionDuration) {
            this._transitionElapsed += dt;
            let t = this._transitionElapsed / this._transitionDuration;
            if (t > 1) t = 1;

            const start = game.timeScale;
            const end = this._targetTimeScale;

            // 線形補間(Lerp)
            const newScale = start + (end - start) * t;
            game.timeScale = newScale;
        } else {
            // 補間完了
            game.timeScale = this._targetTimeScale;
            this._transitionDuration = 0;
            this._transitionElapsed = 0;
        }
    }

    /**
     * キー入力イベントハンドラ
     */
    private _onKeyDown(event: EventKeyboard) {
        if (!this.enableKeyTrigger) {
            return;
        }

        if (event.keyCode === this.triggerKeyCode) {
            if (this.toggleMode) {
                // トグルモード:スロー中なら復帰、そうでなければスロー開始
                if (this._isInSlowMotion) {
                    this.restoreSlow();
                } else {
                    this.triggerSlow();
                }
            } else {
                // 常にスロー開始
                this.triggerSlow();
            }
        }
    }

    /**
     * スローを開始する(外部からも呼び出し可能な API)
     * - slowDuration > 0 の場合、指定時間後に自動で復帰します。
     * - slowDuration <= 0 の場合、自動復帰せず、restoreSlow() または restoreNow() を呼び出す必要があります。
     */
    public triggerSlow() {
        // すでにスロー中の場合は、継続時間をリセットして延長する挙動にする
        if (this._isInSlowMotion) {
            this._slowElapsed = 0;
            return;
        }

        this._isInSlowMotion = true;
        this._slowElapsed = 0;

        // スロー前の timeScale を保存
        this._originalTimeScale = game.timeScale;

        // 目標 timeScale をスロー値に
        this._targetTimeScale = this.slowTimeScale;

        if (this.enableSmoothTransition && this.slowInDuration > 0) {
            this._transitionDuration = this.slowInDuration;
            this._transitionElapsed = 0;
        } else {
            this._transitionDuration = 0;
            this._transitionElapsed = 0;
            game.timeScale = this._targetTimeScale;
        }
    }

    /**
     * スローから元の timeScale に戻す(外部からも呼び出し可能な API)
     * - 補間設定に従って徐々に戻します。
     */
    public restoreSlow() {
        if (!this._isInSlowMotion) {
            return;
        }

        this._isInSlowMotion = false;
        this._slowElapsed = 0;

        this._targetTimeScale = this._originalTimeScale;

        if (this.enableSmoothTransition && this.slowOutDuration > 0) {
            this._transitionDuration = this.slowOutDuration;
            this._transitionElapsed = 0;
        } else {
            this._transitionDuration = 0;
            this._transitionElapsed = 0;
            game.timeScale = this._targetTimeScale;
        }
    }

    /**
     * 即座に timeScale を通常値(_originalTimeScale)に戻し、
     * 補間やスロー状態をすべてリセットします。
     */
    public restoreNow() {
        this._isInSlowMotion = false;
        this._slowElapsed = 0;
        this._transitionDuration = 0;
        this._transitionElapsed = 0;
        this._targetTimeScale = this._originalTimeScale;
        game.timeScale = this._originalTimeScale;
    }

    /**
     * 現在スロー中かどうかを取得
     */
    public isInSlowMotion(): boolean {
        return this._isInSlowMotion;
    }
}

コードの要点解説

  • onLoad
    • 起動時の game.timeScale_originalTimeScale に保存します。
    • enabledOnStart が true かつ enableKeyTrigger が true の場合のみ、キー入力イベントを登録します。
  • start
    • 他のスクリプトが onLoad 中に timeScale を変更したケースを想定し、start() 時点でもう一度 _originalTimeScale を更新しています。
  • update
    • useUnscaledTime が true の場合は game.unscaledDeltaTime を使用し、スロー中でも「演出時間」が正確に進むようにしています。
    • 自動トリガー(enableAutoTrigger)が有効な場合、autoTriggerDelay 経過後に一度だけ triggerSlow() を呼びます。
    • スロー中かつ slowDuration > 0 のとき、経過時間を計測し、指定時間を過ぎたら restoreSlow() で復帰します。
    • _updateTimeScaleTransition() で timeScale の補間(フェード)を行います。
  • _updateTimeScaleTransition
    • _transitionDuration > 0 のときのみ補間を行い、現在の game.timeScale から _targetTimeScale へ線形補間(Lerp)します。
    • 補間が終わったら _transitionDuration を 0 にして、以降は直接 _targetTimeScale を適用します。
  • triggerSlow / restoreSlow / restoreNow
    • triggerSlow() はスロー開始 API で、既にスロー中であれば継続時間をリセットして「延長」する動きにしています。
    • restoreSlow() はスローから元の timeScale に戻す API で、補間設定に従って滑らかに戻します。
    • restoreNow() は補間なしで即座に元の timeScale に戻し、内部状態もリセットします。
  • _onKeyDown
    • triggerKeyCode と一致するキーが押されたときに、トグルモードかどうかで triggerSlow() / restoreSlow() を呼び分けます。

使用手順と動作確認

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

  1. エディタ上部メニュー、または Assets パネルで操作します。
    • Assets パネルで右クリック → Create → TypeScript を選択。
    • ファイル名を SlowMotion.ts に変更します。
  2. 作成した SlowMotion.ts をダブルクリックして開き、
    中身をすべて削除して、前述の TypeScript コードを丸ごと貼り付けて保存します。

2. テスト用ノードの作成

  1. Hierarchy パネルで右クリック → Create → 2D Object → Sprite など、適当なノードを作成します。
    (3D ゲームなら Cube など、何でも構いません。SlowMotion はノードの種類に依存しません)
  2. 作成したノードの名前を分かりやすく SlowMotionTester などに変更しておくと管理しやすくなります。

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

  1. Hierarchy で SlowMotionTester ノードを選択します。
  2. Inspector の下部にある Add Component ボタンをクリックします。
  3. Custom → SlowMotion を選択してアタッチします。

4. Inspector プロパティの設定例

まずは分かりやすい設定値で動作確認してみましょう。

  1. 基本設定
    • Enabled On Start: チェック ON
    • Slow Time Scale: 0.2(20% 速度)
    • Slow Duration: 0.5(0.5 秒間スロー)
    • Use Unscaled Time: チェック ON
  2. 補間設定
    • Enable Smooth Transition: チェック ON
    • Slow In Duration: 0.1
    • Slow Out Duration: 0.2
  3. キー入力トリガー設定
    • Enable Key Trigger: チェック ON
    • Toggle Mode: チェック ON
    • Trigger Key Code: 32(SPACE キー。KeyCode.SPACE の値)
  4. 自動トリガー設定(任意)
    • Enable Auto Trigger: 必要なら ON
    • Auto Trigger Delay: 例として 1.0(シーン開始から 1 秒後に自動スロー)

5. 実行して動作を確認する

  1. エディタ右上の ▶(Play) ボタンを押してゲームを実行します。
  2. ゲーム画面が表示されたら、SPACE キー を押してみてください。
    • 押した瞬間に、ゲーム全体の動きが 0.2 倍速 にスロー化されます。
    • Slow Duration = 0.5 のため、約 0.5 秒後に自動で元の速度へ復帰します。
    • Toggle Mode を ON にしているので、
      スロー中にもう一度 SPACE を押すと、その時点で復帰処理(restoreSlow())が走ります。
  3. 補間の確認
    • Enable Smooth Transition を ON にしている場合、
      通常 → スロー(0.1 秒)、スロー → 通常(0.2 秒)で徐々に時間の流れが変化するのが分かります。
    • 即時切り替えを試したい場合は、Enable Smooth Transition を OFF にするか、
      Slow In Duration / Slow Out Duration を 0 に設定してみてください。
  4. 自動トリガーの確認(任意)
    • Enable Auto Trigger を ON、Auto Trigger Delay を 1.0 にしておくと、
      シーン開始から 1 秒後に自動でスロー演出が一度だけ発動します。

6. 他スクリプトからの呼び出し例(任意)

SlowMotion 自体は外部に依存しませんが、他のスクリプトから API を呼びたい場合の例を示します。


// 任意のコンポーネント側の例
import { _decorator, Component, Node } from 'cc';
import { SlowMotion } from './SlowMotion';
const { ccclass, property } = _decorator;

@ccclass('ExampleCaller')
export class ExampleCaller extends Component {

    @property({ type: SlowMotion, tooltip: '同じノード、または任意のノードにアタッチされた SlowMotion を指定します。' })
    public slowMotion: SlowMotion | null = null;

    onSomeEvent() {
        if (this.slowMotion) {
            this.slowMotion.triggerSlow();
        }
    }
}

このように、インスペクタで slowMotion に SlowMotion コンポーネントをドラッグ&ドロップしておけば、任意のタイミングでスロー演出を発火できます。


まとめ

  • SlowMotion コンポーネントは、Engine.timeScale を安全かつ柔軟に操作するための汎用スクリプトです。
  • キー入力トリガー、自動トリガー、外部 API 呼び出しの 3 パターンでスロー演出を簡単に仕込めます。
  • スロー中の継続時間や、スロー/復帰時の補間時間を Inspector から調整できるため、
    「攻撃ヒット時だけ 0.1 秒スロー」「必殺技発動時に 0.3 秒かけてじわっとスローにする」など、演出のチューニングがしやすくなります。
  • 外部の GameManager やシングルトンに依存せず、このコンポーネント単体で完結しているため、
    プロジェクト間の コピペ再利用 も容易です。任意のノードにアタッチするだけで、すぐに「スロー演出」を導入できます。

このままでも十分実用的ですが、必要に応じて以下のような拡張も検討できます。

  • スロー開始/終了時に コールバックイベント を飛ばす。
  • 複数のスロー演出が同時に発生したときの 優先度管理(今回はシンプルさのため未実装)。
  • timeScale の最小値・最大値を制限する セーフガード

まずは本記事の SlowMotion をそのままプロジェクトに組み込み、
攻撃ヒット時やダメージ時、必殺技発動時などにスロー演出を仕込んで、ゲームの「手触り」を強化してみてください。

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