From better-godot-mcp
Adds GDScript 4.x game mechanics like platformer/top-down movement, health systems, inventory, and save/load. Prevents GDScript 3.x syntax errors in Godot projects.
npx claudepluginhub n24q02m/better-godot-mcpThis skill uses the workspace's default tool permissions.
Add game mechanics using correct GDScript 4.x syntax. Prevents common LLM mistakes with outdated GDScript 3.x patterns.
Provides Godot 4 GDScript patterns for architecture, signals, scenes, state machines, and optimization. Useful for building games, game systems, and best practices.
Guides Godot 4.x GDScript best practices for naming, type hints, scene organization, signals, resources, state machines, object pooling, and performance. Use when generating code, scenes, architecture, or patterns.
Builds and optimizes Godot 4 games using GDScript, node/scene architecture, signals, resources, physics, animations, UI, tilemaps, shaders, multiplayer, and best practices from prototypes to production.
Share bugs, ideas, or general feedback.
Add game mechanics using correct GDScript 4.x syntax. Prevents common LLM mistakes with outdated GDScript 3.x patterns.
These changed from Godot 3 to 4. LLMs frequently generate the OLD syntax:
| Correct (GDScript 4.x) | Wrong (GDScript 3.x -- will error) |
|---|---|
@export var speed: float = 200.0 | export var speed = 200.0 |
@onready var sprite = $Sprite2D | onready var sprite = $Sprite2D |
signal health_changed(new_hp: int) | signal health_changed (no typed params) |
func _ready() -> void: | func _ready(): (return type optional but preferred) |
velocity = Vector2(...) then move_and_slide() | move_and_slide(velocity, Vector2.UP) (args removed in 4.x) |
super() | .method() for parent calls |
await get_tree().create_timer(1.0).timeout | yield(get_tree().create_timer(1.0), "timeout") |
%UniqueNode | get_node("path/to/node") when unique name is set |
extends CharacterBody2D
@export var speed: float = 300.0
@export var jump_velocity: float = -400.0
@export var gravity: float = 980.0
func _physics_process(delta: float) -> void:
# Gravity
if not is_on_floor():
velocity.y += gravity * delta
# Jump
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
# Horizontal movement
var direction := Input.get_axis("move_left", "move_right")
velocity.x = direction * speed
move_and_slide()
Key: move_and_slide() takes NO arguments in Godot 4. Velocity is set on the velocity property directly.
extends CharacterBody2D
@export var speed: float = 200.0
func _physics_process(_delta: float) -> void:
var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity = direction * speed
move_and_slide()
extends CharacterBody2D
@export var speed: float = 200.0
var target_position: Vector2
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
target_position = get_global_mouse_position()
func _physics_process(_delta: float) -> void:
if position.distance_to(target_position) > 5.0:
velocity = position.direction_to(target_position) * speed
else:
velocity = Vector2.ZERO
move_and_slide()
extends Node
class_name HealthComponent
signal health_changed(new_hp: int, max_hp: int)
signal died
@export var max_hp: int = 100
var current_hp: int
func _ready() -> void:
current_hp = max_hp
func take_damage(amount: int) -> void:
current_hp = maxi(current_hp - amount, 0)
health_changed.emit(current_hp, max_hp)
if current_hp <= 0:
died.emit()
func heal(amount: int) -> void:
current_hp = mini(current_hp + amount, max_hp)
health_changed.emit(current_hp, max_hp)
Usage: Add as child node of any entity. Connect died signal to handle death logic.
Pattern: Component node (not inheritance) -- allows reuse on Player, Enemy, Destructible, etc.
extends Node
class_name Inventory
signal item_added(item: ItemResource)
signal item_removed(item: ItemResource)
@export var max_slots: int = 20
var items: Array[ItemResource] = []
func add_item(item: ItemResource) -> bool:
if items.size() >= max_slots:
return false
items.append(item)
item_added.emit(item)
return true
func remove_item(item: ItemResource) -> void:
items.erase(item)
item_removed.emit(item)
func has_item(item_name: String) -> bool:
return items.any(func(i: ItemResource) -> bool: return i.name == item_name)
Item resource:
extends Resource
class_name ItemResource
@export var name: String
@export var description: String
@export var icon: Texture2D
@export var stackable: bool = false
@export var max_stack: int = 1
Use Resource (not Node) for items -- they are data, not scene objects.
extends Node
const SAVE_PATH := "user://save_data.cfg"
func save_game() -> void:
var config := ConfigFile.new()
config.set_value("player", "position", player.global_position)
config.set_value("player", "health", player.health)
config.set_value("game", "level", current_level)
config.save(SAVE_PATH)
func load_game() -> void:
var config := ConfigFile.new()
if config.load(SAVE_PATH) != OK:
return # No save file
player.global_position = config.get_value("player", "position", Vector2.ZERO)
player.health = config.get_value("player", "health", 100)
current_level = config.get_value("game", "level", 1)
func save_game_json() -> void:
var data := {
"player": { "x": player.global_position.x, "y": player.global_position.y, "hp": player.health },
"inventory": items.map(func(i: ItemResource) -> String: return i.name),
"timestamp": Time.get_datetime_string_from_system()
}
var file := FileAccess.open("user://save.json", FileAccess.WRITE)
file.store_string(JSON.stringify(data, "\t"))
func load_game_json() -> void:
if not FileAccess.file_exists("user://save.json"):
return
var file := FileAccess.open("user://save.json", FileAccess.READ)
var data: Dictionary = JSON.parse_string(file.get_as_text())
player.global_position = Vector2(data.player.x, data.player.y)
Use user:// path (NOT res://) for save files -- res:// is read-only in exported games.
scripts(action="create", path="res://scripts/<name>.gd", content="...")
nodes(action="set_property", scene="<scene>.tscn", node_path="<node>", property="script", value="res://scripts/<name>.gd")
editor(action="run_scene", scene="<scene>.tscn")