【Cocos Creator 3.8】BlinkEffect の実装:アタッチするだけでノードを一定間隔で点滅させる汎用スクリプト

イベント演出やダメージ表現など、「このノードをチカチカ点滅させたい」という場面はよくあります。この記事では、任意のノードにアタッチするだけで、そのノード(と子ノード)の表示/非表示を一定間隔で自動切り替える汎用コンポーネント BlinkEffect を Cocos Creator 3.8 + TypeScript で実装します。
外部の GameManager などには一切依存せず、インスペクタのプロパティだけで完結する設計です。

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

1. 機能要件の整理

  • このコンポーネントをアタッチしたノード(親ノード)の active(≒表示状態)を一定間隔で ON/OFF する。
  • 点滅の ON/OFF 間隔をインスペクタから変更できる。
  • 常に点滅し続けるだけでなく、「指定時間だけ点滅して自動停止」もできる。
  • ゲームのポーズなどで一時停止したい場合に備え、「点滅の一時停止/再開」もできる。
  • 外部スクリプトへの依存は一切なし。このスクリプト単体で完結させる。

ここでは「visible」という表現を、Cocos Creator 3.x の実装上は Node.active の ON/OFF として扱います。
active = false にすると、そのノードと子孫ノードがまとめて非表示になるため、演出としての「点滅」には十分です。

2. インスペクタで設定可能なプロパティ設計

BlinkEffect では以下のプロパティを用意します。

  • enabledOnStart: boolean
    – デフォルト: true
    – 説明: ゲーム開始時(onEnable)に自動で点滅を開始するかどうか。
    false にすると、最初は点滅せず、後から startBlink() を呼ぶ(他コンポーネントから)などの制御が可能です。この記事では外部呼び出しは必須ではありませんが、将来の拡張を見据えた設計です。
  • blinkInterval: number
    – デフォルト: 0.3
    – 説明: 1 回の点滅で「表示→非表示」または「非表示→表示」に切り替えるまでの間隔(秒)。
    – 小さくすると高速点滅、大きくするとゆっくり点滅になります。
    防御的実装として、0.01 秒未満の値が設定された場合は内部で 0.01 秒に補正します。
  • startVisible: boolean
    – デフォルト: true
    – 説明: 点滅開始時にノードを「最初に表示状態にするかどうか」。
    – 例えばダメージ演出で「一瞬消えてから点滅させたい」といった場合に false に設定できます。
  • limitDuration: boolean
    – デフォルト: false
    – 説明: 点滅に制限時間を設けるかどうか。
    true にすると、duration 秒後に自動で点滅を停止し、ノードを元の表示状態(開始前の状態)に戻します。
  • duration: number
    – デフォルト: 2.0
    – 説明: limitDurationtrue のときにだけ有効。点滅を続ける総時間(秒)。
    – 0 以下の値が入ってしまった場合は、内部で無視(= 無制限)するように防御的に扱います。
  • pauseOnDisable: boolean
    – デフォルト: true
    – 説明: このコンポーネントが disabled になったとき(またはノードが非アクティブになったとき)に、点滅を自動停止するかどうか。
    – 通常は true 推奨。false にすると、enable/disable の再切り替えで挙動を変えたい特殊なケース向けになります。

3. ライフサイクルと挙動の設計

  • onLoad
    – ノードの初期表示状態(_initialActive)を記録しておく。
    – これにより、点滅終了後に「元の状態」に戻すことができます。
  • onEnable
    enabledOnStarttrue なら点滅を開始。
    – タイマー用の内部カウンタをリセットし、startVisible に応じて最初の表示状態を決定します。
  • onDisable
    pauseOnDisabletrue の場合、点滅を停止し、ノードの表示状態を元に戻します。
  • update(dt)
    – 点滅中のみ動作。
    – 経過時間を積算し、blinkInterval を超えたら node.active を反転、積算タイマーをリセット。
    limitDuration が有効な場合、総経過時間を監視し、duration を超えたら自動停止します。

このように、外部からの参照やシングルトンを一切使わず、ノード自身の状態だけを見て完結する設計にします。

TypeScriptコードの実装


import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

/**
 * BlinkEffect
 * 任意のノードにアタッチするだけで、Node.active を一定間隔で ON/OFF する点滅コンポーネント。
 * 外部スクリプトへの依存は一切なく、このスクリプト単体で完結します。
 */
@ccclass('BlinkEffect')
export class BlinkEffect extends Component {

