Godotで「電子系の敵が出てくるときに画面がビリビリッとノイズる」みたいな演出、やりたくなりますよね。ただ、素直にやろうとすると:

  • 敵シーンの中に専用のCanvasLayerやAnimationPlayerを仕込む
  • 演出用のシェーダーを各シーンごとにコピペ
  • 敵ごとに微妙に違うノイズ演出を作り始めて、だんだんカオスに…

みたいな「継承&複雑なノード階層地獄」になりがちです。
敵シーンを継承して「ノイズ付き敵」「ノイズなし敵」を作り始めると、後から仕様変更が来たときに全部直す羽目になります。

そこで今回は、「画面全体のグリッチ演出」をひとつのコンポーネントとして切り出して、どのシーンからでも簡単に呼び出せる GlitchEffect コンポーネントを作っていきましょう。
敵の登場時だけでなく、被ダメージ時、フェーズ遷移時などにも再利用できるようにしておくと、レベルデザインがかなり楽になります。

【Godot 4】一発で画面をビリビリさせる!「GlitchEffect」コンポーネント

このコンポーネントは:

  • シーンのどこかに 1 個置いておくだけ
  • 敵やイベント側から trigger_glitch() を呼ぶだけ
  • ブロックノイズ・色ズレ・画面揺れをまとめて制御

という「合成(Composition)」志向の作りにします。
敵やプレイヤーは「グリッチ演出のことを知らない」まま、ただコンポーネントに対してシグナルを送るだけ、という構造にしましょう。


フルコード:GlitchEffect.gd


extends CanvasLayer
class_name GlitchEffect
## 画面全体に「ブロックノイズ+色ズレ+軽いシェイク」をかけるコンポーネント
## 任意のシーンに1つ置いておき、敵やイベントから trigger_glitch() を呼び出して使う。

@export_category("基本設定")
@export var auto_disable: bool = true:
	## true のとき、エフェクト終了後に自動で非表示にする
	## false の場合は常に表示され、強度0で「待機」させる運用も可能
	set(value):
		auto_disable = value
		if not auto_disable:
			visible = true

@export var default_duration: float = 0.4:
	## デフォルトのグリッチ持続時間(秒)
	## trigger_glitch() の duration を省略したときに使われる
	set(value):
		default_duration = max(0.01, value)

@export_category("ノイズ・色ズレ")
@export_range(0.0, 1.0, 0.01) var max_noise_strength: float = 0.8
	## ブロックノイズの最大強度(0~1)
@export_range(0.0, 1.0, 0.01) var max_color_shift: float = 0.6
	## RGBチャンネルのズレ量の最大値(0~1)

@export_category("画面揺れ")
@export_range(0.0, 32.0, 0.1) var max_shake_amplitude: float = 12.0
	## グリッチ中の画面揺れの最大ピクセル量
@export_range(0.0, 50.0, 0.1) var shake_frequency: float = 18.0
	## 画面揺れの周波数(大きいほど激しく揺れる)

@export_category("ランダム性")
@export var randomize_seed_on_start: bool = true
	## true のとき、_ready でランダムシードを初期化して
	## 毎回少し違うノイズパターンにする

@export_category("ターゲット")
@export var target_viewport: Viewport:
	## どの Viewport をキャプチャしてグリッチさせるか
	## 空の場合は自動で get_viewport() を使う

@export_category("デバッグ")
@export var preview_on_start: bool = false
	## true にすると、シーン再生時に一度だけ自動でグリッチを再生して確認できる

## 内部状態
var _time: float = 0.0
var _duration: float = 0.0
var _is_playing: bool = false
var _base_offset: Vector2 = Vector2.ZERO

## ノード参照
var _noise_texture: NoiseTexture2D
var _shader_material: ShaderMaterial
var _texture_rect: TextureRect

func _ready() -> void:
	if randomize_seed_on_start:
		randomize()

	# Viewport の自動設定
	if target_viewport == null:
		target_viewport = get_viewport()

	# 子ノードをコード側で生成する方式にして、
	# シーンツリーを汚さない「コンポーネント感」を出す
	_create_render_target()
	_create_glitch_quad()

	visible = false
	_base_offset = position

	if preview_on_start:
		await get_tree().process_frame
		trigger_glitch()

