【Cocos Creator 3.8】SlowMotion の実装:アタッチするだけでゲーム全体をスロー化・復帰できる汎用タイムスケール制御スクリプト
このガイドでは、Engine.timeScale を操作して、ゲーム全体の時間の流れを一時的にスローにしたり、元に戻したりできる汎用コンポーネント SlowMotion を実装します。
任意のノードにアタッチするだけで、キー入力・自動発火・外部トリガー呼び出しの3パターンでスロー演出を簡単に仕込めるようにします。
コンポーネントの設計方針
1. 機能要件の整理
- Engine.timeScale を変更して、ゲーム全体の更新速度をスロー化/通常速度へ戻す。
- スロー化の目標 timeScale 値と、元の timeScaleを自動で記憶・復帰する。
- スロー状態を一定時間だけ維持し、自動的に元に戻す機能を持たせる。
- スロー化・復帰時に、補間(フェード)しながら滑らかに timeScale を変化させられるようにする。
- 外部依存なし:GameManager や他のカスタムスクリプトに依存せず、このコンポーネント単体で完結する。
- 必要な設定はすべて Inspector の @property で調整できるようにする。
2. トリガー方法の設計
SlowMotion を「どうやって発動させるか」を 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 のKeyCodeenum の値を指定(例: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を更新しています。
- 他のスクリプトが onLoad 中に timeScale を変更したケースを想定し、
- 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. スクリプトファイルの作成
- エディタ上部メニュー、または Assets パネルで操作します。
- Assets パネルで右クリック → Create → TypeScript を選択。
- ファイル名を SlowMotion.ts に変更します。
- 作成した
SlowMotion.tsをダブルクリックして開き、
中身をすべて削除して、前述の TypeScript コードを丸ごと貼り付けて保存します。
2. テスト用ノードの作成
- Hierarchy パネルで右クリック → Create → 2D Object → Sprite など、適当なノードを作成します。
(3D ゲームなら Cube など、何でも構いません。SlowMotion はノードの種類に依存しません) - 作成したノードの名前を分かりやすく SlowMotionTester などに変更しておくと管理しやすくなります。
3. コンポーネントのアタッチ
- Hierarchy で SlowMotionTester ノードを選択します。
- Inspector の下部にある Add Component ボタンをクリックします。
- Custom → SlowMotion を選択してアタッチします。
4. Inspector プロパティの設定例
まずは分かりやすい設定値で動作確認してみましょう。
- 基本設定
Enabled On Start: チェック ONSlow Time Scale: 0.2(20% 速度)Slow Duration: 0.5(0.5 秒間スロー)Use Unscaled Time: チェック ON
- 補間設定
Enable Smooth Transition: チェック ONSlow In Duration: 0.1Slow Out Duration: 0.2
- キー入力トリガー設定
Enable Key Trigger: チェック ONToggle Mode: チェック ONTrigger Key Code: 32(SPACE キー。KeyCode.SPACE の値)
- 自動トリガー設定(任意)
Enable Auto Trigger: 必要なら ONAuto Trigger Delay: 例として 1.0(シーン開始から 1 秒後に自動スロー)
5. 実行して動作を確認する
- エディタ右上の ▶(Play) ボタンを押してゲームを実行します。
- ゲーム画面が表示されたら、SPACE キー を押してみてください。
- 押した瞬間に、ゲーム全体の動きが 0.2 倍速 にスロー化されます。
Slow Duration = 0.5のため、約 0.5 秒後に自動で元の速度へ復帰します。Toggle Modeを ON にしているので、
スロー中にもう一度 SPACE を押すと、その時点で復帰処理(restoreSlow())が走ります。
- 補間の確認
Enable Smooth Transitionを ON にしている場合、
通常 → スロー(0.1 秒)、スロー → 通常(0.2 秒)で徐々に時間の流れが変化するのが分かります。- 即時切り替えを試したい場合は、
Enable Smooth Transitionを OFF にするか、
Slow In Duration/Slow Out Durationを 0 に設定してみてください。
- 自動トリガーの確認(任意)
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 をそのままプロジェクトに組み込み、
攻撃ヒット時やダメージ時、必殺技発動時などにスロー演出を仕込んで、ゲームの「手触り」を強化してみてください。




