Claude CodeのGDScriptを読む|変数・条件・関数・シグナル【AIでゲーム開発】
Claude Code が作った Godot 4 のコイン集めの3つのスクリプトは、変数・条件・関数・シグナルの4つの語で、どの行が何を決めているかを追えました。コインの必要数を決める1行を5から3に書き換えると、3枚目で「CLEAR」が出ました。画面の文字を出す部分を別のファイルに分けてもらうと、取り込みを済ませた写しでは分ける前と同じ動きになり、取り込みの前の写しでは読み込みのエラーが出ました。
このページの目次
この試しで読んだ3つのファイル
Claude Code に作ってもらった Godot Engine 4 の小さな2Dゲームを例にします。左右キーで動いて上キーでジャンプするプレイヤーが、画面に置いたコイン5枚を集めるゲームです。コインに触れるとコインが消えてスコアが1増え、5枚集めると画面に「CLEAR」と出ます。
スクリプト(GDScript のファイル)は3つあり、それぞれがシーンの中のノード1つに付いています。
| ファイル | 付いているノード | 行数 |
|---|---|---|
| scripts/Player.gd | Player.tscn の Player(CharacterBody2D) | 19 |
| scripts/Coin.gd | Coin.tscn の Coin(Area2D) | 11 |
| scripts/Main.gd | Main.tscn の Main(Node2D) | 23 |
どのファイルも1行目は extends で始まり、そのスクリプトを付けるノードの種類を書いています。Player.gd の1行目は次のとおりです。
extends CharacterBody2D
このページでは、この3つのファイルに出てきた行だけを例にして、変数・条件・関数・シグナルの4つを順に読みます。文法を頭から順に並べることはしません。
変数:値に名前を付けた行
Main.gd の上のほうには、次の行があります。
const TOTAL_COINS := 5
var score := 0
@onready var score_label: Label = $CanvasLayer/ScoreLabel
@onready var clear_label: Label = $CanvasLayer/ClearLabel
- var で始まる行は変数です。ゲームの途中で値が変わります。この作例のスコアは 0 から始まり、コインを1枚取るたびに1増えます。
- const で始まる行は定数です。ゲームが動いている間は値を変えられません。この作例では、「CLEAR」に要るコインの数が 5 になっています。
- 「:=」は、右に書いた値から型(整数か文字列か など)を決める書き方です。名前のあとに「: Label」のように型をじかに書く形もあります。
- 「$」で始まるところは、シーンの中のノードを道の形で取り出す書き方です。行の頭の「@onready」は、ノードの準備ができてから値を入れる印です。
Player.gd では、横に動く速さとジャンプの強さが定数になっています。
const SPEED = 200.0
const JUMP_VELOCITY = -400.0
数を変えたいときに書き換える場所は、こうした定数の行です。Main.gd の写しで、コインの数の 5 を 3 に書き換え、画面なしの Godot で動かしました。
const TOTAL_COINS := 3
3枚目を取ったところで「CLEAR」が出ました。4枚目・5枚目でもスコアは増え続け、最後は「Score: 5」でした。
条件:「〜なら」で分かれる行
if のあとに書いた式が成り立つときだけ、その下の字下げした行が動きます。else の下の行は、成り立たないときに動きます。GDScript は字下げで、どこまでが if の中かを決めます。
Player.gd の、プレイヤーを動かす関数の中には、次の条件があります。
if not is_on_floor():
velocity += get_gravity() * delta
if Input.is_action_just_pressed("ui_up") and is_on_floor():
velocity.y = JUMP_VELOCITY
var direction := Input.get_axis("ui_left", "ui_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
- 1つ目は、床の上にいない(not)ときだけ、下向きの速さを足す条件です。
- 2つ目は、上キーを押した瞬間で、しかも(and)床の上にいるときだけ、縦の速さをジャンプの強さにする条件です。
- 3つ目は、左右キーが押されているときは横の速さを決め、押されていないとき(else)は横の速さを 0 に近づける条件です。関数の中で var を使った変数は、その関数の中だけで使われます。
画面なしの Godot で右キーを押したままにすると、横の速さは 200 になり、離すと 0 に戻りました。床の上で上キーを押すと、縦の速さは -400 になりました。
Coin.gd と Main.gd にも、条件が1つずつあります。
if body.is_in_group("player"):
if score >= TOTAL_COINS:
Coin.gd のほうは、触れたものが player というグループに入っているときだけ、コインを取った扱いにする条件です。Main.gd のほうは、スコアが「CLEAR」に要る数以上になったときだけ、「CLEAR」の文字を見えるようにする条件です。
関数:まとまった手順に名前を付けた行
func で始まる行から、その下の字下げが続くところまでが、関数1つです。この作例の関数は、Godot が決まったときに呼ぶものと、スクリプトの中で名前を付けて呼ぶものの2種類に分かれます。
func _ready() -> void:
func _physics_process(delta: float) -> void:
- 前者は、ノードの準備ができたときに1回呼ばれる関数です。Main.gd と Coin.gd にあります。
- 後者は、物理の更新のたびに呼ばれる関数で、この回数は初めの設定では1秒に60回です。delta には、前に呼ばれてからの秒数が入ります。Player.gd の動きは、すべてこの中に書かれています。
- 「-> void」は、この関数が値を返さないことを表します。
Main.gd には、名前を付けて呼ぶ関数があります。
func _update_score_label() -> void:
score_label.text = "Score: %d" % score
この関数は、スコアの文字を書き換えるだけの関数です。「%d」のところにスコアの数が入り、「Score: 3」のような文字になります。スコアの文字の形を変えたいときに書き換える場所は、この関数の中です。
シグナル:コインが「取られた」と知らせる行
シグナルは、ノードが「何かが起きた」と知らせる仕組みです。知らせを受けたい側がつないでおくと、知らせが出たときに、つないだ関数が呼ばれます。Coin.gd は次の11行です。
extends Area2D
signal collected
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
collected.emit()
queue_free()
- 「signal collected」は、このコインが出す知らせに名前を付けた行です。
- 「collected.emit()」は、その知らせを出す行です。
- 6行目でつないでいるシグナルは、Coin.gd の中では決めていないもので、Area2D のノードが元から持っています。ほかの物体がコインの範囲に入ると出るシグナルで、ここでは Coin.gd の中の、その下の関数につないでいます。
- 知らせを出したあとの行で、コインは消えます。
知らせを受ける側は Main.gd です。
for coin in get_tree().get_nodes_in_group("coins"):
coin.collected.connect(_on_coin_collected)
coins というグループに入っているコインを1枚ずつ取り出し、そのコインの知らせを、スコアを1増やす関数につないでいます。プレイヤーがコインに触れると、Coin.gd が知らせを出し、Main.gd のスコアが1増える、という順につながっています。
このつなぎ方は、コインが coins のグループに入っていることが前提です。最初にできた版の写しで試すと、コインは消えるのに、スコアは「Score: 0」のままでした。グループに入っているコインを数えると 0 枚でした。Main.tscn の中で、コインをグループに入れる書き方(groups=["coins"])が、コインのノードの見出しの行([node …] の行)の外に1行で置かれていました。見出しの行の中に入れた版では、グループのコインは5枚になり、スコアは1枚ごとに1ずつ増えました。
Claude Code に1ファイルを分けてもらう頼み方
Main.gd は、コインの数を数えることと、画面の文字(スコアと「CLEAR」)を出すことの両方を、1つのファイルで書いていました。画面の文字を出す部分だけを別のファイルに分けるよう、Claude Code に次の文を1回だけ渡しました。分けるファイルと仕事・変えない動き・分けること以外は直さないこと・返してほしいことを、1文ずつ書いています。
Claude CodeにGDScriptの1ファイルを分けてもらった文
作業フォルダは D:\Claude_Game_Site\drafts\W-142-A\read-gdscript.play\ だけです。このフォルダの外は読まない・書かないでください。
この Godot 4 のプロジェクトの scripts/Main.gd は、コインを取った数を数えることと、画面の文字(ScoreLabel のスコアと ClearLabel の「CLEAR」)を出すことを、1つのファイルで両方やっています。
画面の文字を出す部分だけを、別のスクリプトのファイルに分けてください。ゲームの動き(コインに触れると消えてスコアが1増え、5枚で「CLEAR」と出る)は変えないでください。分けること以外の直しはしないでください。
Godot のエディタは開けないので、ファイル(.tscn・.gd)を直接書いてください。
できたら、新しく作ったファイルと書き換えたファイルの一覧、それぞれのスクリプトがどのシーンのどのノードに付いているか、Main.gd に残った仕事と新しいファイルに移った仕事を、短く返してください。
Claude Code は、フォルダの中のファイルを読み、書き換え、コマンドを動かすツールです。頼んでから返事が来るまでは43秒でした。Claude Code は、scripts/Hud.gd を新しく作り、Main.gd と Main.tscn を書き換えた、と返しました。Hud.gd は、文字を出す2つのノードの親(CanvasLayer)に付いています。分けたあとの Hud.gd は次の14行です。
class_name Hud
extends CanvasLayer
@onready var score_label: Label = $ScoreLabel
@onready var clear_label: Label = $ClearLabel
func _ready() -> void:
clear_label.visible = false
func update_score(score: int) -> void:
score_label.text = "Score: %d" % score
func show_clear() -> void:
clear_label.visible = true
1行目は、このスクリプトに Hud という名前を付け、ほかのスクリプトから型の名前として使えるようにする行です。分けたあとの Main.gd は、次のように Hud.gd の関数を呼ぶ形に変わりました。
@onready var hud: Hud = $CanvasLayer
func _ready() -> void:
hud.update_score(score)
for coin in get_tree().get_nodes_in_group("coins"):
coin.collected.connect(_on_coin_collected)
func _on_coin_collected() -> void:
score += 1
hud.update_score(score)
if score >= TOTAL_COINS:
hud.show_clear()
コインを数えることと「CLEAR」にするかどうかを決めることは Main.gd に残り、文字を書き換えることは Hud.gd に移りました。Player.gd と Coin.gd は変わっていませんでした。
分けたあとに読み込めるか・動きが変わっていないかを確かめる
分ける前と分けたあとのプロジェクトの写しを、画面なしの Godot 4.7.2 で、同じ台本で動かしました。台本は、プレイヤーをコインの位置に1枚ずつ置き、スコアの文字と「CLEAR」が見えているかを記録します。右キーと上キーの押下も送りました。どちらの写しも、先に次のコマンドで取り込み(エディタを起動し、取り込みを済ませて閉じる)を1回走らせています。
Godot_v4.7.2-stable_win64_console.exe --headless --path <プロジェクトの写し> --import
| 見たこと | 分ける前 | 分けたあと |
|---|---|---|
| coins のグループに入っているコイン | 5枚 | 5枚 |
| 1枚目から4枚目を取ったあとのスコアの文字 | Score: 1 から Score: 4 | Score: 1 から Score: 4 |
| 5枚目を取ったあと | Score: 5・「CLEAR」が見える | Score: 5・「CLEAR」が見える |
| 右キーを押したままにした30回の物理の更新での横の移動 | 約100 | 約100 |
| 床の上で上キーを押したあとの縦の速さ | -400 | -400 |
| 読み込みのエラー | なし | なし |
取り込みを1回も走らせていない写しでは、結果が違いました。分けたあとの版をそのまま動かすと、次のエラーが出て Main.gd が読み込めず、コインは消えてもスコアは「Score: 0」のままでした。
SCRIPT ERROR: Parse Error: Could not find type "Hud" in the current scope.
at: GDScript::reload (res://scripts/Main.gd:7)
ERROR: Failed to load script "res://scripts/Main.gd" with error "Parse error".
エラーの行の Main.gd:7 は、分けたあとの Main.gd で Hud という型の名前を使っている行です。Hud という名前は、Hud.gd の1行目で付けたものです。同じ写しで取り込みを1回走らせたあとは、このエラーは出ず、上の表のとおりに動きました。分ける前の版(型の名前を付ける行が無い)は、取り込みを走らせていない写しでも、スコアが1ずつ増えて5枚目で「CLEAR」が見えました。
エディタの画面を開いて実行ボタンから遊ぶことは、この試しでは確かめきれませんでした。
情報源
- GDScript reference(Godot Engine の公式ドキュメント)https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_basics.html確認日 2026-09-24
- Using signals(Godot Engine の公式ドキュメント)https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html確認日 2026-09-24
- Groups(Godot Engine の公式ドキュメント)https://docs.godotengine.org/en/stable/tutorials/scripting/groups.html確認日 2026-09-24
- Idle and Physics Processing(Godot Engine の公式ドキュメント)https://docs.godotengine.org/en/stable/tutorials/scripting/idle_and_physics_processing.html確認日 2026-09-24
- Area2D(Godot Engine の公式ドキュメント)https://docs.godotengine.org/en/stable/classes/class_area2d.html確認日 2026-09-24
- Command line tutorial(Godot Engine の公式ドキュメント)https://docs.godotengine.org/en/stable/tutorials/editor/command_line_tutorial.html確認日 2026-09-24
- Claude Code overview(Claude Code の公式ドキュメント)https://code.claude.com/docs/en/overview確認日 2026-09-24
