Godot 4 game development with GDScript. Use when creating, editing, or debugging any Godot project files (.gd, .tscn, .tres, .godot), writing GDScript code, creating scenes, or working with Godot's node system. Prevents Godot 3 mistakes.
Claude's training data contains a LOT of Godot 3 code. NEVER use these Godot 3 patterns:
Godot 3 (WRONG)
Godot 4 (CORRECT)
KinematicBody2D
CharacterBody2D
move_and_slide(velocity)
velocity = vel; move_and_slide()
Spatial
Node3D
instance()
instantiate()
rand_range(a, b)
randf_range(a, b) / randi_range(a, b)
deg2rad() / rad2deg()
deg_to_rad() / rad_to_deg()
相关技能
stepify()
snapped()
BUTTON_LEFT
MOUSE_BUTTON_LEFT
connect("signal", obj, "method")
signal.connect(method) or signal.connect(Callable(obj, "method"))
yield()
await
export var
@export var
onready var
@onready var
tool
@tool
translation (3D position)
position (now consistent with 2D)
rect_position (Control)
position
rect_size (Control)
size
RectangleShape2D.extents
RectangleShape2D.size (full size, not half)
randomize() needed
NOT needed — automatic in Godot 4
JSON parse via JSON.parse()
var json = JSON.new(); json.parse(str); json.data
GDScript 2.0 Style Guide
Naming
Files: snake_case.gd
Classes: PascalCase (class_name MyClass)
Functions/variables: snake_case
Constants/enums: CONSTANT_CASE
Private members: prefix with _underscore
Signals: past tense — health_changed, enemy_died
Booleans: prefix with is_, has_, can_ — is_alive, has_key
Always Use Static Typing
# WRONG — untyped
var health = 100
var name = "John"
func take_damage(amount):
# CORRECT — typed
var health: int = 100
var name: String = "John"
func take_damage(amount: int) -> void:
# WRONG — fragile, breaks if you rearrange scene tree
@onready var label: Label = $MarginContainer/VBoxContainer/Label
# CORRECT — unique node reference, survives rearrangement
@onready var label: Label = %Label
Mark nodes as unique in the editor (right-click → "Access as Unique Name").
For older 4.x: TileMap node with layers configured in the TileSet.
HTTP Requests (for Gemini API)
# gemini_client.gd
extends Node
const API_URL: String = "https://generativelanguage.googleapis.com/v1beta/models/"
var _api_key: String = ""
var _http: HTTPRequest
func _ready() -> void:
_http = HTTPRequest.new()
add_child(_http)
_http.request_completed.connect(_on_request_completed)
_load_api_key()
func _load_api_key() -> void:
# Load from .env or user settings — NEVER hardcode
var file := FileAccess.open("user://.env", FileAccess.READ)
if file:
_api_key = file.get_line().strip_edges()
func generate(model: String, prompt: String, system: String = "") -> void:
var url: String = API_URL + model + ":generateContent?key=" + _api_key
var body: Dictionary = {
"contents": [{"parts": [{"text": prompt}]}]
}
if system != "":
body["system_instruction"] = {"parts": [{"text": system}]}
var json: String = JSON.stringify(body)
var headers: PackedStringArray = ["Content-Type: application/json"]
_http.request(url, headers, HTTPClient.METHOD_POST, json)
func _on_request_completed(result: int, code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
if code != 200:
push_warning("Gemini API error: " + str(code))
return
var json := JSON.new()
json.parse(body.get_string_from_utf8())
var text: String = json.data["candidates"][0]["content"]["parts"][0]["text"]
# Emit signal with result or use callback
Data Serialization (Save/Load)
# Save entire world state
func save_game() -> void:
var save_data: Dictionary = {
"time": GameClock.get_state(),
"npcs": _serialize_all_npcs(),
"reputation": ReputationSystem.get_state(),
"crimes": CrimeSystem.get_state(),
}
var json: String = JSON.stringify(save_data, "\t")
var file := FileAccess.open("user://savegame.json", FileAccess.WRITE)
file.store_string(json)
func load_game() -> void:
var file := FileAccess.open("user://savegame.json", FileAccess.READ)
if not file:
return
var json := JSON.new()
json.parse(file.get_as_text())
var data: Dictionary = json.data
# Restore all systems from data
Performance Tips for Simulation
Use _physics_process for NPC movement, _process for UI only
Offscreen NPCs: reduce tick rate. Use a timer (every 2-5 seconds) instead of every frame
Group NPCs: add_to_group("npcs") → get_tree().get_nodes_in_group("npcs")
Use call_deferred() for operations that modify the scene tree
For 20+ NPCs: stagger processing — don't update all NPCs on the same frame
Use ResourceLoader.load_threaded_request() for async loading
Common Gotchas
move_and_slide() in Godot 4 takes NO arguments — set velocity property first
@onready vars are null in _init() — only use them in _ready() or later
Signals connected in editor persist across scene reloads — prefer code connections
FileAccess.open() returns null on failure — always check before using
get_node() / $ returns null if node doesn't exist — use has_node() or %UniqueNode
Timer nodes auto-start only if autostart is checked — otherwise call .start()
Area2D signals need collision layers/masks set correctly on BOTH bodies
JSON in Godot 4: use var json = JSON.new(); json.parse(text); var data = json.data37:["$","$L3f",null,{"content":"$40","frontMatter":{"name":"godot4-gdscript","description":"Godot 4 game development with GDScript. Use when creating, editing, or debugging any Godot project files (.gd, .tscn, .tres, .godot), writing GDScript code, creating scenes, or working with Godot's node system. Prevents Godot 3 mistakes."}}]