mcpField
G
godot-programming-patterns

godot-programming-patterns

github.com/pbzona開発252 回インストール15 週間前に更新
リポジトリ

godot-programming-patterns について

Godot Programming Patternsは、Godotプロジェクトで具体的な設計上または性能上の課題に取り組む開発者向けのスキルです。コンポーネント、シグナル、コマンド、オブジェクトプールを必要に応じて選び、検証する方法を案内します。

主な機能

  1. 01SOLIDと結合度の評価
  2. 02型付きシグナルと接続の設計
  3. 03取り消し・キュー対応アクションへのコマンド利用
  4. 04明確なライフサイクルを持つプールの構築
  5. 05動作、ライフサイクル、性能の検証

活用シーン

  1. 01密結合したスクリプトの責務を分割
  2. 02完了イベントを複数のリスナーへ通知
  3. 03特定の操作に取り消しや再実行を追加
  4. 04反復的なインスタンス化の実測コストを削減

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/pbzona/godot-skills --skill godot-programming-patterns

インストール後、Claude Code で/godot-programming-patternsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
object-pooling.md
signals-and-commands.md
solid-and-coupling.md

詳細

Godot のプログラミングパターン

パターンは、実証された設計上またはパフォーマンス上の課題に対してのみ適用してください。直接の型付きメソッド呼び出しが最適な設計であることもよくあります。

対象範囲

このスキルは、次の場合に使用します。

  • Godot のスクリプトやシーン構成を診断するツールとして SOLID を適用する。
  • 不要な継承を避けつつ、振る舞いを目的別のコンポーネントに分離する。
  • 型付きシグナルを設計、発行、接続、テストする。
  • 可逆、キューイング、リプレイ、または再割り当てが可能なアクションをコマンドとしてカプセル化する。
  • 明示的な取得、アクティブ化、解放、リセットの契約を備えたオブジェクトプールを実装する。

ノード、シーン、リソース、Input Map、エディターの入門ガイダンスにはこのスキルを使用せず、基礎スキルを使用してください。ゲームの信頼できる状態、オートロードマネージャー、シーン遷移、永続化形式の選定にも使用せず、ゲームアーキテクチャスキルを使用してください。ローカルなパターンがそれらのシステムを支えることはありますが、このスキルではそれらの所有権を決定しません。

リファクタリングの前に調査する

  1. project.godot を見つけ、config/features を読んで正確な Godot 4.x のベースラインを特定します。
  2. 周辺のスクリプトとシーンを調べます。静的型付け、class_name、命名、フォルダー、コンポーネント、接続、テストの規約に合わせます。
  3. 現在の動作を再現し、ベースラインを記録します。プーリングでは、割り当てがボトルネックだと決めつけず、プロファイラーまたはモニターの根拠を取得します。
  4. 現在の連携先を把握します。呼び出し元、呼び出し先、シグナル、グループ、ノードパス、エクスポートされた参照、実行時に生成されるノードを確認します。
  5. 寿命と所有権を明確にします。シグナル接続、コマンドの対象、プール内オブジェクトは、それらを所有するオブジェクトより長く存続してはなりません。
  6. 別の抽象化を追加する前に、既存のコンポーネント、イベント、コマンドの基底クラス、履歴サービス、プールを検索します。
  7. 具体的な課題を特定します。変更理由の多さ、一対多の通知、アクション履歴、または測定された生成・破棄の頻発です。

契約の変更が依頼の一部である場合を除き、公開されている動作とエディターの配線は維持してください。

最小限のパターンを選ぶ

  • 呼び出し元が結果を必要とする場合、または処理順序を制御する場合、既知の連携先 1 つには直接のメソッド呼び出しを使用します。
  • すでに発生した事実について、ローカルのリスナーが 0 個以上関心を持つ可能性がある場合は、シグナルを使用します。
  • 振る舞いが独自の状態、ライフサイクル、または再利用の境界を持つ場合は、目的別のコンポーネントを使用します。
  • アクションを保存、取り消し、キューイング、再生、記録、または実行と独立して生成する必要がある場合は、コマンドを使用します。
  • 繰り返しのインスタンス化と破棄に測定可能なコストがあり、オブジェクトを確実にリセットできる場合に限り、プールを使用します。
  • すべてのサブタイプが基底クラスの契約を守る場合に限り、継承を使用します。任意の機能にはシーン構成を優先してください。

デフォルトでパターンを組み合わせないでください。たとえば、単発のボタンアクションにコマンドオブジェクト、イベントバス、プールは必要ありません。

実装ワークフロー

  1. 既存の観測可能な契約を書き出すか、対象を絞った回帰テストを追加します。
  2. 型付きメソッド、コンポーネント、シグナル、コマンド、プール境界のうち、1 つの接点を導入します。
  3. プロジェクトを実行可能な状態に保ちながら、プロデューサーとコンシューマーを 1 つずつ移行します。
  4. エクスポートされた参照、セットアップ引数、または所有するシーンを使って、所有権を明示します。
  5. すべての呼び出し元の移行後にのみ、不要になった経路を削除します。
  6. シーン終了、対象の削除、接続の繰り返し、削除後の取り消し、プールの枯渇など、ライフサイクルの境界を検証します。
  7. ベースラインと動作およびパフォーマンスを比較します。

パターンの選択