    @property({
        tooltip: 'true の場合、ノードが有効化されたタイミングで自動的に点滅を開始します。'
    })
    public enabledOnStart: boolean = true;

    @property({
        tooltip: '点滅の切り替え間隔(秒)。値が小さいほど高速に点滅します。内部的には 0.01 秒未満にはなりません。'
    })
    public blinkInterval: number = 0.3;

    @property({
        tooltip: '点滅開始時に、最初の状態を「表示(true) / 非表示(false)」のどちらにするか。'
    })
    public startVisible: boolean = true;

    @property({
        tooltip: 'true の場合、指定時間(duration)だけ点滅し、その後自動で停止して元の表示状態に戻します。'
    })
    public limitDuration: boolean = false;

    @property({
        tooltip: '点滅を続ける合計時間(秒)。limitDuration が true のときのみ有効です。0 以下の場合は無視されます。'
    })
    public duration: number = 2.0;

    @property({
        tooltip: 'コンポーネントが無効化されたときに点滅を停止し、ノードの表示状態を元に戻すかどうか。'
    })
    public pauseOnDisable: boolean = true;

    // --- 内部状態管理用のフィールド ---

    /** 点滅中かどうか */
    private _isBlinking: boolean = false;

    /** 点滅開始前の Node.active の状態を保存 */
    private _initialActive: boolean = true;

    /** 点滅開始からの経過時間(総時間) */
    private _elapsedTotal: number = 0;

    /** 前回の ON/OFF 切り替えからの経過時間(インターバル用) */
    private _elapsedInterval: number = 0;

    onLoad() {
        // ノードの初期表示状態を記録しておく
        this._initialActive = this.node.active;
    }

    onEnable() {
        // enable 時に自動開始するかどうか
        if (this.enabledOnStart) {
            this.startBlink();
        }
    }

    onDisable() {
        if (this.pauseOnDisable) {
            this.stopBlink(true);
        }
    }

    update(dt: number) {
        if (!this._isBlinking) {
            return;
        }

        // blinkInterval を防御的に補正
        const interval = Math.max(0.01, this.blinkInterval);

        this._elapsedInterval += dt;
        this._elapsedTotal += dt;

        // 一定間隔ごとに active を反転
        if (this._elapsedInterval >= interval) {
            this._elapsedInterval -= interval;
            this.node.active = !this.node.active;
        }

        // 制限時間が有効なら、duration を超えたら停止
        if (this.limitDuration && this.duration > 0 && this._elapsedTotal >= this.duration) {
            this.stopBlink(true);
        }
    }

    /**
     * 点滅を開始します。
     * すでに点滅中の場合はタイマーをリセットして再スタートします。
     */
    public startBlink(): void {
        // 現在の active を初期状態として保存
        this._initialActive = this.node.active;

        // 開始時の表示状態を設定
        this.node.active = this.startVisible;

        this._elapsedTotal = 0;
        this._elapsedInterval = 0;
        this._isBlinking = true;
    }

    /**
     * 点滅を停止します。
     * @param restoreInitial true の場合、開始前の表示状態(_initialActive)に戻します。
     */
    public stopBlink(restoreInitial: boolean = true): void {
        this._isBlinking = false;
        this._elapsedTotal = 0;
        this._elapsedInterval = 0;

        if (restoreInitial) {
            this.node.active = this._initialActive;
        }
    }

    /**
     * 現在点滅中かどうかを返します。
     */
    public isBlinking(): boolean {
        return this._isBlinking;
    }
}

コードのポイント解説

  • 完全な独立性
    – 他のコンポーネントやシングルトンを一切参照していません。
    – 必要な情報はすべて this.node とインスペクタで設定したプロパティだけで完結しています。
  • onLoad
    this.node.active_initialActive に記録し、後から stopBlink() で元に戻せるようにしています。
  • onEnable / onDisable
    enabledOnStarttrue のときだけ自動開始するので、細かい制御が必要な場合には false にしておき、別コンポーネントから startBlink() を呼ぶといった拡張も可能です。
    pauseOnDisabletrue のときは、コンポーネントやノードが無効化されたタイミングで点滅を停止し、_initialActive に復帰させます。
  • update(dt)
    _isBlinkingtrue のときだけ動きます。
    blinkInterval を防御的に 0.01 秒以上に補正してから使用することで、誤設定による極端な負荷やバグを防いでいます。
    _elapsedInterval がインターバルを超えたら node.active を反転。
    limitDuration + duration > 0 のときは、_elapsedTotal を監視し、制限時間を超えたら stopBlink(true) を呼んで終了します。
  • startBlink / stopBlink
    startBlink は、現在の node.active_initialActive に保存してから、startVisible に応じた初期状態に切り替えます。
    stopBlink は、restoreInitialtrue のときに限り、開始前の状態に戻すようにしています。これにより、「点滅後は消えたままにしたい」などの特殊な用途にも対応できます(その場合は stopBlink(false) を呼ぶ)。