func _process(delta: float) -> void:
	if not _is_playing:
		return

	_time += delta
	var t := clamp(_time / _duration, 0.0, 1.0)
	# 後半でスッと収束するようにイージング
	var ease := pow(1.0 - t, 2.0)

	# ノイズ・色ズレの強度を時間とともに減衰させる
	var noise_strength := max_noise_strength * ease
	var color_shift := max_color_shift * ease

	# シェーダーパラメータに反映
	_shader_material.set_shader_parameter("u_noise_strength", noise_strength)
	_shader_material.set_shader_parameter("u_color_shift", color_shift)
	_shader_material.set_shader_parameter("u_time", Time.get_ticks_msec() / 1000.0)

	# 画面揺れ
	var shake_amp := max_shake_amplitude * ease
	var shake_offset := Vector2(
		sin(_time * shake_frequency) * shake_amp,
		cos(_time * shake_frequency * 0.8) * shake_amp
	)
	position = _base_offset + shake_offset

	if _time >= _duration:
		_stop_effect()

func trigger_glitch(duration: float = -1.0) -> void:
	## 外部から呼ぶメインAPI
	## duration を指定しなければ default_duration が使われる
	if duration <= 0.0:
		duration = default_duration

	_duration = duration
	_time = 0.0
	_is_playing = true
	visible = true

	# 開始時は強めに
	_shader_material.set_shader_parameter("u_noise_strength", max_noise_strength)
	_shader_material.set_shader_parameter("u_color_shift", max_color_shift)

func _stop_effect() -> void:
	_is_playing = false
	position = _base_offset

	# シェーダーパラメータをリセット
	_shader_material.set_shader_parameter("u_noise_strength", 0.0)
	_shader_material.set_shader_parameter("u_color_shift", 0.0)

	if auto_disable:
		visible = false

func _create_render_target() -> void:
	## ViewportTexture を張るための TextureRect を作成する
	_texture_rect = TextureRect.new()
	_texture_rect.name = "GlitchQuad"
	_texture_rect.anchor_left = 0.0
	_texture_rect.anchor_top = 0.0
	_texture_rect.anchor_right = 1.0
	_texture_rect.anchor_bottom = 1.0
	_texture_rect.offset_left = 0.0
	_texture_rect.offset_top = 0.0
	_texture_rect.offset_right = 0.0
	_texture_rect.offset_bottom = 0.0
	_texture_rect.stretch_mode = TextureRect.STRETCH_SCALE
	_texture_rect.mouse_filter = Control.MOUSE_FILTER_IGNORE

	# メインViewportの内容を貼る
	var vp_tex := target_viewport.get_texture()
	_texture_rect.texture = vp_tex
	add_child(_texture_rect)

func _create_glitch_quad() -> void:
	## ノイズ用のNoiseTexture2Dとシェーダーを作成して TextureRect に適用
	_noise_texture = NoiseTexture2D.new()
	var fast_noise := FastNoiseLite.new()
	fast_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
	fast_noise.frequency = 4.0
	_noise_texture.noise = fast_noise
	_noise_texture.width = 256
	_noise_texture.height = 256
	_noise_texture.seamless = true

	var shader_code := _get_glitch_shader_code()
	var shader := Shader.new()
	shader.code = shader_code

	_shader_material = ShaderMaterial.new()
	_shader_material.shader = shader
	_shader_material.set_shader_parameter("u_noise_tex", _noise_texture)
	_shader_material.set_shader_parameter("u_noise_strength", 0.0)
	_shader_material.set_shader_parameter("u_color_shift", 0.0)
	_shader_material.set_shader_parameter("u_time", 0.0)

	_texture_rect.material = _shader_material

func _get_glitch_shader_code() -> String:
	## 画面全体に対してブロックノイズと色ズレをかけるシェーダー
	return """
shader_type canvas_item;

uniform sampler2D u_noise_tex;
uniform float u_noise_strength : hint_range(0.0, 1.0) = 0.0;
uniform float u_color_shift : hint_range(0.0, 1.0) = 0.0;
uniform float u_time = 0.0;

void fragment() {
	vec2 uv = UV;

	// ブロックノイズ用にUVを粗くサンプリング
	vec2 block_uv = floor(uv * 40.0) / 40.0;
	float noise = texture(u_noise_tex, block_uv + vec2(u_time * 0.5, 0.0)).r;

	// 0~1 → -1~1 に変換
	float n = noise * 2.0 - 1.0;

	// 横方向にガタつく感じのオフセット
	float glitch_offset = n * 0.03 * u_noise_strength;
	uv.x += glitch_offset;

	// ライン状のチラつきを追加
	float line = sin(uv.y * 400.0 + u_time * 40.0);
	float line_mask = step(0.8, line); // 0 or 1
	uv.x += line_mask * 0.02 * u_noise_strength;

	// 元の画面色をサンプリング
	vec4 base_col = texture(TEXTURE, uv);

	// 色ズレ(RGBチャンネルを少しずつずらす)
	vec2 shift = vec2(0.004, 0.0) * u_color_shift;
	float r = texture(TEXTURE, uv + shift).r;
	float g = base_col.g;
	float b = texture(TEXTURE, uv - shift).b;

	vec3 col = vec3(r, g, b);

	// ノイズで明滅させる
	col += n * 0.15 * u_noise_strength;

	COLOR = vec4(col, base_col.a);
}
"""


