Godot 4で「ホバーしたら輪郭線を光らせたい!」みたいな演出、よくやりたくなりますよね。
でも素直にやろうとすると、
- 各シーンごとに
Sprite2Dにマウス判定用のスクリプトを書く - 共通のシェーダーを継承で持たせたり、子ノードに専用ノードを増やしたり
- 「プレイヤー用」「敵用」「アイテム用」と微妙に違うスクリプトが乱立
……といった感じで、どんどんスクリプトやノード構造が肥大化しがちです。
特に「ホバーしたら輪郭線ON/OFFするだけ」のために、ベースクラスを作って継承させるのはちょっと大げさですよね。
そこでこの記事では、親の Sprite2D のマテリアルをいじって輪郭線を出すだけに特化したコンポーネント、OutlineShader コンポーネントを紹介します。
どのシーンにも「ペタッ」と貼るだけで、継承なし・ノード構造の変更なしでホバー輪郭線を実現できます。
まさに「継承より合成(Composition)」なアプローチですね。
【Godot 4】ホバーで光る輪郭線!「OutlineShader」コンポーネント
このコンポーネントは、
- 親ノードの
Sprite2D(またはCanvasItem)のマテリアルを操作 - マウスホバー時にシェーダーパラメータを変更して輪郭線(アウトライン)を表示
- ホバー解除で自動的に元に戻す
という、かなり一点特化の小さなノードです。
プレイヤー、敵、アイテム、UIボタンなど、どこにでも同じノードをアタッチして使い回せます。
前提:シェーダーマテリアルについて
この記事のコンポーネントは、アウトライン描画用の ShaderMaterial を前提にしています。
「とりあえず動かしたい」方のために、簡単なサンプルシェーダーも後半に載せておきます。
OutlineShader.gd(フルコード)
extends Node
class_name OutlineShader
"""
OutlineShader コンポーネント
親の Sprite2D / CanvasItem の ShaderMaterial を操作して、
マウスホバー時に輪郭線をON/OFFするためのコンポーネントです。
・親ノードに ShaderMaterial を設定しておき、
アウトライン用のパラメータ(例: "outline_enabled")を持たせておきます。
・このコンポーネントは、そのパラメータを切り替えるだけに専念します。
「継承して各キャラごとにホバー処理を書く」のではなく、
必要なノードにこのコンポーネントをポン付けする想定です。
"""
# =========================
# エクスポートパラメータ
# =========================
@export var target_node: CanvasItem:
"""
輪郭線を操作したい描画ノード。
通常は Sprite2D / TextureRect などの CanvasItem を指定します。
未指定の場合は、自動的に親ノードを CanvasItem として解釈しようとします。
"""
get:
return target_node
set(value):
target_node = value
_update_references()
@export var shader_param_enabled_name: StringName = &"outline_enabled":
"""
シェーダー側で「輪郭線ON/OFF」を制御する bool パラメータ名。
例: uniform bool outline_enabled;
"""
get:
return shader_param_enabled_name
set(value):
shader_param_enabled_name = value
_apply_outline_state()
@export var shader_param_color_name: StringName = &"outline_color":
"""
輪郭線の色を制御する Color パラメータ名。
例: uniform vec4 outline_color : source_color;
"""
get:
return shader_param_color_name
set(value):
shader_param_color_name = value
_apply_outline_state()
@export var shader_param_thickness_name: StringName = &"outline_thickness":
"""
輪郭線の太さを制御する float パラメータ名。
ピクセル単位など、シェーダーの実装に合わせてください。
"""
get:
return shader_param_thickness_name
set(value):
shader_param_thickness_name = value
_apply_outline_state()
@export var hover_outline_color: Color = Color(1, 1, 0, 1):
"""
ホバー時に設定する輪郭線の色。
デフォルトは黄色。
"""
get:
return hover_outline_color
set(value):
hover_outline_color = value
if _is_hovered:
_apply_outline_state()
@export var hover_outline_thickness: float = 2.0:
"""
ホバー時に設定する輪郭線の太さ。
"""
get:
return hover_outline_thickness
set(value):
hover_outline_thickness = value
if _is_hovered:
_apply_outline_state()
@export var default_outline_enabled: bool = false:
"""
ホバーしていない時のデフォルト状態で、輪郭線を有効にしておくか。
「常にうっすら表示しておいて、ホバーで強調」みたいな使い方が可能です。
"""
get:
return default_outline_enabled
set(value):
default_outline_enabled = value
if not _is_hovered:
_apply_outline_state()
@export var default_outline_color: Color = Color(1, 1, 1, 0.0):
"""
ホバーしていない時の輪郭線の色。
完全に消したい場合は alpha=0 にするか、enabled=false にします。
"""
get:
return default_outline_color
set(value):
default_outline_color = value
if not _is_hovered:
_apply_outline_state()
@export var default_outline_thickness: float = 0.0:
"""
ホバーしていない時の輪郭線の太さ。
"""
get:
return default_outline_thickness
set(value):
default_outline_thickness = value
if not _is_hovered:
_apply_outline_state()
@export var require_left_click_focus: bool = false:
"""
true の場合:
・ホバーしていても、左クリックでフォーカスを取るまでは輪郭線を有効にしない
・左クリック中だけ輪郭線を強調する…などのUI的な使い方が可能
この記事ではオプションなので、基本は false のままでOKです。
"""
get:
return require_left_click_focus
set(value):
require_left_click_focus = value
# =========================
# 内部状態
# =========================
var _canvas_item: CanvasItem
var _shader_material: ShaderMaterial
var _is_hovered: bool = false
var _has_focus_click: bool = false
func _ready() -> void:
"""
初期化。ターゲットノードとシェーダーへの参照を取得します。
親ノードが CanvasItem なら、それを自動的にターゲットにします。
"""
if target_node == null:
# 親が CanvasItem なら自動で採用
if owner is CanvasItem:
target_node = owner
elif get_parent() is CanvasItem:
target_node = get_parent()
_update_references()
_apply_outline_state()
# マウスイベントを受け取るために、親側のマウスフィルタ設定を確認しておく
_enable_mouse_pickable_if_possible()
func _update_references() -> void:
"""
target_node の変更時に呼ばれ、CanvasItem および ShaderMaterial への参照を更新します。
"""
_canvas_item = null
_shader_material = null
if target_node and target_node is CanvasItem:
_canvas_item = target_node
elif get_parent() is CanvasItem:
_canvas_item = get_parent()
if _canvas_item:
# CanvasItem.material が ShaderMaterial であることを期待
if _canvas_item.material is ShaderMaterial:
_shader_material = _canvas_item.material
else:
push_warning("OutlineShader: target CanvasItem has no ShaderMaterial. Outline will not work.")
else:
push_warning("OutlineShader: No valid CanvasItem target found.")
func _enable_mouse_pickable_if_possible() -> void:
"""
親ノードが Control / Node2D 系の場合に、マウスイベントを拾えるように設定を補助します。
必要に応じて、CollisionShape2D などはユーザー側で用意してください。
"""
if _canvas_item == null:
return
# Control の場合は mouse_filter を調整
if _canvas_item is Control:
var c := _canvas_item as Control
if c.mouse_filter == Control.MOUSE_FILTER_IGNORE:
c.mouse_filter = Control.MOUSE_FILTER_STOP
# Sprite2D / Node2D の場合は、CollisionShape2D + input_pickable が必要になるケースが多いです。
# ここでは自動化しませんが、注意喚起として警告を出しておきます。
if _canvas_item is Sprite2D:
if not (_canvas_item as Sprite2D).input_pickable:
push_warning("OutlineShader: Sprite2D.input_pickable is false. Hover detection may not work without a CollisionShape2D + Area2D or enabling input_pickable.")
func _unhandled_input(event: InputEvent) -> void:
"""
クリック系の入力を拾って、require_left_click_focus オプションに対応します。
"""
if not require_left_click_focus:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed and _is_hovered:
_has_focus_click = true
_apply_outline_state()
elif not event.pressed:
_has_focus_click = false
_apply_outline_state()
func _on_mouse_entered() -> void:
_is_hovered = true
_apply_outline_state()
func _on_mouse_exited() -> void:
_is_hovered = false
_has_focus_click = false
_apply_outline_state()
func _apply_outline_state() -> void:
"""
現在の状態(ホバー中かどうか、クリックフォーカスが必要かどうか)に応じて、
ShaderMaterial のパラメータを更新します。
"""
if _shader_material == null:
return
var enabled := default_outline_enabled
var color := default_outline_color
var thickness := default_outline_thickness
if _is_hovered:
if require_left_click_focus:
# 左クリックでフォーカスを取っている時だけ強調表示
if _has_focus_click:
enabled = true
color = hover_outline_color
thickness = hover_outline_thickness
else:
# フォーカス前はデフォルト状態
enabled = default_outline_enabled
color = default_outline_color
thickness = default_outline_thickness
else:
# 単純にホバー中は強調表示
enabled = true
color = hover_outline_color
thickness = hover_outline_thickness
# 実際にシェーダーパラメータを設定
if shader_param_enabled_name != StringName():
if _shader_material.get_shader_parameter(shader_param_enabled_name) != null:
_shader_material.set_shader_parameter(shader_param_enabled_name, enabled)
if shader_param_color_name != StringName():
if _shader_material.get_shader_parameter(shader_param_color_name) != null:
_shader_material.set_shader_parameter(shader_param_color_name, color)
if shader_param_thickness_name != StringName():
if _shader_material.get_shader_parameter(shader_param_thickness_name) != null:
_shader_material.set_shader_parameter(shader_param_thickness_name, thickness)
func _enter_tree() -> void:
"""
親ノードの mouse_entered / mouse_exited シグナルに自動接続します。
"""
# Hover 判定は「描画ノード」側のシグナルを使うのが自然なので、
# target_node または親 CanvasItem のシグナルに接続します。
await get_tree().process_frame # 親が揃うのを待つ
_update_references()
if _canvas_item:
# すでに接続済みかどうかチェック
if not _canvas_item.is_connected("mouse_entered", Callable(self, "_on_mouse_entered")):
_canvas_item.mouse_entered.connect(_on_mouse_entered)
if not _canvas_item.is_connected("mouse_exited", Callable(self, "_on_mouse_exited")):
_canvas_item.mouse_exited.connect(_on_mouse_exited)
サンプル:アウトライン用シェーダー
最低限動かすための、非常にシンプルな 2D アウトラインシェーダー例です。
(高品質なアウトラインが欲しい場合は、ここを各自で強化してください。)
# ファイル: outline.shader
shader_type canvas_item;
uniform bool outline_enabled = false;
uniform float outline_thickness = 2.0;
uniform vec4 outline_color : source_color = vec4(1.0, 1.0, 0.0, 1.0);
void fragment() {
vec4 tex = texture(TEXTURE, UV);
if (!outline_enabled) {
COLOR = tex;
return;
}
// ざっくり 8 方向サンプリングでアウトラインを描画
float alpha = tex.a;
float max_alpha = alpha;
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
if (x == 0 && y == 0) continue;
vec2 offset = vec2(float(x), float(y)) * outline_thickness / TEXTURE_PIXEL_SIZE;
vec4 sample_tex = texture(TEXTURE, UV + offset);
max_alpha = max(max_alpha, sample_tex.a);
}
}
if (alpha > 0.0) {
// 本体ピクセル
COLOR = tex;
} else if (max_alpha > 0.0) {
// 周囲にピクセルがある=輪郭
COLOR = outline_color;
} else {
// 完全に透明
COLOR = vec4(0.0);
}
}
このシェーダーを ShaderMaterial に設定し、OutlineShader コンポーネントのパラメータ名をデフォルトのまま使えば、すぐ動きます。
使い方の手順
① シェーダーマテリアルを用意する
- Godot で新規リソース >
Shaderを作成し、ファイル名をoutline.shaderにする。 - 上記のシェーダーコードをコピペ。
- 同じく新規リソース >
ShaderMaterialを作成し、Shaderにoutline.shaderを指定。
この ShaderMaterial を、輪郭線を出したい Sprite2D の Material に設定します。
② OutlineShader コンポーネントをシーンに追加する
例として「プレイヤーキャラ」にアウトラインを付けるシーン構成はこんな感じです:
Player (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D └── OutlineShader (Node)
Playerシーンを開く。Sprite2Dの Material に、さきほど作ったShaderMaterialを設定。Playerの子としてNodeを追加し、スクリプトにOutlineShader.gdをアタッチ。- インスペクタで
target_nodeをSprite2Dに設定(または未設定なら自動で親を拾います)。
この時、ホバー判定のために以下のどちらかを用意してください:
Sprite2D.input_pickable = trueにする(簡易)Area2D + CollisionShape2Dを別途用意して、そちらのmouse_entered/mouse_exitedに連動させる(高精度)
この記事のコンポーネントは Sprite2D.mouse_entered を使う前提なので、
まずは Sprite2D.input_pickable = true をオンにするのがおすすめです。
③ 輪郭線の色や太さを調整する
OutlineShader ノードを選択し、インスペクタから:
hover_outline_color… ホバー中の色(例:黄色、青色など)hover_outline_thickness… ホバー中の太さ(ピクセル相当)default_outline_enabled… 通常時も薄く輪郭を出したい場合にtruedefault_outline_color/default_outline_thickness… 通常時の見た目
たとえば「通常は薄い白い輪郭、ホバーで黄色く強調」といった演出も簡単にできます。
④ 他のオブジェクトにもコピペで使い回す
敵キャラやアイテムにも、まったく同じコンポーネントを貼るだけでOKです。
例:敵キャラシーン
Enemy (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D └── OutlineShader (Node)
例:拾えるアイテムシーン
Item (Area2D) ├── Sprite2D ├── CollisionShape2D └── OutlineShader (Node)
どのシーンでも、同じ OutlineShader コンポーネントを使い回せるので、
「敵用ホバー処理」「アイテム用ホバー処理」といったコピペ地獄から解放されます。
メリットと応用
このコンポーネントを使うことで、
- ノード構造を増やさずに演出を足せる
Sprite2D の子にさらに装飾用ノードを増やす必要がなく、シーンツリーがスッキリします。 - 継承ベースの共通クラスが不要
「ホバーで光るプレイヤー」「ホバーで光る敵」などを、HoverHighlightBaseみたいな基底クラスでまとめる必要がありません。 - アセットの再利用が簡単
ShaderMaterial と OutlineShader コンポーネントをセットでプリセット化しておけば、新しいオブジェクトにも数クリックで導入できます。 - レベルデザイン時の視認性アップ
拾えるアイテムやインタラクト可能なオブジェクトだけに輪郭線を付けることで、プレイヤーにとって「触れるもの」が一目で分かります。
「描画(シェーダー)」と「いつ光らせるか(ロジック)」を分離しているので、
シェーダーを差し替えるだけで、グロー、色反転、点滅など別の演出にも簡単に応用できます。
改造案:点滅しながらホバーアウトラインを出す
例えば「ホバー中は輪郭線が点滅する」ようにしたい場合、
コンポーネント側で時間を使って太さを揺らすだけでもそれっぽくなります。
# OutlineShader.gd に追記(簡易な点滅エフェクト)
@export var pulse_on_hover: bool = false
@export var pulse_speed: float = 6.0
@export var pulse_amplitude: float = 1.0
var _time_accum: float = 0.0
func _process(delta: float) -> void:
if not pulse_on_hover:
return
if not _is_hovered:
return
if _shader_material == null:
return
_time_accum += delta * pulse_speed
var pulse := sin(_time_accum) * 0.5 + 0.5 # 0.0 ~ 1.0
var base_thickness := hover_outline_thickness
var t := base_thickness + pulse * pulse_amplitude
if shader_param_thickness_name != StringName():
if _shader_material.get_shader_parameter(shader_param_thickness_name) != null:
_shader_material.set_shader_parameter(shader_param_thickness_name, t)
このように、コンポーネント一つを改造するだけで、全てのオブジェクトの演出を一括強化できるのが、
「継承より合成」スタイルの一番おいしいところですね。
ぜひ、自分のプロジェクト用に OutlineShader をカスタマイズして、
「ホバーで光るオブジェクト」を量産してみてください。
