godot-best-practices について
Godot 4.xプロジェクトでGDScriptを生成、整理、レビューするAIエージェント向けのガイドスキルです。コーディング規約、アーキテクチャ指針、ゲーム開発パターンを扱い、Godot 3.x、C#、GDExtension/C++には対応しません。
主な機能
- 01GDScriptの命名規則と型指定
- 02ノード参照とシグナルのパターン
- 03リソース読み込みの指針
- 04ステートマシンとオブジェクトプールのパターン
- 05セーブと入力処理のガイド
- 06スクリプトとアーキテクチャのテンプレート
活用シーン
- 01Godot 4.xゲームのGDScript生成
- 02シーンとノード階層の整理
- 03スクリプトの一般的なアンチパターン確認
- 04セーブシステムやステートマシンの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jwynia/ agent-skills --skill godot-best-practicesインストール後、Claude Code で/godot-best-practicesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/templates/autoload-manager.gd.mdbase-script.gd.mdstate-machine.gd.mdreferences/architecture/node-communication.mdproject-structure.mdscene-composition.mdreferences/gdscript/coroutines-await.mdtype-system.mdreferences/patterns/input-handling.mdobject-pooling.mdsave-load-system.mdstate-machine.md詳細
Godot 4.x GDScript のベストプラクティス
Godot 4.x 向けの高品質な GDScript コードを作成できるよう、AI エージェントを支援します。このスキルでは、ゲーム開発のコーディング規約、アーキテクチャパターン、テンプレートを紹介します。
このスキルを使用する場面
次の場合にこのスキルを使用してください:
- 新しい GDScript コードを生成する
- Godot シーンを作成、または整理する
- ゲームのアーキテクチャやノード階層を設計する
- ステートマシン、オブジェクトプール、セーブシステムを実装する
- GDScript のパターンや Godot の規約について質問に回答する
- GDScript コードの品質上の問題をレビューする
次の場合は、このスキルを使用しないでください:
- Godot で C# を使用する(C# のパターンを使用してください)
- Godot 3.x を使用する(構文が大きく異なります)
- GDExtension/C++ を使用する(パラダイムが異なります)
- Godot のビジュアルスクリプティングを使用する
基本原則
1. 命名規則
GDScript の命名規則に一貫して従ってください:
# Classes: PascalCase
class_name PlayerController
extends CharacterBody2D
# Signals: past_tense_snake_case (describe what happened)
signal health_changed(new_health: int)
signal player_died
signal item_collected(item: Item)
# Constants: SCREAMING_SNAKE_CASE
const MAX_SPEED: float = 200.0
const JUMP_FORCE: int = -400
# Variables and functions: snake_case
var current_health: int = 100
var _private_variable: float = 0.0 # Leading underscore for private
func calculate_damage(base: int, multiplier: float) -> int:
return int(base * multiplier)
func _private_helper() -> void: # Leading underscore for private
pass
2. 型ヒント(静的型付け)
オートコンプリートとエラー検出のため、すべての箇所で明示的な型ヒントを使用してください:
# Variable declarations
var speed: float = 100.0
var player: CharacterBody2D
var items: Array[Item] = []
var stats: Dictionary = {}
# Function signatures with return types
func get_damage() -> int:
return _base_damage * _multiplier
func find_nearest_enemy(position: Vector2) -> Enemy:
# Implementation
return null
# Typed signals (Godot 4.x)
signal score_updated(new_score: int, old_score: int)
signal target_acquired(target: Node2D, distance: float)
# Node references with types
@onready var sprite: Sprite2D = $Sprite2D
@onready var collision: CollisionShape2D = $CollisionShape2D
@onready var animation_player: AnimationPlayer = %AnimationPlayer
3. ノード参照
安定性が高く、リファクタリングしやすい参照パターンを使用してください:
# PREFER: @onready with type hints
@onready var health_bar: ProgressBar = $UI/HealthBar
@onready var weapon: Weapon = $WeaponMount/Weapon
# PREFER: Unique names with % for critical nodes
@onready var player: Player = %Player
@onready var game_manager: GameManager = %GameManager
# AVOID: get_node() in _ready()
func _ready() -> void:
# Don't do this
var sprite = get_node("Sprite2D")
# AVOID: Deep fragile paths
@onready var thing = $Parent/Child/GrandChild/GreatGrandChild # Fragile
4. シグナル駆動型アーキテクチャ
疎結合な通信にはシグナルを使用してください。「シグナルは上へ、呼び出しは下へ」の原則に従ってください:
# Child node emits signals (doesn't know about parent)
class_name HealthComponent
extends Node
signal health_changed(current: int, maximum: int)
signal died
var _health: int = 100
var _max_health: int = 100
func take_damage(amount: int) -> void:
_health = max(0, _health - amount)
health_changed.emit(_health, _max_health)
if _health <= 0:
died.emit()
# Parent connects to child signals (knows about children)
class_name Player
extends CharacterBody2D
@onready var health: HealthComponent = $HealthComponent
@onready var sprite: Sprite2D = $Sprite2D
func _ready() -> void:
health.health_changed.connect(_on_health_changed)
health.died.connect(_on_died)
func _on_health_changed(current: int, maximum: int) -> void:
# Update UI, play effects, etc.
pass
func _on_died() -> void:
sprite.modulate = Color.RED
queue_free()
5. リソースの読み込み
適切な読み込み方法を選択してください:
# preload(): Compile-time loading for critical/small assets
const BULLET_SCENE: PackedScene = preload("res://scenes/bullet.tscn")
const PLAYER_SPRITE: Texture2D = preload("res://sprites/player.png")
const DAMAGE_SOUND: AudioStream = preload("res://audio/damage.wav")
# load(): Runtime loading for optional/large assets
func load_level(level_name: String) -> void:
var path := "res://levels/%s.tscn" % level_name
var level_scene: PackedScene = load(path)
var level := level_scene.instantiate()
add_child(level)
# ResourceLoader for async loading (prevents stuttering)
func _load_level_async(path: String) -> void:
ResourceLoader.load_threaded_request(path)
# Check with: ResourceLoader.load_threaded_get_status(path)
# Get with: ResourceLoader.load_threaded_get(path)
クイックリファレンス
| カテゴリ | 推奨 | 避ける |
|---|---|---|
| ノード参照 | @onready var x: Type = $Path | _ready() での get_node() |
| 一意なノード | %UniqueName | 深いパス $A/B/C/D |
| リソースの読み込み | 小規模または重要なものには preload() | あらゆる箇所での load() |
| シグナル | 型付き:signal x(val: int) | 文字列:emit_signal("x") |
| 型の安全性 | 明示的な型ヒント | 型指定のない変数 |
| 定数 | const または @export | マジックナンバー/文字列 |
| null チェック | is_instance_valid(node) | 解放済みノードに対する node != null |
| コルーチン | await | yield(非推奨) |
| グループ | シーン固有のグループ | あらゆる用途でのグローバルグループ |
| Autoload | サービス/マネージャーのみ | Autoload 内のゲームロジック |
| プロパティ | セッター/ゲッター | 直接変更 |
| 通信 | シグナルは上へ、呼び出しは下へ | 子ノードから親ノードのメソッドを呼び出す |
コード生成ガイドライン
スクリプト構造
各セクションの順序を統一してください:
class_name MyClass
extends Node2D
## Brief description of this class.
##
## Longer description if needed, explaining purpose and usage.
# === Signals ===
signal state_changed(new_state: State)
# === Enums ===
enum State { IDLE, RUNNING, JUMPING }
# === Exports ===
@export var speed: float = 100.0
@export_group("Combat")
@export var damage: int = 10
@export var attack_range: float = 50.0
# === Constants ===
const MAX_HEALTH: int = 100
# === Public Variables ===
var current_state: State = State.IDLE
# === Private Variables ===
var _internal_counter: int = 0
# === Onready ===
@onready var sprite: Sprite2D = $Sprite2D
@onready var collision: CollisionShape2D = $CollisionShape2D
# === Lifecycle Methods ===
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass
func _physics_process(delta: float) -> void:
pass
# === Public Methods ===
func take_damage(amount: int) -> void:
pass
# === Private Methods ===
func _calculate_knockback() -> Vector2:
return Vector2.ZERO
Export アノテーション
エディターで設定可能な値には export を使用してください:
# Basic exports
@export var health: int = 100
@export var speed: float = 200.0
@export var player_name: String = "Player"
# Range constraints
@export_range(0, 100) var percentage: int = 50
@export_range(0.0, 1.0, 0.1) var volume: float = 0.8
# Resource exports
@export var texture: Texture2D
@export var scene: PackedScene
@export var audio: AudioStream
# Grouped exports
@export_group("Movement")
@export var walk_speed: float = 100.0
@export var run_speed: float = 200.0
@export_group("Combat")
@export var attack_damage: int = 10
# Enum exports
@export var difficulty: Difficulty = Difficulty.NORMAL
enum Difficulty { EASY, NORMAL, HARD }
# Flags (multiselect)
@export_flags("Fire", "Water", "Earth", "Air") var elements: int = 0
よくあるゲームパターン
ステートマシン(概要)
シンプルなケースでは、enum ベースのステートマシンを使用してください:
enum State { IDLE, WALK, JUMP, ATTACK }
var current_state: State = State.IDLE
func _physics_process(delta: float) -> void:
match current_state:
State.IDLE:
_process_idle(delta)
State.WALK:
_process_walk(delta)
State.JUMP:
_process_jump(delta)
State.ATTACK:
_process_attack(delta)
func change_state(new_state: State) -> void:
if current_state == new_state:
return
_exit_state(current_state)
current_state = new_state
_enter_state(new_state)
高度な実装については、references/patterns/state-machine.md を参照してください。
オブジェクトプーリング(概要)
インスタンス化のコストを抑えるため、オブジェクトを再利用してください:
class_name ObjectPool
extends Node
var _pool: Array[Node] = []
var _scene: PackedScene
func _init(scene: PackedScene, initial_size: int = 10) -> void:
_scene = scene
for i in initial_size:
var obj := _scene.instantiate()
obj.set_process(false)
_pool.append(obj)
func acquire() -> Node:
if _pool.is_empty():
return _scene.instantiate()
var obj := _pool.pop_back()
obj.set_process(true)
return obj
func release(obj: Node) -> void:
obj.set_process(false)
_pool.append(obj)
完全な実装については、references/patterns/object-pooling.md を参照してください。
セーブ/ロード(概要)
セーブデータには Resource または JSON を使用してください:
# Custom Resource for save data
class_name SaveData
extends Resource
@export var player_position: Vector2
@export var player_health: int
@export var inventory: Array[String]
@export var level_name: String
# Save
func save_game(data: SaveData) -> void:
ResourceSaver.save(data, "user://save.tres")
# Load
func load_game() -> SaveData:
if ResourceLoader.exists("user://save.tres"):
return load("user://save.tres") as SaveData
return SaveData.new()
詳しいガイドについては、references/patterns/save-load-system.md を参照してください。
よくあるアンチパターン
| アンチパターン | 問題 | 解決策 |
|---|---|---|
_process 内でのポーリング | 状態が変化していない場合も CPU を消費する | 状態の変化にはシグナルを使用する |
get_parent().get_parent() | 結合度が高く、壊れやすい | シグナルを上へ送るか、グループを使用する |
深いノードパス $A/B/C/D | リファクタリング時に壊れる | %UniqueName を使用する |
_process 内での load() | 動作のカクつきやメモリの頻繁な使用につながる | preload() を使用するか、参照をキャッシュする |
文字列シグナル emit_signal("x") | タイプミスが起こりやすく、オートコンプリートも使えない | 型付きの signal_name.emit() を使用する |
型指定のない @onready var x = $Node | オートコンプリートが使えなくなる | 必ず型ヒントを追加する |
| Autoload 内のロジック | テストが難しくなり、結合度が高くなる | Autoload は薄く保つ |
| マジックナンバー | 意味が不明瞭 | const または @export を使用する |
解放済みノードに対する node != null | 解放済みでも true を返す | is_instance_valid() を使用する |
| 循環依存 | 読み込みエラーや処理の流れが不明瞭になる | 依存性注入またはシグナルを使用する |
追加リソース
パターンガイド
references/patterns/state-machine.md- ステートマシンの完全な実装references/patterns/object-pooling.md- 完全なプーリングシステムreferences/patterns/save-load-system.md- セーブ/ロードの包括的なガイドreferences/patterns/input-handling.md- 入力バッファリングと再割り当て
アーキテクチャ
references/architecture/project-structure.md- ディレクトリ構成references/architecture/scene-composition.md- シーン設計パターンreferences/architecture/node-communication.md- シグナルと直接呼び出しの比較
GDScript の詳細解説
references/gdscript/type-system.md- 静的型付けの詳細references/gdscript/coroutines-await.md- await を使った非同期パターン
テンプレート
assets/templates/base-script.gd.md- 標準スクリプトテンプレートassets/templates/state-machine.gd- ステートマシンテンプレートassets/templates/autoload-manager.gd- 自動読み込みシングルトンテンプレート
制限事項
- GDScript のみ(C#、GDExtension、VisualScript は対象外)
- Godot 4.x の構文(3.x とは異なるパターンもあります)
- ゲームに特化したパターン(エディタープラグインの開発は対象外)
- 実行時検証スクリプトなし(GDScript には Godot の実行環境が必要です)