【Cocos Creator】アタッチするだけ!DamageVignette (被弾枠)の実装方法【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】DamageVignette の実装:アタッチするだけで「被弾時に画面端を赤くフラッシュ表示」できる汎用スクリプト

この記事では、プレイヤーがダメージを受けた瞬間に、画面の端を赤く点滅させる「被弾枠」演出を行う汎用コンポーネント DamageVignette を実装します。
UI ノード(Sprite / UITextureなど)にアタッチし、インスペクタから色・フェード時間・カーブなどを設定するだけで使える、完全に独立したスクリプトとして設計します。


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

想定する利用シーン

  • 2D/3D ゲームで、プレイヤーが被弾したときに画面端を赤く光らせたい。
  • UI Canvas 上に「赤いビネット用のフルスクリーン画像」を置き、その見た目だけをスクリプトで制御したい。
  • ゲームロジック側から「ダメージを受けた」ときに node.emit('DamageVignette:Hit') のようにイベントを飛ばすだけで演出を再生したい。

本コンポーネントは以下のようなポリシーで設計します。

  • 完全独立:他のカスタムスクリプトには一切依存せず、このコンポーネント単体で完結。
  • UIコンポーネント依存の明示Sprite または UIOpacity を利用し、存在しない場合はエラーログを出力。
  • 柔軟なインスペクタ設定:色、最大アルファ、フラッシュ時間、カーブ(イージング)、自動非表示などをすべて @property で調整可能に。
  • イベント駆動:外部から playHit() を直接呼ぶことも、NodeEvent 経由で再生することも可能に。

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

DamageVignette で用意する主なプロパティと役割は次の通りです。

  • enabledOnStart: boolean
    – ゲーム開始時に「被弾枠演出」を有効にするかどうか。
    false にすると、コンポーネントはロードされるが演出は動かず、手動で enable() するまで無効。
  • defaultColor: Color
    – 被弾フラッシュ時に適用する色。通常は赤系(例: RGBA(255, 0, 0, 255))。
    – ベースの色で、アルファ値は maxAlpha で制御。
  • maxAlpha: number
    – 被弾直後の「最大透明度(0〜1)」を指定。
    – 例: 0.8 なら、ほぼ真っ赤にフラッシュ。
    – 0 を指定すると何も見えなくなるため注意。
  • fadeDuration: number
    – フラッシュしてから完全に消えるまでの時間(秒)。
    – 例: 0.4 秒など。短くすると瞬間的なフラッシュ、長くすると余韻のあるエフェクト。
  • useEaseOut: boolean
    – フェードアウトに「イージング(減速)」を使うかどうか。
    – ON: 最初は速く、後半ゆっくり消える。
    – OFF: 一定速度でスッと消える。
  • autoHideNode: boolean
    – 完全に透明になったときに、ノード自体の activefalse にするかどうか。
    – Canvas 上に常に置いておきたくない場合に有効。
    – 演出開始時には自動的に node.active = true に戻します。
  • listenHitEvent: boolean
    – ノードのイベント 'DamageVignette:Hit' を監視して、自動的に playHit() を呼び出すかどうか。
    – ON にすると、外部から node.emit('DamageVignette:Hit') と送るだけで被弾演出が再生されます。
  • hitEventName: string
    – 監視するイベント名。デフォルトは 'DamageVignette:Hit'
    – プロジェクトに合わせて任意のイベント名に変更可能。
  • debugLog: boolean
    – 動作ログをコンソールに出すかどうか。
    – 初期セットアップ時や挙動確認時に ON にしておくと便利。

依存する標準コンポーネント(UI側):

  • Sprite または UIOpacity(どちらかは必須)
    色の制御Sprite.color を使用。
    透明度制御UIOpacity.opacity を使用(なければ Sprite.color.a を直接変更)。
    – いずれも無い場合はエラーを出し、フェード処理は行わない。

TypeScriptコードの実装

