godot-gdscript-patterns
Godot 4のGDScriptにおけるシグナル、シーン、ステートマシン、最適化などのパターンを習得できます。Godotゲームの開発、ゲームシステムの実装、またはGDScriptのベストプラクティスを学ぶ際に活用してください。
description の原文を見る
Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or learning GDScript best practices.
SKILL.md 本文
Godot GDScriptパターン
GDScriptを用いたGodot 4.xゲーム開発のための本番環境パターン。アーキテクチャ、シグナル、シーン、最適化をカバーしています。
このスキルを使用する場合
- Godot 4でゲームを構築する
- GDScriptでゲームシステムを実装する
- シーンアーキテクチャを設計する
- ゲーム状態を管理する
- GDScriptのパフォーマンスを最適化する
- Godotベストプラクティスを学習する
コアコンセプト
1. Godotアーキテクチャ
Node: 基本となるビルディングブロック
├── Scene: 再利用可能なノードツリー(.tscnで保存)
├── Resource: データコンテナ(.tresで保存)
├── Signal: イベント通信
└── Group: ノードの分類
2. GDScriptの基本
class_name Player
extends CharacterBody2D
# シグナル
signal health_changed(new_health: int)
signal died
# Exports (インスペクタで編集可能)
@export var speed: float = 200.0
@export var max_health: int = 100
@export_range(0, 1) var damage_reduction: float = 0.0
@export_group("Combat")
@export var attack_damage: int = 10
@export var attack_cooldown: float = 0.5
# Onready (ready时に初期化)
@onready var sprite: Sprite2D = $Sprite2D
@onready var animation: AnimationPlayer = $AnimationPlayer
@onready var hitbox: Area2D = $Hitbox
# プライベート変数(慣例: アンダースコアプレフィックス)
var _health: int
var _can_attack: bool = true
func _ready() -> void:
_health = max_health
func _physics_process(delta: float) -> void:
var direction := Input.get_vector("left", "right", "up", "down")
velocity = direction * speed
move_and_slide()
func take_damage(amount: int) -> void:
var actual_damage := int(amount * (1.0 - damage_reduction))
_health = max(_health - actual_damage, 0)
health_changed.emit(_health)
if _health <= 0:
died.emit()
パターン
パターン1: ステートマシン
# state_machine.gd
class_name StateMachine
extends Node
signal state_changed(from_state: StringName, to_state: StringName)
@export var initial_state: State
var current_state: State
var states: Dictionary = {}
func _ready() -> void:
# すべてのStateの子ノードを登録
for child in get_children():
if child is State:
states[child.name] = child
child.state_machine = self
child.process_mode = Node.PROCESS_MODE_DISABLED
# 初期状態を開始
if initial_state:
current_state = initial_state
current_state.process_mode = Node.PROCESS_MODE_INHERIT
current_state.enter()
func _process(delta: float) -> void:
if current_state:
current_state.update(delta)
func _physics_process(delta: float) -> void:
if current_state:
current_state.physics_update(delta)
func _unhandled_input(event: InputEvent) -> void:
if current_state:
current_state.handle_input(event)
func transition_to(state_name: StringName, msg: Dictionary = {}) -> void:
if not states.has(state_name):
push_error("State '%s' not found" % state_name)
return
var previous_state := current_state
previous_state.exit()
previous_state.process_mode = Node.PROCESS_MODE_DISABLED
current_state = states[state_name]
current_state.process_mode = Node.PROCESS_MODE_INHERIT
current_state.enter(msg)
state_changed.emit(previous_state.name, current_state.name)
# state.gd
class_name State
extends Node
var state_machine: StateMachine
func enter(_msg: Dictionary = {}) -> void:
pass
func exit() -> void:
pass
func update(_delta: float) -> void:
pass
func physics_update(_delta: float) -> void:
pass
func handle_input(_event: InputEvent) -> void:
pass
# player_idle.gd
class_name PlayerIdle
extends State
@export var player: Player
func enter(_msg: Dictionary = {}) -> void:
player.animation.play("idle")
func physics_update(_delta: float) -> void:
var direction := Input.get_vector("left", "right", "up", "down")
if direction != Vector2.ZERO:
state_machine.transition_to("Move")
func handle_input(event: InputEvent) -> void:
if event.is_action_pressed("attack"):
state_machine.transition_to("Attack")
elif event.is_action_pressed("jump"):
state_machine.transition_to("Jump")
パターン2: オートロードシングルトン
# game_manager.gd (プロジェクト設定 > オートロードに追加)
extends Node
signal game_started
signal game_paused(is_paused: bool)
signal game_over(won: bool)
signal score_changed(new_score: int)
enum GameState { MENU, PLAYING, PAUSED, GAME_OVER }
var state: GameState = GameState.MENU
var score: int = 0:
set(value):
score = value
score_changed.emit(score)
var high_score: int = 0
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
_load_high_score()
func _input(event: InputEvent) -> void:
if event.is_action_pressed("pause") and state == GameState.PLAYING:
toggle_pause()
func start_game() -> void:
score = 0
state = GameState.PLAYING
game_started.emit()
func toggle_pause() -> void:
var is_paused := state != GameState.PAUSED
if is_paused:
state = GameState.PAUSED
get_tree().paused = true
else:
state = GameState.PLAYING
get_tree().paused = false
game_paused.emit(is_paused)
func end_game(won: bool) -> void:
state = GameState.GAME_OVER
if score > high_score:
high_score = score
_save_high_score()
game_over.emit(won)
func add_score(points: int) -> void:
score += points
func _load_high_score() -> void:
if FileAccess.file_exists("user://high_score.save"):
var file := FileAccess.open("user://high_score.save", FileAccess.READ)
high_score = file.get_32()
func _save_high_score() -> void:
var file := FileAccess.open("user://high_score.save", FileAccess.WRITE)
file.store_32(high_score)
# event_bus.gd (グローバルシグナルバス)
extends Node
# プレイヤーイベント
signal player_spawned(player: Node2D)
signal player_died(player: Node2D)
signal player_health_changed(health: int, max_health: int)
# 敵イベント
signal enemy_spawned(enemy: Node2D)
signal enemy_died(enemy: Node2D, position: Vector2)
# アイテムイベント
signal item_collected(item_type: StringName, value: int)
signal powerup_activated(powerup_type: StringName)
# レベルイベント
signal level_started(level_number: int)
signal level_completed(level_number: int, time: float)
signal checkpoint_reached(checkpoint_id: int)
パターン3: リソースベースのデータ
# weapon_data.gd
class_name WeaponData
extends Resource
@export var name: StringName
@export var damage: int
@export var attack_speed: float
@export var range: float
@export_multiline var description: String
@export var icon: Texture2D
@export var projectile_scene: PackedScene
@export var sound_attack: AudioStream
# character_stats.gd
class_name CharacterStats
extends Resource
signal stat_changed(stat_name: StringName, new_value: float)
@export var max_health: float = 100.0
@export var attack: float = 10.0
@export var defense: float = 5.0
@export var speed: float = 200.0
# ランタイム値(保存されない)
var _current_health: float
func _init() -> void:
_current_health = max_health
func get_current_health() -> float:
return _current_health
func take_damage(amount: float) -> float:
var actual_damage := maxf(amount - defense, 1.0)
_current_health = maxf(_current_health - actual_damage, 0.0)
stat_changed.emit("health", _current_health)
return actual_damage
func heal(amount: float) -> void:
_current_health = minf(_current_health + amount, max_health)
stat_changed.emit("health", _current_health)
func duplicate_for_runtime() -> CharacterStats:
var copy := duplicate() as CharacterStats
copy._current_health = copy.max_health
return copy
# リソースの使用
class_name Character
extends CharacterBody2D
@export var base_stats: CharacterStats
@export var weapon: WeaponData
var stats: CharacterStats
func _ready() -> void:
# リソースの変更を避けるためランタイムコピーを作成
stats = base_stats.duplicate_for_runtime()
stats.stat_changed.connect(_on_stat_changed)
func attack() -> void:
if weapon:
print("Attacking with %s for %d damage" % [weapon.name, weapon.damage])
func _on_stat_changed(stat_name: StringName, value: float) -> void:
if stat_name == "health" and value <= 0:
die()
パターン4: オブジェクトプーリング
# object_pool.gd
class_name ObjectPool
extends Node
@export var pooled_scene: PackedScene
@export var initial_size: int = 10
@export var can_grow: bool = true
var _available: Array[Node] = []
var _in_use: Array[Node] = []
func _ready() -> void:
_initialize_pool()
func _initialize_pool() -> void:
for i in initial_size:
_create_instance()
func _create_instance() -> Node:
var instance := pooled_scene.instantiate()
instance.process_mode = Node.PROCESS_MODE_DISABLED
instance.visible = false
add_child(instance)
_available.append(instance)
# 戻すシグナルが存在する場合は接続
if instance.has_signal("returned_to_pool"):
instance.returned_to_pool.connect(_return_to_pool.bind(instance))
return instance
func get_instance() -> Node:
var instance: Node
if _available.is_empty():
if can_grow:
instance = _create_instance()
_available.erase(instance)
else:
push_warning("Pool exhausted and cannot grow")
return null
else:
instance = _available.pop_back()
instance.process_mode = Node.PROCESS_MODE_INHERIT
instance.visible = true
_in_use.append(instance)
if instance.has_method("on_spawn"):
instance.on_spawn()
return instance
func _return_to_pool(instance: Node) -> void:
if not instance in _in_use:
return
_in_use.erase(instance)
if instance.has_method("on_despawn"):
instance.on_despawn()
instance.process_mode = Node.PROCESS_MODE_DISABLED
instance.visible = false
_available.append(instance)
func return_all() -> void:
for instance in _in_use.duplicate():
_return_to_pool(instance)
# pooled_bullet.gd
class_name PooledBullet
extends Area2D
signal returned_to_pool
@export var speed: float = 500.0
@export var lifetime: float = 5.0
var direction: Vector2
var _timer: float
func on_spawn() -> void:
_timer = lifetime
func on_despawn() -> void:
direction = Vector2.ZERO
func initialize(pos: Vector2, dir: Vector2) -> void:
global_position = pos
direction = dir.normalized()
rotation = direction.angle()
func _physics_process(delta: float) -> void:
position += direction * speed * delta
_timer -= delta
if _timer <= 0:
returned_to_pool.emit()
func _on_body_entered(body: Node2D) -> void:
if body.has_method("take_damage"):
body.take_damage(10)
returned_to_pool.emit()
パターン5: コンポーネントシステム
# health_component.gd
class_name HealthComponent
extends Node
signal health_changed(current: int, maximum: int)
signal damaged(amount: int, source: Node)
signal healed(amount: int)
signal died
@export var max_health: int = 100
@export var invincibility_time: float = 0.0
var current_health: int:
set(value):
var old := current_health
current_health = clampi(value, 0, max_health)
if current_health != old:
health_changed.emit(current_health, max_health)
var _invincible: bool = false
func _ready() -> void:
current_health = max_health
func take_damage(amount: int, source: Node = null) -> int:
if _invincible or current_health <= 0:
return 0
var actual := mini(amount, current_health)
current_health -= actual
damaged.emit(actual, source)
if current_health <= 0:
died.emit()
elif invincibility_time > 0:
_start_invincibility()
return actual
func heal(amount: int) -> int:
var actual := mini(amount, max_health - current_health)
current_health += actual
if actual > 0:
healed.emit(actual)
return actual
func _start_invincibility() -> void:
_invincible = true
await get_tree().create_timer(invincibility_time).timeout
_invincible = false
# hitbox_component.gd
class_name HitboxComponent
extends Area2D
signal hit(hurtbox: HurtboxComponent)
@export var damage: int = 10
@export var knockback_force: float = 200.0
var owner_node: Node
func _ready() -> void:
owner_node = get_parent()
area_entered.connect(_on_area_entered)
func _on_area_entered(area: Area2D) -> void:
if area is HurtboxComponent:
var hurtbox := area as HurtboxComponent
if hurtbox.owner_node != owner_node:
hit.emit(hurtbox)
hurtbox.receive_hit(self)
# hurtbox_component.gd
class_name HurtboxComponent
extends Area2D
signal hurt(hitbox: HitboxComponent)
@export var health_component: HealthComponent
var owner_node: Node
func _ready() -> void:
owner_node = get_parent()
func receive_hit(hitbox: HitboxComponent) -> void:
hurt.emit(hitbox)
if health_component:
health_component.take_damage(hitbox.damage, hitbox.owner_node)
高度なGodotパターン、パフォーマンスのヒント、ベストプラクティスについては、references/advanced-patterns.mdを参照してください:
- パターン6: シーン管理 —
SceneManagerのオートロード(非同期スレッド化ロードResourceLoader.load_threaded_request、ResourceLoader.has_cachedチェック、トランジション オーバーレイ対応、queue_freeを使用したシーン切り替え) - パターン7: セーブシステム —
SaveManagerのオートロード(AES暗号化セーブファイルFileAccess.open_encrypted_with_pass、JSON シリアライズ、ノードごとのセーブ/ロード ライフサイクル用の再利用可能なSaveableコンポーネント) - パフォーマンスのヒント —
@onready参照のキャッシング、_process内のアロケーション回避、スタティック型の利点、画面外ノードの処理無効化 - ベストプラクティス — シグナル、型指定、リソース、プーリング、オートロードに関するDo'sとDon'ts
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- wshobson
- リポジトリ
- wshobson/agents
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/wshobson/agents / ライセンス: MIT
関連スキル
superfluid
Superfluidプロトコルおよびそのエコシステムに関するナレッジベースです。Superfluidについて情報を検索する際は、ウェブ検索の前にこちらを参照してください。対応キーワード:Superfluid、CFA、GDA、Super App、Super Token、stream、flow rate、real-time balance、pool(member/distributor)、IDA、sentinels、liquidation、TOGA、@sfpro/sdk、semantic money、yellowpaper、whitepaper
civ-finish-quotes
実質的なタスクが真に完了した際に、文明風の儀式的な引用句を追加します。ユーザーやエージェントが機能追加、リファクタリング、分析、設計ドキュメント、プロセス改善、レポート、執筆タスクといった実際の成果物を完成させるときに、明示的な依頼がなくても使用します。短い返信や小さな修正、未完成の作業には適用しません。
nookplot
Base(Ethereum L2)上のAIエージェント向け分散型調整ネットワークです。エージェントがオンチェーンアイデンティティを登録する、コンテンツを公開する、他のエージェントにメッセージを送る、マーケットプレイスで専門家を雇う、バウンティを投稿・請求する、レピュテーションを構築する、共有プロジェクトで協業する、リサーチチャレンジを解くことでNOOKをマイニングする、キュレーションされたナレッジを備えたスタンドアロンオンチェーンエージェントをデプロイする、またはアグリーメントとリワードで収益を得る場合に利用できます。エージェントネットワーク、エージェント調整、分散型エージェント、NOOKトークン、マイニングチャレンジ、ナレッジバンドル、エージェントレピュテーション、エージェントマーケットプレイス、ERC-2771メタトランザクション、Prepare-Sign-Relay、AgentFactory、またはNookplotが言及された場合にトリガーされます。
web3-polymarket
Polygon上でのPolymarket予測市場取引統合です。認証機能(L1 EIP-712、L2 HMAC-SHA256、ビルダーヘッダー)、注文発注(GTC/GTD/FOK/FAK、バッチ、ポストオンリー、ハートビート)、市場データ(Gamma API、Data API、オーダーブック、サブグラフ)、WebSocketストリーミング(市場・ユーザー・スポーツチャネル)、CTF操作(分割、統合、償却、ネガティブリスク)、ブリッジ機能(入金、出金、マルチチェーン)、およびガスレスリレイトランザクションに対応しています。AIエージェント、自動マーケットメーカー、予測市場UI、またはPolygraph上のPolymarketと統合するアプリケーション構築時に活用できます。
ethskills
Ethereum、EVM、またはブロックチェーン関連のリクエストに対応します。スマートコントラクト、dApps、ウォレット、DeFiプロトコルの構築、監査、デプロイ、インタラクションに適用されます。Solidityの開発、コントラクトアドレス、トークン規格(ERC-20、ERC-721、ERC-4626など)、Layer 2ネットワーク(Base、Arbitrum、Optimism、zkSync、Polygon)、Uniswap、Aave、Curveなどのプロトコルとの統合をカバーします。ガスコスト、コントラクトのデシマル設定、オラクルセキュリティ、リエントランシー、MEV、ブリッジング、ウォレット管理、オンチェーンデータの取得、本番環境へのデプロイ、プロトコル進化(EIPライフサイクル、フォーク追跡、今後の変更予定)といったトピックを含みます。
xxyy-trade
このスキルは、ユーザーが「トークン購入」「トークン売却」「トークンスワップ」「暗号資産取引」「取引ステータス確認」「トランザクション照会」「トークンスキャン」「フィード」「チェーン監視」「トークン照会」「トークン詳細」「トークン安全性確認」「ウォレット一覧表示」「マイウォレット」「AIスキャン」「自動スキャン」「ツイートスキャン」「オンボーディング」「IP確認」「IPホワイトリスト」「トークン発行」「自動売却」「損切り」「利益確定」「トレーリングストップ」「保有者」「トップホルダー」「KOLホルダー」などをリクエストした場合、またはSolana/ETH/BSC/BaseチェーンでXXYYを経由した取引について言及した場合に使用します。XXYY Open APIを通じてオンチェーン取引とデータ照会を実現します。