【Cocos Creator 3.8】ScreenFaderの実装:アタッチするだけでシーン遷移時の暗転フェードイン・アウトを実現する汎用スクリプト
このガイドでは、黒い矩形(UIノード)の透明度を自動制御して、シーンのフェードイン / フェードアウト演出を行う「ScreenFader」コンポーネントを実装します。
Canvas 配下の任意の UI ノードにこのコンポーネントをアタッチし、インスペクタでパラメータを設定するだけで、開始時のフェードインや、任意タイミングのフェードアウトを簡単に扱えるようになります。外部の GameManager などには一切依存せず、このスクリプト単体で完結します。
コンポーネントの設計方針
実現したい機能
- UI ノード(
UIOpacityで透明度制御可能なノード)にアタッチするだけで使える。 - ゲーム開始時に、黒画面から徐々に明るくなるフェードインを自動で行える。
- スクリプトから呼び出すことで、現在の画面を黒くフェードアウトできる。
- フェード時間・遅延時間・自動再生の有無などをインスペクタから調整可能。
- フェード中は、オプションでタッチ入力をブロックできる(UIブロッカーとしても利用可能)。
- 他のスクリプトやシングルトンに依存しない。
前提と防御的設計
フェード演出は UIOpacity コンポーネントで実装します。
ScreenFader は、アタッチされたノードに UIOpacity がある前提で動作しますが、存在しない場合は自動追加を試み、取得できなければエラーログを出します。
UIOpacityがない場合:addComponent(UIOpacity)で自動追加。- それでも取得できなければ:
console.errorでエラーを出し、フェード処理は無効化。
また、フェード中にユーザー入力を抑止したい場合のために、BlockInputEvents コンポーネントをオプションで利用できるようにします。これも自動追加・削除を行います。
インスペクタで設定可能なプロパティ
ScreenFader コンポーネントに用意する @property は次の通りです。
- autoFadeInOnStart: boolean
– 説明:start()実行時に自動でフェードインを行うかどうか。
– 用途: シーン開始時に暗転から明るくしたい場合はtrueに設定。
– 例: タイトルシーンやゲームシーンの開始時に自然な導入を行う。 - initialAlpha: number (0〜255)
– 説明: 開始時のアルファ値(透明度)。0=完全透明, 255=完全不透明。
– 用途:autoFadeInOnStartが true のとき、ここで指定した値から 0 までフェードインする。
– 例: 暗転スタートなら 255、半透明スタートなら 128 など。 - fadeInDuration: number (秒)
– 説明: フェードインにかける時間(秒)。
– 用途: ゆっくり明るくしたい場合は 1.0〜2.0 秒程度、素早くなら 0.3〜0.5 秒程度。 - fadeOutDuration: number (秒)
– 説明: フェードアウトにかける時間(秒)。
– 用途: シーン遷移前の暗転時間を調整する。 - fadeInDelay: number (秒)
– 説明: フェードイン開始までの待機時間(秒)。
– 用途: シーンロード後、少しだけ黒画面を維持したい場合に使用。
– 例: ロゴ表示などで 0.5〜1.0 秒待ってからフェードイン。 - fadeOutDelay: number (秒)
– 説明: フェードアウト開始までの待機時間(秒)。
– 用途: フェードアウト演出を開始する前に少しだけ演出を続けたい場合など。 - blockInputDuringFade: boolean
– 説明: フェード中にタッチやクリックなどの UI 入力をブロックするかどうか。
– 用途: シーン遷移中にボタンが押されないようにする。 - deactivateOnFadeInComplete: boolean
– 説明: フェードイン完了時に、このノードをnode.active = falseにするかどうか。
– 用途: フェードイン終了後にフェード用ノードを非表示にしておきたい場合。 - destroyOnFadeOutComplete: boolean
– 説明: フェードアウト完了時に、このノードをdestroy()するかどうか。
– 用途: シーン遷移時に不要になるフェードノードを自動的に破棄したい場合。
また、スクリプトから呼び出せる 公開メソッド を用意します。
fadeIn(duration?: number, delay?: number): Promise<void>
– 説明: 現在のアルファ値から 0 までフェードインする。
– 引数: 省略時はインスペクタのfadeInDuration,fadeInDelayを使用。
– 戻り値: フェード完了時に resolve される Promise。fadeOut(duration?: number, delay?: number): Promise<void>
– 説明: 現在のアルファ値から 255 までフェードアウトする。
– 引数: 省略時はインスペクタのfadeOutDuration,fadeOutDelayを使用。
– 戻り値: フェード完了時に resolve される Promise。isFading(): boolean
– 説明: 現在フェード中かどうかを返す。
TypeScriptコードの実装
以下が、Cocos Creator 3.8.7 用の ScreenFader.ts の完全な実装例です。
import { _decorator, Component, Node, UIOpacity, BlockInputEvents, tween, Tween, easing, log, error } from 'cc';
const { ccclass, property } = _decorator;
/**
* ScreenFader
* 黒いUIノードにアタッチして、シーンのフェードイン/フェードアウトを制御する汎用コンポーネント。
*
* 必要コンポーネント:
* - UIOpacity: 透明度制御用(自動追加を試みます)
* - BlockInputEvents: 入力ブロック用(オプション、自動追加/削除)
*/
@ccclass('ScreenFader')
export class ScreenFader extends Component {
@property({
tooltip: 'start() 実行時に自動でフェードインを行うかどうか。\ntrue: initialAlpha から 0 までフェードインします。'
})
public autoFadeInOnStart: boolean = true;
@property({
tooltip: '開始時のアルファ値(0〜255)。\n0: 完全透明, 255: 完全不透明。\nautoFadeInOnStart が true の場合、この値から 0 までフェードインします。',
range: [0, 255, 1],
slide: true
})
public initialAlpha: number = 255;
@property({
tooltip: 'フェードインにかける時間(秒)。\n0 にすると即座に切り替わります。'
})
public fadeInDuration: number = 1.0;
@property({
tooltip: 'フェードアウトにかける時間(秒)。\n0 にすると即座に切り替わります。'
})
public fadeOutDuration: number = 0.7;
@property({
tooltip: 'フェードイン開始までの待機時間(秒)。'
})
public fadeInDelay: number = 0.0;
@property({
tooltip: 'フェードアウト開始までの待機時間(秒)。'
})
public fadeOutDelay: number = 0.0;
@property({
tooltip: 'フェード中にタッチ/クリックなどのUI入力をブロックするかどうか。\nBlockInputEvents コンポーネントを自動で制御します。'
})
public blockInputDuringFade: boolean = true;
@property({
tooltip: 'フェードイン完了時に、このノードを非アクティブ (node.active = false) にするかどうか。'
})
public deactivateOnFadeInComplete: boolean = true;
@property({
tooltip: 'フェードアウト完了時に、このノードを destroy() するかどうか。'
})
public destroyOnFadeOutComplete: boolean = false;
// 内部状態
private _uiOpacity: UIOpacity | null = null;
private _blockInput: BlockInputEvents | null = null;
private _currentTween: Tween<UIOpacity> | null = null;
private _isFading: boolean = false;
/**
* onLoad:
* - 必要なコンポーネント (UIOpacity, BlockInputEvents) を取得・準備します。
* - initialAlpha を適用します。
*/
onLoad() {
// UIOpacity を取得 or 追加
this._uiOpacity = this.getComponent(UIOpacity);
if (!this._uiOpacity) {
log('[ScreenFader] UIOpacity が見つからないため、自動で追加します。');
this._uiOpacity = this.addComponent(UIOpacity);
}
if (!this._uiOpacity) {
error('[ScreenFader] UIOpacity コンポーネントを取得・追加できませんでした。フェード機能は無効になります。');
return;
}
// 初期アルファを適用
this._uiOpacity.opacity = this._clampOpacity(this.initialAlpha);
// 入力ブロック用コンポーネントの参照を取得(必要に応じて有効化/無効化)
this._blockInput = this.getComponent(BlockInputEvents) || null;
this._setInputBlock(false);
}
/**
* start:
* - autoFadeInOnStart が true の場合、自動でフェードインを開始します。
*/
start() {
if (!this._uiOpacity) {
return;
}
if (this.autoFadeInOnStart) {
// 開始時点で initialAlpha にセットし直す(エディタ上で変更された可能性があるため)
this._uiOpacity.opacity = this._clampOpacity(this.initialAlpha);
this.fadeIn().catch((e) => {
error('[ScreenFader] autoFadeIn 中にエラーが発生しました:', e);
});
}
}
/**
* 現在フェード中かどうかを返します。
*/
public isFading(): boolean {
return this._isFading;
}
/**
* フェードインを開始します。
* @param duration フェードイン時間(秒)。省略時は fadeInDuration を使用。
* @param delay フェードイン開始までの待機時間(秒)。省略時は fadeInDelay を使用。
*/
public fadeIn(duration?: number, delay?: number): Promise<void> {
const d = duration ?? this.fadeInDuration;
const dl = delay ?? this.fadeInDelay;
return this._startFade(0, d, dl, 'in');
}
/**
* フェードアウトを開始します。
* @param duration フェードアウト時間(秒)。省略時は fadeOutDuration を使用。
* @param delay フェードアウト開始までの待機時間(秒)。省略時は fadeOutDelay を使用。
*/
public fadeOut(duration?: number, delay?: number): Promise<void> {
const d = duration ?? this.fadeOutDuration;
const dl = delay ?? this.fadeOutDelay;
return this._startFade(255, d, dl, 'out');
}
/**
* 内部フェード処理
* @param targetOpacity 目標アルファ値(0〜255)
* @param duration フェード時間(秒)
* @param delay フェード開始までの待機時間(秒)
* @param direction 'in' | 'out'
*/
private _startFade(targetOpacity: number, duration: number, delay: number, direction: 'in' | 'out'): Promise<void> {
return new Promise((resolve, reject) => {
if (!this._uiOpacity) {
error('[ScreenFader] UIOpacity が存在しないため、フェードを開始できません。');
reject(new Error('UIOpacity not found'));
return;
}
// 既存のフェードを停止
if (this._currentTween) {
this._currentTween.stop();
this._currentTween = null;
}
this._isFading = true;
// 入力ブロックを有効化
this._setInputBlock(this.blockInputDuringFade);
const startOpacity = this._clampOpacity(this._uiOpacity.opacity);
const endOpacity = this._clampOpacity(targetOpacity);
// duration が 0 以下なら即座に反映
if (duration <= 0) {
this._uiOpacity.opacity = endOpacity;
this._isFading = false;
this._setInputBlock(false);
this._onFadeComplete(direction);
resolve();
return;
}
// Tween でアルファ値を補間
const tw = tween(this._uiOpacity)
.delay(Math.max(0, delay))
.to(duration, { opacity: endOpacity }, { easing: easing.sineOut })
.call(() => {
this._currentTween = null;
this._isFading = false;
this._setInputBlock(false);
this._onFadeComplete(direction);
resolve();
});
this._currentTween = tw;
tw.start();
});
}
/**
* フェード完了時の後処理
*/
private _onFadeComplete(direction: 'in' | 'out') {
if (direction === 'in') {
if (this.deactivateOnFadeInComplete) {
this.node.active = false;
}
} else {
if (this.destroyOnFadeOutComplete) {
this.node.destroy();
}
}
}
/**
* 入力ブロックのON/OFF制御
*/
private _setInputBlock(enabled: boolean) {
if (!this.blockInputDuringFade) {
// 設定自体がオフなら、常に無効化
if (this._blockInput) {
this._blockInput.enabled = false;
}
return;
}
if (enabled) {
if (!this._blockInput) {
// 無ければ追加
this._blockInput = this.addComponent(BlockInputEvents);
}
if (this._blockInput) {
this._blockInput.enabled = true;
}
} else {
if (this._blockInput) {
this._blockInput.enabled = false;
}
}
}
/**
* アルファ値を 0〜255 にクランプ
*/
private _clampOpacity(value: number): number {
if (value < 0) return 0;
if (value > 255) return 255;
return Math.round(value);
}
/**
* onDisable:
* - ノードが無効化されたときに、進行中のフェードを停止し、入力ブロックを解除します。
*/
onDisable() {
if (this._currentTween) {
this._currentTween.stop();
this._currentTween = null;
}
this._isFading = false;
this._setInputBlock(false);
}
/**
* onDestroy:
* - 後始末として、Tween と入力ブロックを確実に解除します。
*/
onDestroy() {
if (this._currentTween) {
this._currentTween.stop();
this._currentTween = null;
}
this._setInputBlock(false);
}
}
コードのポイント解説
- onLoad
–this.getComponent(UIOpacity)で透明度制御用のコンポーネントを取得し、無ければaddComponent(UIOpacity)で追加します。
–initialAlphaをUIOpacity.opacityに適用して、開始時の透明度を決定します。
– 入力ブロック用のBlockInputEventsを取得し、初期状態では無効化します。 - start
–autoFadeInOnStartが true の場合、fadeIn()を呼び出して自動フェードインを開始します。 - fadeIn / fadeOut
– インスペクタのデフォルト値を基準にしつつ、引数で個別指定も可能。
– どちらも内部で_startFadeを呼び出し、Promise で完了を通知します。 - _startFade
– 既に進行中の Tween があれば停止し、新しい Tween を開始します。
–blockInputDuringFadeが true の場合、フェード中はBlockInputEventsを有効化して入力をブロックします。
– duration が 0 以下なら、Tween を使わず即座にアルファ値を反映します。 - _onFadeComplete
– フェードイン完了時にdeactivateOnFadeInCompleteが true なら、ノードを非アクティブにします。
– フェードアウト完了時にdestroyOnFadeOutCompleteが true なら、ノードを破棄します。 - onDisable / onDestroy
– ノードが無効化・破棄される際に Tween を停止し、入力ブロックを解除しておくことで、安全に後始末を行います。
使用手順と動作確認
1. スクリプトファイルの作成
- Editor の Assets パネルで、フェード用スクリプトを置きたいフォルダを選択します(例:
assets/scripts/ui)。 - フォルダ上で右クリック → Create → TypeScript を選択します。
- ファイル名を
ScreenFader.tsに変更します。 - 作成した
ScreenFader.tsをダブルクリックして開き、前述の TypeScript コードを丸ごと貼り付けて保存します。
2. フェード用ノードの作成
ここでは UI の黒い全画面矩形を作り、それに ScreenFader をアタッチします。
- Hierarchy パネルで、既存の
Canvasノードを選択します。 - Canvas を右クリック → Create → UI → Sprite を選択して、新しい Sprite ノードを作成します。
- 作成された Sprite ノードの名前を
ScreenFaderなど分かりやすい名前に変更します。 - Sprite ノードを選択し、Inspector で以下の設定を行います。
- Size(Content Size)を画面サイズに合わせます。
– 例: 画面が 1080×1920 の縦画面なら、Width=1080, Height=1920 に設定。
– または、Widgetコンポーネントを追加して、上下左右を 0 にして画面いっぱいに広げるのもおすすめです。 - Color を黒 (0,0,0) に設定します。
- 必要に応じて、
Sprite TypeをSimpleのままで構いません。
- Size(Content Size)を画面サイズに合わせます。
3. ScreenFader コンポーネントのアタッチ
- 先ほど作成した黒い Sprite ノード(例:
ScreenFader)を選択した状態で、Inspector の下部にある Add Component ボタンをクリックします。 - Custom → ScreenFader を選択します。
- Inspector に
ScreenFaderコンポーネントが表示されていることを確認します。
4. プロパティの設定例
典型的な「シーン開始時に黒から自然に明るくなる」設定例です。
autoFadeInOnStart: ON (true)initialAlpha: 255(完全な黒画面からスタート)fadeInDuration: 1.0(1秒かけてフェードイン)fadeInDelay: 0.0(すぐにフェードイン開始)fadeOutDuration: 0.7(後で使う想定)fadeOutDelay: 0.0blockInputDuringFade: ON (true)(フェード中は入力無効)deactivateOnFadeInComplete: ON (true)(フェードインが終わったらノードを非表示)destroyOnFadeOutComplete: OFF (false)(フェードアウト後もノードは残す)
この状態で Preview(再生) ボタンを押すと、ゲーム開始時に黒い画面から 1 秒かけて明るくなるフェードインが自動で実行されます。
5. フェードアウトのテスト(任意)
ScreenFader は他のスクリプトに依存しませんが、任意のタイミングでフェードアウトを呼び出したい場合、別のスクリプトから fadeOut() を呼び出すこともできます。
簡単なテスト用の例:
- Assets パネルで右クリック → Create → TypeScript から
FadeTest.tsを作成します。 - 以下のようなコードを記述します(あくまで動作確認用・依存なしの例です)。
import { _decorator, Component, Node, input, Input, EventTouch } from 'cc';
import { ScreenFader } from './ScreenFader';
const { ccclass, property } = _decorator;
@ccclass('FadeTest')
export class FadeTest extends Component {
@property({ tooltip: 'フェード制御を行う ScreenFader コンポーネントが付いたノードを指定してください。' })
public faderNode: Node | null = null;
private _fader: ScreenFader | null = null;
start() {
if (!this.faderNode) {
console.error('[FadeTest] faderNode が設定されていません。');
return;
}
this._fader = this.faderNode.getComponent(ScreenFader);
if (!this._fader) {
console.error('[FadeTest] 指定ノードに ScreenFader コンポーネントが見つかりません。');
return;
}
// 画面タップでフェードアウトを実行
input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
}
onDestroy() {
input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
}
private onTouchStart(event: EventTouch) {
if (!this._fader) {
return;
}
if (this._fader.isFading()) {
return;
}
// 0.7秒でフェードアウト
this._fader.fadeOut(0.7, 0.0).then(() => {
console.log('[FadeTest] フェードアウト完了');
});
}
}
使い方:
- 任意のノード(例: Canvas)に
FadeTestコンポーネントを追加します。 FadeTestのfaderNodeに、先ほど作成したScreenFaderノードをドラッグ&ドロップで指定します。- プレビューを実行し、画面をタップするとフェードアウトが実行されることを確認します。
※ 本ガイドの主役は ScreenFader であり、FadeTest はあくまで「外部スクリプトからの呼び出し例」です。
ScreenFader 自体は、他のカスタムスクリプトに依存せず単体で完結しています。
まとめ
このガイドでは、Cocos Creator 3.8.7 と TypeScript を用いて、
- UI ノードにアタッチするだけで使える ScreenFader コンポーネント
- インスペクタから調整可能なフェードパラメータ(時間・遅延・入力ブロック・自動再生など)
- 防御的なコンポーネント取得(
UIOpacityの自動追加、BlockInputEventsの自動制御) - Promise ベースで完了を待てる
fadeIn/fadeOutメソッド
を実装しました。
このコンポーネントをプロジェクトの UI 共通部品として用意しておけば、
- シーン開始時のフェードイン
- シーン遷移前のフェードアウト
- 演出中の入力ブロック
といった処理を、毎回一から書かずに再利用できるようになります。
外部の GameManager やシングルトンに依存しないため、シーンごと・Prefab ごとに独立して配置でき、保守性も高くなります。
まずは本記事のコードをそのままコピーして動かし、慣れてきたら色付きフェードやクロスフェードなど、アレンジ版のコンポーネントも作ってみるとよいでしょう。