シーンベースのコードにおける SOLID
  • スクリプトには、恣意的な行数制限ではなく、一貫した変更理由を持たせます。
  • 入力の意図、シミュレーション、表示は、それぞれ独立して変化する場合に分離します。
  • 型の分岐を増やすのではなく、目的を絞ったコンポーネントや安定した契約を通じて拡張します。
  • サブタイプの事前条件を基底の契約より厳しくせず、結果を基底の契約より弱くしません。
  • 協調する相手が必要とする最小限の API に依存します。
  • 依存関係は外部から渡します。再利用可能なノードに、具体的な実装をグローバルに検索させないでください。

3 行のメソッドをすべて新しいノードに切り出すのは避けてください。分割が有効なのは、所有権、変更の圧力、テスト、または再利用が明確になる場合に限られます。

シグナル

value_changed や request_finished のように、完了した事実を示す名前を付け、ペイロードには型を指定します。シグナルの意味はエミッターが定義し、リスナーはその反応を担います。

class_name HeatGauge
extends Node

signal value_changed(value: float)

@export_range(0.1, 100.0, 0.1) var maximum: float = 10.0
var _value: float = 0.0

func value() -> float:
	return _value

func set_value(next_value: float) -> void:
	var clamped := clampf(next_value, 0.0, maximum)
	if is_equal_approx(clamped, _value):
		return
	_value = clamped
	value_changed.emit(_value)

シグナルは保存された状態ではありません。後から追加されたリスナーは、初期化時に現在の値を読み取り、その後の変更を受け取るために購読します。

コマンド

コマンドには、実行に必要なすべての情報と、取り消しを提供する場合は元に戻すために必要な正確な直前の状態を含めます。入力の収集はコマンドの外で行います。ノードが解放されたときの動作を定義せずに、長期間保持される履歴に生きたノード参照を保存しないでください。

フレームごとの連続的な移動すべてではなく、意味のあるアクションにコマンドを使います。履歴を有用な状態に保てるよう、ドラッグのような操作は統合するか、チェックポイントを設けます。

オブジェクトプール

コンテナを書く前に、ライフサイクルを定義します。

  1. 非アクティブなインスタンスを取得します。
  2. 必要なスポーンデータをすべて渡してアクティブ化します。
  3. 各サイクルで置き換え用の状態を割り当てずに使用します。
  4. 役目を終えたら解放を要求します。
  5. 変更可能なすべてのサブシステムをリセットします。
  6. 一度だけ返却します。

表示状態だけでは、十分な非アクティブ状態の契約になりません。非表示でも、処理、監視、タイマー、アニメーション、パーティクル、オーディオ、速度、一時的なシグナル接続が動作し続けることがあります。

検証

プロジェクトで確立されているチェックを実行してから、選択したパターンを直接検証します。

  • SOLID/コンポーネントのリファクタリング: 既存の呼び出し元が引き続き動作すること、切り出した単位を独立してテストできること、新たなグローバル検索が導入されていないことを確認します。
  • シグナル: 変更時に 1 回だけ発火すること、値が変わらない場合は発火しないこと、ペイロードが正しいこと、リスナーを再作成しても反応が重複しないことを確認します。
  • コマンド: 実行後に取り消すと直前の状態が正確に復元されること、やり直しで再適用されること、新しいコマンドでやり直し履歴が消去されること、履歴が空でも安全であること、無効な対象に対するポリシーが定義されていることを確認します。
  • プール: ウォームアップで想定したサイズに達すること、定常状態での再利用により割り当てが停止すること、プール枯渇時に選択した拡張/破棄/失敗のポリシーに従うこと、解放が冪等であること、再利用したオブジェクトに古い状態が残っていないことを確認します。
  • パフォーマンス: 同じ負荷条件でプロファイラーのキャプチャを比較し、フレーム時間、割り当て、またはノードの生成・破棄が改善したかを報告します。
  • ライフサイクル: シーンの変更や再読み込みを繰り返し、孤立したノード、古いコールバック、保持された参照がないか確認します。

失敗パターン

  • クラスが増えても、結合度は変わらない: 責務の名前を変えても、依存関係がすべての境界をまたいでいます。ファイルだけでなく、契約を再設計してください。
  • シグナルをコマンドとして使用: エミッターは、必須の処理を実行するリスナーがちょうど1つ存在することを前提としています。明示的な依存関係とメソッド呼び出しを使用してください。
  • 起動時の更新を見逃す: 初回の発行後にリスナーが登録されました。以降のシグナルに依存する前に、初期スナップショットを取得してください。
  • コールバックの重複: 初期化時に複数回接続されたか、長寿命のエミッターが予期せずリスナーを保持していました。接続の所有権を確認し、意図的な再接続をガードしてください。
  • 元に戻すと状態がずれる: 以前の状態を記録して復元するのではなく、逆操作を計算していました。
  • コマンドの対象が消失: 履歴が、存続期間を過ぎたシーンノードを保持していました。明示的な方針に従って、拒否、削除、または安定した対象の解決を行ってください。
  • プールで二重返却が発生: 衝突とタイムアウトの両方で、同じオブジェクトが解放されます。アクティブ状態を明示し、解放を冪等にしてください。
  • プール内の非表示オブジェクトが動作する: 衝突、処理、タイマー、アニメーション、またはオーディオの状態が無効化されていませんでした。
  • プーリングの方が遅い: 処理負荷が割り当てに起因していない、プールの線形走査が発生している、またはリセット処理のコストがインスタンス化を上回っています。測定結果に基づき、よりシンプルな設計を維持してください。

参考資料

コメント