以下が完成した DamageVignette.ts の全コードです。


import { _decorator, Component, Node, Sprite, UIOpacity, Color, math, log, warn } from 'cc';
const { ccclass, property } = _decorator;

/**
 * DamageVignette
 * 被弾時に画面端のビネット(赤枠など)をフラッシュ表示する汎用コンポーネント。
 *
 * 想定ノード:
 * - Canvas 配下のフルスクリーン UI ノード
 * - Sprite または UIOpacity を持つノード
 *
 * 使い方:
 * - ノードにこのコンポーネントをアタッチし、色・フェード時間などを設定。
 * - 外部スクリプトから:
 *     const vignette = node.getComponent(DamageVignette);
 *     vignette?.playHit();
 *   または:
 *     node.emit('DamageVignette:Hit');
 */
@ccclass('DamageVignette')
export class DamageVignette extends Component {

    @property({
        tooltip: 'ゲーム開始時にこの演出を有効にするかどうか。\nfalse の場合、enable() を呼ぶまで無効です。'
    })
    public enabledOnStart: boolean = true;

    @property({
        tooltip: '被弾フラッシュ時のベースカラー。\nアルファは maxAlpha で制御されます。'
    })
    public defaultColor: Color = new Color(255, 0, 0, 255);

    @property({
        tooltip: '被弾直後の最大透明度(0〜1)。\n0 にすると何も見えなくなります。'
    })
    public maxAlpha: number = 0.8;

    @property({
        tooltip: 'フラッシュしてから完全に消えるまでの時間(秒)。'
    })
    public fadeDuration: number = 0.4;

    @property({
        tooltip: 'フェードアウトにイージング(減速)を使用するかどうか。'
    })
    public useEaseOut: boolean = true;

    @property({
        tooltip: '完全に透明になったときに、このノードを自動的に非アクティブにするかどうか。'
    })
    public autoHideNode: boolean = true;

    @property({
        tooltip: 'ノードのイベントを監視して自動的に被弾演出を再生するかどうか。\nON の場合、node.emit(hitEventName) で再生できます。'
    })
    public listenHitEvent: boolean = true;

    @property({
        tooltip: '監視する被弾イベント名。\n外部から node.emit(この名前) を呼ぶことで被弾演出を再生します。'
    })
    public hitEventName: string = 'DamageVignette:Hit';

    @property({
        tooltip: 'デバッグ用ログをコンソールに出力するかどうか。'
    })
    public debugLog: boolean = false;

    // 内部状態
    private _sprite: Sprite | null = null;
    private _uiOpacity: UIOpacity | null = null;
    private _isPlaying: boolean = false;
    private _elapsed: number = 0;
    private _enabled: boolean = true;

    onLoad() {
        // 必要なコンポーネントの取得を試みる
        this._sprite = this.getComponent(Sprite);
        this._uiOpacity = this.getComponent(UIOpacity);

        if (!this._sprite && !this._uiOpacity) {
            warn(
                '[DamageVignette] Sprite も UIOpacity も見つかりませんでした。' +
                'このコンポーネントを使用するには、同じノードに Sprite または UIOpacity を追加してください。'
            );
        }

        // 初期状態では透明にしておく
        this._applyAlpha(0);

        // enabledOnStart の状態を反映
        this._enabled = this.enabledOnStart;

        // 自動イベントリッスン
        if (this.listenHitEvent) {
            this._registerHitEvent();
        }

        if (this.debugLog) {
            log('[DamageVignette] onLoad: enabledOnStart =', this.enabledOnStart);
        }
    }

    start() {
        // autoHideNode が true の場合、開始時は非表示にしておく
        if (this.autoHideNode) {
            this.node.active = false;
        }
    }

    onEnable() {
        // ノードが有効化されたときにイベントを再登録
        if (this.listenHitEvent) {
            this._registerHitEvent();
        }
    }

