Godotでアクションゲームやシューティングを作っていると、デバッグ中に「無敵モードほしいな…」と思う瞬間、めちゃくちゃ多いですよね。
でも、素直に実装しようとすると、
- プレイヤーの
take_damage()の中にif god_mode: returnを書いたり - 敵やトラップ側の攻撃判定に「プレイヤーが無敵なら当てない」条件を入れたり
- シーンごとに「デバッグ用フラグ」をバラバラに置いてしまったり
…と、あちこちに「無敵モード」の条件分岐が散らばっていきます。
継承ベースで PlayerBase に god_mode を持たせて、そこから全部のプレイヤーを派生させる、なんてやり方もできますが、結局「無敵モードのためだけに継承構造をいじる」のは本末転倒ですよね。
そこで今回は、「無敵モード」をひとつの独立したコンポーネントとして切り出してしまいましょう。
プレイヤーや敵にポン付けするだけで、HPが減らなくなり、攻撃判定も自動で無視できる GodMode コンポーネントを実装していきます。
【Godot 4】デバッグが一瞬で快適に!「GodMode」コンポーネント
この「GodMode」コンポーネントの思想はシンプルです。
- 「無敵かどうか」の状態をコンポーネントに集約する
- ダメージ処理側は「GodMode を見に行くだけ」にする
- シーンのどこにでも、必要なときだけアタッチして使い回せる
深い継承ツリーや、巨大な Player.gd にデバッグ用のコードを足していくのではなく、
「無敵」という機能を 1 ノード・1 スクリプトに閉じ込めて合成で足す、というアプローチですね。
フルコード:GodMode.gd
extends Node
class_name GodMode
## GodMode (無敵モード) コンポーネント
##
## ・デバッグ用の「無敵モード」を提供するコンポーネントです。
## ・HPが減らなくなり、攻撃判定を無視するための「単一の真実のソース」として機能します。
##
## 使い方の基本:
## - 無敵にしたいオブジェクト(プレイヤーや敵など)の子ノードとして、このコンポーネントを追加します。
## - ダメージ処理側では、このコンポーネントを探して is_invincible() をチェックします。
## - コンソールやデバッグUIから toggle() を呼んで ON/OFF することもできます。
@export var enabled: bool = true:
## 無敵モードが有効かどうか。
## デフォルト true にしておくと、シーンを再生した瞬間から無敵になります。
## テストプレイ中だけ有効にしたいなら、false にしておいて、
## 入力やコンソールから toggle() で切り替えるのもアリです。
set(value):
enabled = value
_apply_debug_visual()
@export var show_debug_icon: bool = true:
## 無敵中に「デバッグ用のアイコン」を表示するかどうか。
## ON にしておくと、エディタ/ゲーム画面上で今どのキャラが無敵か一目でわかります。
set(value):
show_debug_icon = value
_apply_debug_visual()
@export var debug_icon_color: Color = Color(1, 1, 0, 0.6):
## 無敵中のデバッグアイコンの色(デフォルトは半透明の黄色)。
set(value):
debug_icon_color = value
_apply_debug_visual()
@export var debug_icon_radius: float = 16.0:
## デバッグアイコンの半径(円のサイズ)。
set(value):
debug_icon_radius = value
_apply_debug_visual()
## 内部用: デバッグ表示用の子ノード(必要なら自動生成)
var _debug_draw_node: Node2D
func _ready() -> void:
## シーンに追加されたときに、必要に応じてデバッグ表示をセットアップします。
_ensure_debug_draw_node()
_apply_debug_visual()
# エディタ上でのテストもしやすいように、ログを出しておきます。
# (うるさい場合はコメントアウトしてください)
if Engine.is_editor_hint():
return
if enabled:
print_debug("[GodMode] Enabled on: ", get_parent())
else:
print_debug("[GodMode] Disabled on: ", get_parent())
func is_invincible() -> bool:
## 外部から参照するための API。
## 攻撃・ダメージ処理側は、これを呼んで true ならダメージをスキップします。
return enabled
func set_enabled(value: bool) -> void:
## スクリプトから有効/無効を切り替えるときに使うヘルパー。
enabled = value
func toggle() -> void:
## 無敵モードをトグル(ON/OFF)します。
enabled = not enabled
if not Engine.is_editor_hint():
print_debug("[GodMode] Toggled: ", enabled, " on ", get_parent())
func _ensure_debug_draw_node() -> void:
## デバッグ表示用の Node2D を用意します。
## すでに存在する場合は何もしません。
if _debug_draw_node and is_instance_valid(_debug_draw_node):
return
# 親が 2D 系ノードでない場合でも最低限動くように、自前で Node2D をぶら下げます。
_debug_draw_node = Node2D.new()
_debug_draw_node.name = "GodModeDebugDraw"
add_child(_debug_draw_node)
_debug_draw_node.owner = get_tree().edited_scene_root if Engine.is_editor_hint() else null
# カスタム描画用に _draw() を持つスクリプトを動的にアタッチ
var draw_script := GDScript.new()
draw_script.source_code = _get_debug_draw_script_source()
draw_script.reload()
_debug_draw_node.set_script(draw_script)
func _apply_debug_visual() -> void:
## デバッグアイコンの表示/非表示と見た目を更新します。
if not _debug_draw_node or not is_instance_valid(_debug_draw_node):
return
_debug_draw_node.visible = enabled and show_debug_icon
_debug_draw_node.set(&"color", debug_icon_color)
_debug_draw_node.set(&"radius", debug_icon_radius)
_debug_draw_node.queue_redraw()
func _get_debug_draw_script_source() -> String:
## デバッグ用 Node2D のスクリプトを文字列で返します。
## GodMode 本体とは分離しておきたいので、ここで動的に生成しています。
return '''
extends Node2D
var color: Color = Color(1, 1, 0, 0.6)
var radius: float = 16.0
func _draw() -> void:
# 半透明の円を描画して「無敵中」を視覚化します。
draw_circle(Vector2.ZERO, radius, color)
func _process(_delta: float) -> void:
# 常に再描画しておく(色やサイズが動的に変わったときにも対応)
queue_redraw()
'''
# ---------------------------------------------------------
# 便利なユーティリティ: 親ノードに対する補助関数
# ---------------------------------------------------------
func get_owner_root() -> Node:
## このコンポーネントがぶら下がっている「ルートオブジェクト」を返します。
## 通常は get_parent() と同じですが、将来的にラッパーを挟みたいときのために関数化。
return get_parent()
func debug_print_state() -> void:
## 現在の無敵状態をログに出力します(デバッグコンソール用)。
print_debug("[GodMode] enabled = ", enabled, " on ", get_parent())
使い方の手順
ここからは、実際にプレイヤーや敵に組み込む手順を見ていきましょう。
コンポーネント指向らしく、「どのシーンにも同じ手順でポン付け」できるようにしてあります。
① コンポーネントをプロジェクトに追加する
res://components/など、コンポーネント用のフォルダを作成します。- そこに
GodMode.gdを作成し、上記のコードをコピペします。 - Godot エディタを再読み込みすると、
GodModeがスクリプトクラスとして認識されます。
② プレイヤーに GodMode をアタッチする
例として、2D アクションゲームのプレイヤーを考えます。
Player (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D └── GodMode (Node) ← このノードに GodMode.gd をアタッチ
- プレイヤーシーンを開きます。
Playerの子としてNode(またはNode2D)を追加し、名前をGodModeにします。- そのノードに
GodMode.gdをアタッチします。 - インスペクタで
enabledやshow_debug_iconを好みに設定します。
③ ダメージ処理で GodMode を参照する
重要なのは、「ダメージ処理側は GodMode コンポーネントを見に行くだけ」にすることです。
プレイヤー本体のスクリプトを例にします。
# Player.gd (例)
extends CharacterBody2D
@export var max_hp: int = 10
var hp: int
var god_mode: GodMode
func _ready() -> void:
hp = max_hp
# 子ノードから GodMode コンポーネントを探す
god_mode = get_node_or_null("GodMode")
# 無くても動くようにしておくと、別シーンへの再利用が楽になります
if not god_mode:
push_warning("GodMode component is not attached to Player. No invincibility available.")
func take_damage(amount: int) -> void:
# 1. GodMode が存在して、かつ無敵ならダメージを無視
if god_mode and god_mode.is_invincible():
print_debug("[Player] Damage ignored due to GodMode.")
return
# 2. 通常のダメージ処理
hp -= amount
print_debug("[Player] Took damage: ", amount, " / HP: ", hp)
if hp <= 0:
die()
func die() -> void:
print_debug("[Player] Dead.")
queue_free()
ポイントは、プレイヤー自身は「無敵のロジック」をまったく持たないことです。
「無敵かどうか」は GodMode コンポーネントに聞くだけ。
これで、プレイヤーのスクリプトはシンプルなまま、デバッグ用の無敵機能を後付けできます。
④ 敵や動く床にもそのまま使い回す
同じ要領で、敵キャラや動く床にも GodMode をアタッチできます。
例:敵キャラ
Enemy (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D └── GodMode (Node)
# Enemy.gd (例)
extends CharacterBody2D
var hp: int = 3
var god_mode: GodMode
func _ready() -> void:
god_mode = get_node_or_null("GodMode")
func take_damage(amount: int) -> void:
if god_mode and god_mode.is_invincible():
print_debug("[Enemy] Damage ignored due to GodMode.")
return
hp -= amount
if hp <= 0:
queue_free()
例:動く床(トゲ床だけどデバッグ中は当たらないようにしたい)
SpikyPlatform (StaticBody2D) ├── Sprite2D ├── CollisionShape2D └── GodMode (Node)
# SpikyPlatform.gd (例)
extends StaticBody2D
var god_mode: GodMode
func _ready() -> void:
god_mode = get_node_or_null("GodMode")
func on_player_touch(player: Node) -> void:
# プレイヤー側にダメージを与える前に、自分が無敵なら攻撃をスキップ
if god_mode and god_mode.is_invincible():
print_debug("[SpikyPlatform] Attack ignored due to GodMode.")
return
if player.has_method("take_damage"):
player.take_damage(1)
このように、「攻撃する側」「ダメージを受ける側」どちらにでも同じコンポーネントを付けられるのが、合成の強みですね。
メリットと応用
この GodMode コンポーネントを導入すると、具体的にどんなメリットがあるかを整理してみます。
- シーン構造がスッキリする
無敵モードのために Player.gd や Enemy.gd を肥大化させる必要がなく、
「無敵」という機能は GodMode.gd に完結します。スクリプトの責務がはっきりします。 - 再利用性が高い
プレイヤーだけでなく、敵、ギミック、ボス戦のパーツなど、
どのシーンにも同じコンポーネントをそのままポン付けできます。 - デバッグフローが楽になる
例えば「特定のステージだけ敵を無敵にして挙動だけ確認したい」といったときに、
対象シーンに GodMode を追加してenabled = trueにするだけで OK です。 - 本番コードにデバッグ条件が混ざらない
本番用のダメージ処理は「GodMode があれば見る」だけで、
プロジェクト全体の「無敵モード」が 1 箇所にまとまります。
さらに、コンポーネント同士を組み合わせる発想とも相性が良いです。
たとえば、
Healthコンポーネント(HP 管理)DamageReceiverコンポーネント(ダメージ受付)GodModeコンポーネント(無敵フラグ)
をそれぞれ独立させておけば、
「敵Aは HP あり + ダメージ受付あり + 無敵なし」
「ボスコアは HP あり + ダメージ受付あり + 無敵あり」
というように、ノードを組み合わせるだけで挙動を構成できるようになります。
改造案:入力で GodMode をトグルする
最後に、ちょっとした改造案として、
「キーボードのキーで GodMode を ON/OFF する」サンプルを載せておきます。
プレイヤー側に、こんな関数を追加してみましょう。
# Player.gd の一部に追加
func _input(event: InputEvent) -> void:
# InputMap で "toggle_god_mode" アクションを定義しておく(例: F1 キー)
if event.is_action_pressed("toggle_god_mode"):
if god_mode:
god_mode.toggle()
god_mode.debug_print_state()
else:
push_warning("GodMode component is not attached, cannot toggle.")
InputMap で toggle_god_mode を F1 などに割り当てておけば、
テストプレイ中にワンキーで無敵モードを切り替えられます。
このあたりも、「無敵のロジックが GodMode にまとまっている」おかげで、
プレイヤー側は god_mode.toggle() を呼ぶだけで済むのが嬉しいところですね。
こんな感じで、「継承より合成」でデバッグ用機能をコンポーネント化していくと、
プロジェクトが大きくなっても管理しやすいコードベースになっていきます。
ぜひ、自分のゲームにも GodMode コンポーネントを導入して、快適なデバッグライフを送りましょう。
