【Cocos Creator】アタッチするだけ!ScreenFader (暗転フェード)の実装方法【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】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) で追加します。
    initialAlphaUIOpacity.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. スクリプトファイルの作成

  1. Editor の Assets パネルで、フェード用スクリプトを置きたいフォルダを選択します(例: assets/scripts/ui)。
  2. フォルダ上で右クリック → CreateTypeScript を選択します。
  3. ファイル名を ScreenFader.ts に変更します。
  4. 作成した ScreenFader.ts をダブルクリックして開き、前述の TypeScript コードを丸ごと貼り付けて保存します。

2. フェード用ノードの作成

ここでは UI の黒い全画面矩形を作り、それに ScreenFader をアタッチします。

  1. Hierarchy パネルで、既存の Canvas ノードを選択します。
  2. Canvas を右クリック → CreateUISprite を選択して、新しい Sprite ノードを作成します。
  3. 作成された Sprite ノードの名前を ScreenFader など分かりやすい名前に変更します。
  4. Sprite ノードを選択し、Inspector で以下の設定を行います。
    • Size(Content Size)を画面サイズに合わせます。
      – 例: 画面が 1080×1920 の縦画面なら、Width=1080, Height=1920 に設定。
      – または、Widget コンポーネントを追加して、上下左右を 0 にして画面いっぱいに広げるのもおすすめです。
    • Color を黒 (0,0,0) に設定します。
    • 必要に応じて、Sprite TypeSimple のままで構いません。

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

  1. 先ほど作成した黒い Sprite ノード(例: ScreenFader)を選択した状態で、Inspector の下部にある Add Component ボタンをクリックします。
  2. CustomScreenFader を選択します。
  3. Inspector に ScreenFader コンポーネントが表示されていることを確認します。

4. プロパティの設定例

典型的な「シーン開始時に黒から自然に明るくなる」設定例です。

  • autoFadeInOnStart: ON (true)
  • initialAlpha: 255(完全な黒画面からスタート)
  • fadeInDuration: 1.0(1秒かけてフェードイン)
  • fadeInDelay: 0.0(すぐにフェードイン開始)
  • fadeOutDuration: 0.7(後で使う想定)
  • fadeOutDelay: 0.0
  • blockInputDuringFade: ON (true)(フェード中は入力無効)
  • deactivateOnFadeInComplete: ON (true)(フェードインが終わったらノードを非表示)
  • destroyOnFadeOutComplete: OFF (false)(フェードアウト後もノードは残す)

この状態で Preview(再生) ボタンを押すと、ゲーム開始時に黒い画面から 1 秒かけて明るくなるフェードインが自動で実行されます。

5. フェードアウトのテスト(任意)

ScreenFader は他のスクリプトに依存しませんが、任意のタイミングでフェードアウトを呼び出したい場合、別のスクリプトから fadeOut() を呼び出すこともできます。

簡単なテスト用の例:

  1. Assets パネルで右クリック → CreateTypeScript から FadeTest.ts を作成します。
  2. 以下のようなコードを記述します(あくまで動作確認用・依存なしの例です)。

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] フェードアウト完了');
        });
    }
}

使い方:

  1. 任意のノード(例: Canvas)に FadeTest コンポーネントを追加します。
  2. FadeTestfaderNode に、先ほど作成した ScreenFader ノードをドラッグ&ドロップで指定します。
  3. プレビューを実行し、画面をタップするとフェードアウトが実行されることを確認します。

※ 本ガイドの主役は ScreenFader であり、FadeTest はあくまで「外部スクリプトからの呼び出し例」です。
ScreenFader 自体は、他のカスタムスクリプトに依存せず単体で完結しています。


まとめ

このガイドでは、Cocos Creator 3.8.7 と TypeScript を用いて、

  • UI ノードにアタッチするだけで使える ScreenFader コンポーネント
  • インスペクタから調整可能なフェードパラメータ(時間・遅延・入力ブロック・自動再生など)
  • 防御的なコンポーネント取得(UIOpacity の自動追加、BlockInputEvents の自動制御)
  • Promise ベースで完了を待てる fadeIn / fadeOut メソッド

を実装しました。

このコンポーネントをプロジェクトの UI 共通部品として用意しておけば、

  • シーン開始時のフェードイン
  • シーン遷移前のフェードアウト
  • 演出中の入力ブロック

といった処理を、毎回一から書かずに再利用できるようになります。
外部の GameManager やシングルトンに依存しないため、シーンごと・Prefab ごとに独立して配置でき、保守性も高くなります。

まずは本記事のコードをそのままコピーして動かし、慣れてきたら色付きフェードやクロスフェードなど、アレンジ版のコンポーネントも作ってみるとよいでしょう。

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