使用手順と動作確認

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

  1. エディタ上部の Assets パネルで、任意のフォルダ(例: assets/scripts)を右クリックします。
  2. Create → TypeScript を選択します。
  3. 新しく作成されたスクリプトの名前を BlinkEffect.ts に変更します。
  4. ダブルクリックしてエディタ(VS Code など)で開き、既存のテンプレートコードをすべて削除して、上記の TypeScript コードをそのまま貼り付けて保存します。

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

  1. Hierarchy パネルで右クリックし、Create → 2D Object → Sprite など、表示を確認しやすいノードを作成します。
    (3D の場合は Create → 3D Object → Cube などでも構いません)
  2. 作成したノードに適当な画像(SpriteFrame)やマテリアルを設定して、シーンビューで見える状態にしておきます。

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

  1. 先ほど作成した Sprite(または Cube)ノードを Hierarchy から選択します。
  2. Inspector パネルの下部にある Add Component ボタンをクリックします。
  3. メニューから CustomBlinkEffect を選択します。
    @ccclass('BlinkEffect') で定義しているため、この名前で表示されます)

4. プロパティの設定例

Inspector に追加された BlinkEffect の各プロパティを次のように設定してみます。

  • Enabled On Start: true
    → シーン再生と同時に点滅が始まります。
  • Blink Interval: 0.2
    → 0.2 秒ごとに表示/非表示が切り替わる、比較的速い点滅。
  • Start Visible: true
    → 再生直後は表示状態からスタートします。
  • Limit Duration: true
    → 一定時間だけ点滅させて自動停止したい場合に有効にします。
  • Duration: 1.5
    → 1.5 秒間だけ点滅し、その後は元の表示状態に戻ります。
  • Pause On Disable: true
    → ノードやコンポーネントを無効化した際には点滅を停止し、表示状態を元に戻します。

5. 再生して動作を確認する

  1. エディタ上部の Play ボタン(プレビューボタン)をクリックしてゲームを実行します。
  2. シーンビューまたはゲームビューで、先ほどの Sprite(または Cube)が 一定間隔で表示/非表示を繰り返していることを確認します。
  3. Limit Duration = true, Duration = 1.5 にしている場合は、約 1.5 秒後に点滅が自動で止まり、元の状態に戻ることも確認してください。

6. 動作パターンのバリエーション

いくつか設定を変えて試すと、用途ごとの感覚がつかみやすくなります。

  • 常時点滅させたい場合
    Enabled On Start = true
    Limit Duration = false
    → ゲーム中ずっと点滅させたい UI や警告ランプなどに使えます。
  • ダメージ時など、一瞬だけ点滅させたい場合
    Enabled On Start = true
    Limit Duration = true, Duration = 0.5 など短め
    Blink Interval = 0.1 くらいにすると「バチバチッ」とした感じの演出になります。
  • 最初は非表示から点滅させたい場合
    Start Visible = false
    → 「一瞬消えて、そこから点滅しながら現れる」といった演出に使えます。

まとめ

今回実装した BlinkEffect は、任意のノードにアタッチするだけで、点滅演出を即座に導入できる汎用コンポーネントです。

  • 外部の GameManager やシングルトンには一切依存せず、このスクリプト単体で完結するように設計しました。
  • blinkIntervalduration などをインスペクタから調整するだけで、さまざまな点滅パターンを実現できます。
  • キャラクターのダメージ演出、危険エリアの警告表示、UI ボタンの強調、チュートリアルの注目誘導など、多くの場面で再利用できます。

こうした「アタッチするだけで動く」汎用コンポーネントを積み重ねていくことで、毎回同じような処理を書く手間が減り、ゲーム開発全体のスピードと保守性が大きく向上します。
まずは BlinkEffect から、自分のプロジェクトに合った汎用コンポーネント群を少しずつ増やしていくとよいでしょう。