    onDisable() {
        // ノードが無効化されたときはイベントを解除
        if (this.listenHitEvent) {
            this._unregisterHitEvent();
        }
    }

    update(deltaTime: number) {
        if (!this._enabled) {
            return;
        }
        if (!this._isPlaying) {
            return;
        }

        this._elapsed += deltaTime;

        if (this.fadeDuration <= 0) {
            // フェード時間が 0 以下なら即座に非表示
            this._applyAlpha(0);
            this._isPlaying = false;
            if (this.autoHideNode) {
                this.node.active = false;
            }
            return;
        }

        // 0〜1 の正規化時間
        let t = math.clamp01(this._elapsed / this.fadeDuration);

        // イージング適用
        if (this.useEaseOut) {
            // 簡易的な easeOutQuad
            t = 1 - (1 - t) * (1 - t);
        }

        // 現在のアルファ = maxAlpha * (1 - t)
        const currentAlpha = this.maxAlpha * (1 - t);
        this._applyAlpha(currentAlpha);

        if (t >= 1) {
            // フェード完了
            this._isPlaying = false;
            if (this.autoHideNode) {
                this.node.active = false;
            }
            if (this.debugLog) {
                log('[DamageVignette] fade out completed');
            }
        }
    }

    /**
     * 外部から被弾演出を再生するための公開メソッド。
     * 例: node.getComponent(DamageVignette)?.playHit();
     */
    public playHit(): void {
        if (!this._enabled) {
            if (this.debugLog) {
                log('[DamageVignette] playHit called but component is disabled.');
            }
            return;
        }
        if (!this._sprite && !this._uiOpacity) {
            warn('[DamageVignette] 必要な UI コンポーネントが見つからないため、playHit を実行できません。');
            return;
        }

        // ノードをアクティブにして、演出を開始
        if (this.autoHideNode) {
            this.node.active = true;
        }

        this._elapsed = 0;
        this._isPlaying = true;

        // 最大アルファから開始
        this._applyAlpha(this.maxAlpha);

        if (this.debugLog) {
            log('[DamageVignette] playHit');
        }
    }

    /**
     * この演出コンポーネントを有効化する。
     * enabledOnStart が false の場合などに、ゲームロジックから呼び出せます。
     */
    public enable(): void {
        this._enabled = true;
        if (this.debugLog) {
            log('[DamageVignette] enabled');
        }
    }

    /**
     * この演出コンポーネントを無効化する。
     * 無効化中は playHit を呼んでも何も起きません。
     */
    public disable(): void {
        this._enabled = false;
        this._isPlaying = false;
        this._applyAlpha(0);
        if (this.autoHideNode) {
            this.node.active = false;
        }
        if (this.debugLog) {
            log('[DamageVignette] disabled');
        }
    }

    // ========== 内部ユーティリティ ==========

    /**
     * 指定された 0〜1 のアルファを Sprite / UIOpacity に適用する。
     */
    private _applyAlpha(alpha01: number): void {
        const a = math.clamp01(alpha01);
        const alpha255 = Math.round(a * 255);

        // Sprite の色を更新
        if (this._sprite) {
            const c = this.defaultColor.clone();
            c.a = alpha255;
            this._sprite.color = c;
        }

        // UIOpacity があれば優先的にそちらも更新
        if (this._uiOpacity) {
            this._uiOpacity.opacity = alpha255;
        }
    }

    private _registerHitEvent(): void {
        if (!this.hitEventName) {
            warn('[DamageVignette] hitEventName が空です。イベント監視は行われません。');
            return;
        }
        // 同じイベントが二重登録されないように一度解除してから登録
        this._unregisterHitEvent();

        this.node.on(this.hitEventName, this._onHitEvent, this);

        if (this.debugLog) {
            log('[DamageVignette] register hit event:', this.hitEventName);
        }
    }

    private _unregisterHitEvent(): void {
        if (!this.hitEventName) {
            return;
        }
        this.node.off(this.hitEventName, this._onHitEvent, this);
    }