使い方の手順

  1. コンポーネントシーンを作る
    上のコードを GlitchEffect.gd という名前で保存し、
    新規シーンで CanvasLayer をルートにして、このスクリプトをアタッチしておきます。
    そのシーンを GlitchEffect.tscn などの名前で保存しておきましょう。
  2. メインシーンに配置する
    ゲームのメインシーン(例:Main.tscn)に GlitchEffect.tscn を1つだけインスタンスします。
    これで「画面全体にかけるグリッチコンポーネント」が常駐します。
  3. 敵やイベントから呼び出す
    敵の登場アニメーションが終わったタイミングや、スポーン直後に
    GlitchEffect.trigger_glitch() を呼び出します。
    ここでは、敵スクリプトから get_tree().get_first_node_in_group() で探すパターンを例にします。
  4. パラメータを調整して好みのノイズにする
    max_noise_strength, max_color_shift, max_shake_amplitude などをいじって、
    「ちょっとしたノイズ」から「画面が崩壊しそうなノイズ」まで調整しましょう。

シーン構成例1:プレイヤー+電子系の敵

Main (Node)
 ├── Player (CharacterBody2D)
 │    ├── Sprite2D
 │    └── CollisionShape2D
 ├── EnemySpawner (Node)
 ├── GlitchEffect (CanvasLayer)  ← このコンポーネント
 └── UI (CanvasLayer)

敵のスクリプト例:


extends Node2D

@export var spawn_glitch_duration: float = 0.3

func _ready() -> void:
	# スポーン時にグリッチを走らせる
	_play_spawn_glitch()

func _play_spawn_glitch() -> void:
	var glitch := get_tree().get_first_node_in_group("glitch_effect")
	if glitch == null:
		# グループ登録していない場合は、直接パスで取るなど別の方法でもOK
		glitch = get_tree().root.get_node_or_null("Main/GlitchEffect")
	if glitch and glitch.has_method("trigger_glitch"):
		glitch.trigger_glitch(spawn_glitch_duration)

上のコードを活かすには、GlitchEffect ノードを「glitch_effect」グループに登録しておきます:


# GlitchEffect.gd の _ready 内のどこかに追加してもOK
func _ready() -> void:
	add_to_group("glitch_effect")
	# 既存の処理...

シーン構成例2:ボス登場シーン専用のノイズ

BossIntro (Node2D)
 ├── Camera2D
 ├── Boss (Node2D)
 ├── GlitchEffect (CanvasLayer)  ← このシーン専用に置いてもよい
 └── AnimationPlayer

ボス登場アニメーションの途中で、AnimationPlayer の「Call Method Track」を使って GlitchEffect.trigger_glitch(0.6) を呼ぶようにすれば、
ボスが画面ににじみ出るような演出を簡単に仕込めます。


メリットと応用

  • シーン構造がシンプル:敵やプレイヤーにノイズ用ノードを増やさず、演出は GlitchEffect に集約できます。
  • 継承ではなく合成で管理EnemyBase に「グリッチ機能」を持たせる必要はなく、ただ「コンポーネントに通知するだけ」で済みます。
  • パラメータ調整が一箇所:ゲーム全体の「ノイズ感」を1つのノードで統一できるので、後からトーンを変えるのが楽です。
  • 再利用性が高い:タイトル画面、ゲームオーバー演出、テレポート演出など、どのシーンでも同じコンポーネントを再利用できます。

応用として、敵の種類によってノイズの強さを変えるのも簡単です。たとえば「ボスのときだけ強めにする」メソッドを追加する改造案:


func trigger_boss_glitch() -> void:
	## ボス専用の強めグリッチ(色ズレと揺れを一時的にブースト)
	var prev_noise := max_noise_strength
	var prev_color := max_color_shift
	var prev_shake := max_shake_amplitude

	max_noise_strength = 1.0
	max_color_shift = 0.9
	max_shake_amplitude = 20.0

	await trigger_glitch(0.8)

	# 終了後に元の値に戻す
	max_noise_strength = prev_noise
	max_color_shift = prev_color
	max_shake_amplitude = prev_shake

こんな感じで、「画面演出」はどんどんコンポーネント化していくと、
各キャラやギミックのスクリプトは「ゲームロジックだけ」に集中できて、かなり開発体験が良くなりますね。