godot-2d-gameplay について
godot-2d-gameplayは、Godotプロジェクト既存の規約に沿って2Dゲームプレイを実装するスキルです。移動、物理、戦闘を開発する人向けに、コード例をそのまま適用せず、プロジェクトを調査して端のケースまで検証する手順を示します。
主な機能
- 01Godotのバージョンとプロジェクト設定を確認
- 02既存のシーンと入力規約を維持
- 03ゲーム上の役割に応じて物理ノードを選択
- 04移動と戦闘のインタラクションを構築
- 05衝突、ダメージ、性能の端のケースを検証
- 06よくある2Dゲームプレイの問題を診断
活用シーン
- 01プラットフォーマーやトップダウン操作の実装
- 02衝突を使った攻撃とダメージの追加
- 03TileMapの衝突位置のずれを調査
- 04物理条件ごとのゲームプレイ挙動を確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ pbzona/ godot-skills --skill godot-2d-gameplayインストール後、Claude Code で/godot-2d-gameplayを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/animation-combat-game-feel.mdmovement-physics-tilemaps.mdverification-and-troubleshooting.md詳細
Godot 2D ゲームプレイ
プロジェクトで既に採用されているシーン、入力、物理、コンポーネントの規約に従って、ゲームプレイを実装します。スニペットは応用するパターンとして扱い、そのまま貼り付けるファイルとして扱わないでください。
プロジェクトの情報から始める
ノードやコードを提案する前に、次の作業を行います。
- 最も近い
project.godotを探します。config/featuresを読み、Godot の実行ファイルが利用できる場合は、godot --versionまたはプロジェクトで指定されたバイナリを使ってバージョンを確認します。 application/run/main_scene、[input]、名前付きの 2D 物理レイヤー、物理演算の更新頻度、デフォルトの重力、ウィンドウ/ストレッチ設定、テクスチャフィルタリングを確認します。- 既存の
.gd、.tscn、.tresファイルを検索し、プレイヤーのボディ、TileMapまたはTileMapLayer、アニメーションノード、ダメージ API、グループ、自動読み込み、テストツールを調べます。 - 既存のノード名、型付きスクリプトのスタイル、シグナル名、シーンの所有権、衝突の規約を維持します。
- 例に合わせるためだけに、動作している
TileMapを移行しないでください。新規の作業では、調査した Godot のバージョンが対応している場合に限り、TileMapLayerを優先します。
プロジェクトファイルが存在しない場合は、プロジェクト固有のファイルを生成する前に、対象の Godot 4.x のマイナーバージョンと最小限のシーン仕様を確認します。
物理演算の所有者を選ぶ
- ワールドの衝突に対して滑る必要がある、コード駆動のアクターには
CharacterBody2Dを使用します。プラットフォーマーには接地移動を、見下ろし型のアクターには浮遊移動を使用します。 - シミュレーション駆動のオブジェクトには
RigidBody2Dを使用します。一度だけ発生する衝突にはインパルスを、持続する加速には力を適用します。各フレームでそのトランスフォームを上書きしないでください。 - 動きを予測どおりに伝える必要があるスクリプト駆動の動く足場やハザードには、
AnimatableBody2Dを使用します。 - 重なりだけを検知するセンサー、アイテム、当たり判定用エリア、ほとんどの単純な飛び道具には
Area2Dを使用します。Area2Dは移動経路上のボディ衝突判定を提供しません。 - 物理移動とクエリは
_physics_process()に配置します。物理演算の結果と完全に一致させる必要がない限り、表示の更新は_process()に配置します。
実装ワークフロー
- 動作を測定可能な形で定義します。 速度、加速度、停止距離、ジャンプの高さまたは滞空時間、攻撃間隔、無敵時間、想定される衝突相手を記録します。
- まず入力を作成します。 意味に基づいた Input Map アクションを再利用します。アクターのロジックにキーを直接記述しないでください。キーボードとコントローラーの両方に対応する場合は、デッドゾーンを確認します。
- グレーボックスの物理シーンを構築します。 アート、アニメーション、カメラ、戦闘を追加する前に、プリミティブな衝突形状と単純な床を使用します。
- 移動モードを一つ実装します。 速度はピクセル毎秒で保持します。
CharacterBody2D.move_and_slide()が物理演算のタイムステップを内部で適用します。 - ワールドをレイヤーごとに構築します。 実際の役割に応じて、地面、衝突判定のある地形、装飾、前景、ナビゲーションを分けます。
- ゲームプレイの状態からビジュアルを決定します。 移動の後にアニメーションを選択し、アニメーショントラックが物理演算の基準となるトランスフォームを上書きしないようにします。
- 戦闘を明示的なインタラクションとして追加します。 攻撃、当たり判定用エリア、体力、チーム、クールダウン、死亡に、それぞれ個別の取り決めを設けます。フィードバックにはシグナルを使用します。
- 最後にゲームフィールを加えます。 確認済みのイベントに読み取りやすいエフェクトを重ねます。不安定な衝突判定を隠すために、画面の揺れやアニメーションを使わないでください。
- 端の状況や高負荷時に検証します。 傾斜、角、低い天井、同時に受ける攻撃、シーンの再読み込み、プールから再利用するオブジェクト、想定される最大数のアクター/飛び道具をテストします。
型付きプラットフォーマーの基本
このコントローラーでは、速度に基づく加速、ジャンプ入力のバッファリング、コヨーテタイム、短いジャンプを使い分けるためのボタンを離したときの処理を使用します。プロジェクトのスケールに合わせて値を調整してください。
extends CharacterBody2D
@export_range(0.0, 2000.0, 1.0) var run_speed: float = 260.0
@export_range(0.0, 10000.0, 1.0) var ground_accel: float = 2200.0
@export_range(0.0, 10000.0, 1.0) var air_accel: float = 900.0
@export_range(0.0, 10000.0, 1.0) var gravity: float = 1800.0
@export_range(0.0, 5000.0, 1.0) var jump_speed: float = 560.0
@export_range(0.0, 5000.0, 1.0) var max_fall_speed: float = 1100.0
@export_range(0.0, 0.5, 0.01) var coyote_time: float = 0.10
@export_range(0.0, 0.5, 0.01) var jump_buffer_time: float = 0.12
var _coyote_left: float = 0.0
var _jump_buffer_left: float = 0.0
func _physics_process(delta: float) -> void:
if Input.is_action_just_pressed(&"jump"):
_jump_buffer_left = jump_buffer_time
else:
_jump_buffer_left = maxf(_jump_buffer_left - delta, 0.0)
var axis: float = Input.get_axis(&"move_left", &"move_right")
var accel: float = ground_accel if is_on_floor() else air_accel
velocity.x = move_toward(velocity.x, axis * run_speed, accel * delta)
if is_on_floor():
_coyote_left = coyote_time
else:
_coyote_left = maxf(_coyote_left - delta, 0.0)
velocity.y = minf(velocity.y + gravity * delta, max_fall_speed)
if _jump_buffer_left > 0.0 and _coyote_left > 0.0:
velocity.y = -jump_speed
_jump_buffer_left = 0.0
_coyote_left = 0.0
if Input.is_action_just_released(&"jump") and velocity.y < 0.0:
velocity.y *= 0.45
move_and_slide()
move_and_slide() の前に velocity に delta を掛けないでください。ジャンプの高さを厳密に指定する必要がある場合は、重力と発射速度をそれぞれ独立して調整するのではなく、目標の高さと頂点に達するまでの時間から求めます。
設計ルール
- スプライトの透明部分ではなく、プレイしやすさを考慮して衝突サイズを決めます。足元を安定させ、戦闘用の当たり判定は意図的に寛容にします。
- 衝突レイヤーはインターフェースとして扱います。各レイヤーに名前を付け、どのマスクがそれを使用するかを記載します。
- アニメーションは意味上の状態が変わったときにのみ変更します。空中状態やアクション状態は、アイドル/走行状態より優先します。
- ダメージは権威を持つ受け手側で一度だけ適用します。フィードバックのリスナーは、点滅、揺れ、パーティクルの生成、UI の更新、音声の再生を行えますが、二度目の攻撃を適用してはいけません。
- クールダウンにはタイマーまたは累積ゲームデルタを使用します。壁時計のタイムスタンプでは、ポーズやタイムスケールが自然に反映されません。
- プールは、プロファイリングの結果や規模から必要性が確認できた場合にのみ使用します。プールされたノードには、タイマー、監視状態、速度、所有者、プロセス状態を明示的にリセットするスポーン/デスポーン処理が必要です。表示状態だけではライフサイクルを管理できません。
検証ゲート
作業を終える前に、次の項目を確認します。
- リポジトリに既存のテストコマンドがあれば実行し、互換性のある Godot バイナリが利用可能であれば、ヘッドレスでの解析/インポート確認も行います。
- 参照されているすべての Input Map アクションが存在することを確認します。
- 可視の衝突形状を有効にし、実行中のシーンでタイルの衝突、ボディの形状、Area2D のマスクを確認します。
- 設定された物理ティックレートでの移動と、ウィンドウまたはカメラのスケール変更後の移動を確認します。
- ダメージの上限処理、死亡イベントが一度だけ発生すること、味方が無視されること、無敵状態が永続化しないことを確認します。
- プールを使用する場合は、リモートシーンツリーとプロファイラーを確認します。数が安定し、再利用されたオブジェクトが新規インスタンスと同じように動作する必要があります。
- 変更したファイル、動作を確認したシーン、実行したコマンド、およびアセット不足や Godot 実行ファイルの不在により実施できなかった確認項目を報告します。
よくある失敗例
- トップダウン移動で斜め方向の速度が速くなる:
Input.get_vector()を使うか、ベクトルの長さを制限します。 - フレームレートによってキャラクターの速度が変わる: 移動は物理プロセス内で行い、
deltaは速度の変化率にのみ使用します。最終的な CharacterBody の速度には使用しません。 - 床の状態が常に false になる:
up_direction、モーションモード、マスク、衝突形状、およびmove_and_slide()前後の呼び出し順を確認します。 - タイルの衝突位置がずれて見える: TileSet のタイルサイズ、アトラス領域のサイズ、レイヤーの変換、衝突ポリゴンを比較します。
- アニメーションがちらつく: 複数の分岐で
play()を呼び出しているか、状態の優先順位が曖昧です。状態を一つに決めてから、一度だけ適用します。 - 攻撃が繰り返し命中する: ヒットボックスを無効化または消費済みにする、発動ごとに被害対象を記録する、あるいは受け手側に無敵時間を設けます。
- 弾丸が速すぎて対象をすり抜ける: 最終位置での重なりに頼らず、スイープボディ/レイクエリ、物理フレームごとの移動距離の短縮、またはシェイプキャストを使用します。
- 揺れの後にカメラの位置がずれる: オフセットをゼロに向けて減衰させ、エフェクト終了時に明示的にリセットします。
参考資料
コメント
Markdown 対応