    private _onHitEvent(): void {
        if (this.debugLog) {
            log('[DamageVignette] received hit event:', this.hitEventName);
        }
        this.playHit();
    }
}

コードのポイント解説

  • onLoad
    Sprite / UIOpacitygetComponent で取得し、防御的に存在チェック。
    – 初期状態ではアルファ 0(完全透明)に設定。
    enabledOnStart の値を内部フラグ _enabled に反映。
    listenHitEvent が ON なら、ノードイベントを登録。
  • start
    autoHideNode が ON の場合、開始時に node.active = false にしておく(必要なときだけ表示)。
  • update
    – 再生中のみ経過時間を更新し、fadeDuration に応じて 0〜1 の正規化時間 t を計算。
    useEaseOut が ON なら簡易的な easeOutQuad を適用。
    currentAlpha = maxAlpha * (1 - t) でアルファを減衰させ、0 になったら再生終了&必要に応じてノード非表示。
  • playHit()
    – 外部から被弾演出を再生する公開メソッド。
    – コンポーネントが無効(_enabled = false)なら何もしない。
    – 必要コンポーネントが無ければ警告ログを出して終了。
    autoHideNode が ON の場合は node.active = true に戻し、アルファを maxAlpha にして再生開始。
  • イベント連携
    listenHitEvent が ON の場合、hitEventName(デフォルト 'DamageVignette:Hit')を node.on で監視。
    – ノードに対して node.emit(hitEventName) すると自動的に playHit() が呼ばれる。

使用手順と動作確認

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

  1. エディタの Assets パネルで、被弾演出用のフォルダ(例: assets/scripts/ui)を用意します。
  2. フォルダ上で右クリック → CreateTypeScript を選択し、DamageVignette.ts という名前で作成します。
  3. 自動生成されたコードをすべて削除し、前章の DamageVignette の TypeScript コードを丸ごと貼り付けて保存します。

2. ビネット用 UI ノードの作成

  1. Hierarchy パネルで Canvas を選択します。まだ無い場合は Create → UI → Canvas で作成してください。
  2. Canvas を右クリック → Create → UI → Sprite を選択し、新しい Sprite ノードを作成します。
    分かりやすい名前として DamageVignetteNode などに変更しておきましょう。
  3. Inspector で以下のように設定します。
    • Sprite コンポーネント
      • SpriteFrame: 画面全体を覆うような四角い画像を設定(単色でも可)。
      • Type: SIMPLE のままでOK。
      • Color: とりあえず白 (255,255,255,255) のままでOK(スクリプト側で赤にします)。
    • Transform(UITransform)
      • Width / Height: 画面解像度に合わせて、画面全体を覆うサイズにする(例: 1920×1080)。
      • Anchor: (0.5, 0.5)、Position: (0, 0) など、画面中央にフルスクリーンで配置。
  4. 必要に応じて、同じノードに UIOpacity コンポーネントを追加します(必須ではありませんが、他の UI と一括でフェードさせたい場合に便利です)。

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

  1. Hierarchy で先ほど作成した DamageVignetteNode を選択します。
  2. Inspector 下部の Add Component ボタンをクリックします。
  3. Custom ComponentDamageVignette を選択してアタッチします。
  4. Inspector に表示される DamageVignette の各プロパティを設定します。
    • Enabled On Start: チェック ON(デフォルト)。
    • Default Color: 赤系 (R=255, G=0, B=0, A=255)。
    • Max Alpha: 0.8 くらい。
    • Fade Duration: 0.4 秒。
    • Use Ease Out: チェック ON。
    • Auto Hide Node: チェック ON(演出が終わると自動で非表示)。
    • Listen Hit Event: チェック ON(イベントで再生したい場合)。
    • Hit Event Name: DamageVignette:Hit のままでOK。
    • Debug Log: 動作確認中は ON にしておくと、コンソールにログが出て分かりやすいです。

