mcpField
G
godot-best-practices

godot-best-practices

github.com/jwynia開発1792.1k 回インストール35 週間前に更新
リポジトリ

godot-best-practices について

Godot 4.xプロジェクトでGDScriptを生成、整理、レビューするAIエージェント向けのガイドスキルです。コーディング規約、アーキテクチャ指針、ゲーム開発パターンを扱い、Godot 3.x、C#、GDExtension/C++には対応しません。

主な機能

  1. 01GDScriptの命名規則と型指定
  2. 02ノード参照とシグナルのパターン
  3. 03リソース読み込みの指針
  4. 04ステートマシンとオブジェクトプールのパターン
  5. 05セーブと入力処理のガイド
  6. 06スクリプトとアーキテクチャのテンプレート

活用シーン

  1. 01Godot 4.xゲームのGDScript生成
  2. 02シーンとノード階層の整理
  3. 03スクリプトの一般的なアンチパターン確認
  4. 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.md
base-script.gd.md
state-machine.gd.md
references/architecture/
node-communication.md
project-structure.md
scene-composition.md
references/gdscript/
coroutines-await.md
type-system.md
references/patterns/
input-handling.md
object-pooling.md
save-load-system.md
state-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
コルーチンawaityield(非推奨)
グループシーン固有のグループあらゆる用途でのグローバルグループ
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 の実行環境が必要です)

コメント