【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
– 完全に透明になったときに、ノード自体のactiveをfalseにするかどうか。
– 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/UIOpacityをgetComponentで取得し、防御的に存在チェック。
– 初期状態ではアルファ 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. スクリプトファイルの作成
- エディタの Assets パネルで、被弾演出用のフォルダ(例:
assets/scripts/ui)を用意します。 - フォルダ上で右クリック → Create → TypeScript を選択し、
DamageVignette.tsという名前で作成します。 - 自動生成されたコードをすべて削除し、前章の
DamageVignetteの TypeScript コードを丸ごと貼り付けて保存します。
2. ビネット用 UI ノードの作成
- Hierarchy パネルで Canvas を選択します。まだ無い場合は Create → UI → Canvas で作成してください。
- Canvas を右クリック → Create → UI → Sprite を選択し、新しい Sprite ノードを作成します。
分かりやすい名前としてDamageVignetteNodeなどに変更しておきましょう。 - 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) など、画面中央にフルスクリーンで配置。
- Sprite コンポーネント
- 必要に応じて、同じノードに UIOpacity コンポーネントを追加します(必須ではありませんが、他の UI と一括でフェードさせたい場合に便利です)。
3. DamageVignette コンポーネントをアタッチ
- Hierarchy で先ほど作成した
DamageVignetteNodeを選択します。 - Inspector 下部の Add Component ボタンをクリックします。
- Custom Component → DamageVignette を選択してアタッチします。
- 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. シーンプレビューでの簡単な動作確認
まずはエディタ上から簡単に動作を確認する方法です。
- シーンを保存し、Preview(▶ 再生) ボタンでゲームを起動します。
- 起動後、コンソールにエラーが出ていないことを確認します(Sprite / UIOpacity が無いと警告が出ます)。
- ゲーム実行中に Developer Console(ブラウザの DevTools など)から、以下のようなコードを実行してみます。
// 例: グローバルに参照を持っている場合 // (この部分はプロジェクトごとに異なりますが、概念的にはこういう呼び方になります) // ノードに対してイベントを送るパターン damageVignetteNode.emit('DamageVignette:Hit'); // もしくはコンポーネントを直接取得して呼ぶパターン const comp = damageVignetteNode.getComponent('DamageVignette') as any; comp.playHit(); - 画面端に配置した赤い Sprite が、指定した
maxAlphaまでフラッシュし、fadeDurationに従ってフェードアウトすることを確認します。
5. 実際のゲームロジックから呼び出す例
以下は、プレイヤーがダメージを受けたときに被弾演出を再生する、典型的な呼び出しパターンです。
パターンA:イベントで呼び出す(推奨)
DamageVignette の Listen 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 演出コンポーネントを作ってみてください。