4. シーンプレビューでの簡単な動作確認

まずはエディタ上から簡単に動作を確認する方法です。

  1. シーンを保存し、Preview(▶ 再生) ボタンでゲームを起動します。
  2. 起動後、コンソールにエラーが出ていないことを確認します(Sprite / UIOpacity が無いと警告が出ます)。
  3. ゲーム実行中に Developer Console(ブラウザの DevTools など)から、以下のようなコードを実行してみます。
    
    // 例: グローバルに参照を持っている場合
    // (この部分はプロジェクトごとに異なりますが、概念的にはこういう呼び方になります)
    
    // ノードに対してイベントを送るパターン
    damageVignetteNode.emit('DamageVignette:Hit');
    
    // もしくはコンポーネントを直接取得して呼ぶパターン
    const comp = damageVignetteNode.getComponent('DamageVignette') as any;
    comp.playHit();
    
  4. 画面端に配置した赤い Sprite が、指定した maxAlpha までフラッシュし、fadeDuration に従ってフェードアウトすることを確認します。

5. 実際のゲームロジックから呼び出す例

以下は、プレイヤーがダメージを受けたときに被弾演出を再生する、典型的な呼び出しパターンです。

パターンA:イベントで呼び出す(推奨)

DamageVignetteListen Hit Event を ON にしている場合、ゲームロジックからはノードにイベントを送るだけで再生できます。


// どこかのゲームロジック内(例: Player.ts のダメージ処理など)

// 事前に DamageVignetteNode を参照しておくか、
// シーン上から find() などで取得しておきます。
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

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

    @property({ tooltip: '被弾演出用の DamageVignette ノード' })
    public damageVignetteNode: Node | null = null;

    // 何らかのダメージ処理
    public applyDamage(amount: number) {
        // ... HP 減算などの処理 ...

        // 被弾演出を再生
        if (this.damageVignetteNode) {
            this.damageVignetteNode.emit('DamageVignette:Hit');
        }
    }
}

パターンB:コンポーネントを直接呼び出す

Listen Hit Event を OFF にしている場合や、直接メソッドを呼びたい場合は以下のようにします。


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

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

    @property({ tooltip: '被弾演出用の DamageVignette コンポーネントを持つノード' })
    public damageVignetteNode: Node | null = null;

    public applyDamage(amount: number) {
        // ... HP 減算などの処理 ...

        if (this.damageVignetteNode) {
            const comp = this.damageVignetteNode.getComponent(DamageVignette);
            comp?.playHit();
        }
    }
}

まとめ

DamageVignette コンポーネントは、

  • Canvas 上の 1 つの UI ノードにアタッチするだけで、被弾時の赤いビネット演出を実現できる。
  • 他のカスタムスクリプトへの依存が一切なく、完全に独立した再利用可能コンポーネントとして機能する。
  • 色・最大アルファ・フェード時間・イージング・自動非表示・イベント名などをすべてインスペクタから調整できるため、ゲームごとに細かくチューニング可能。
  • イベント駆動(node.emit(hitEventName))と直接呼び出し(playHit())の両方に対応しており、既存のコードベースにも組み込みやすい。

このように、演出ロジックを 1 つの独立コンポーネントに閉じ込めておくと、

  • 複数のシーンやプロジェクト間で簡単に再利用できる。
  • ゲームロジック側は「ダメージを受けたらイベントを飛ばす/メソッドを呼ぶ」だけで済み、実装がシンプルになる。
  • アーティストやレベルデザイナーも、コードを触らずにインスペクタから演出の強さ・スピードを調整できる。

同じ設計パターンで、ホワイトアウト/ブラックアウト/ポイズンエフェクト など、さまざまな画面演出コンポーネントを量産することもできます。
まずは本記事の DamageVignette をベースに、プロジェクトに合わせた UI 演出コンポーネントを作ってみてください。

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