--- name: godot-gdscript description: "Develops GDScript code for Godot 4.6 projects. Use when writing, editing, or debugging GDScript files, creating nodes, signals, exports, or any Godot game development task." --- # Godot 4.6 GDScript Development Write idiomatic GDScript for Godot 4.6 following these conventions and language features. ## File Structure ```gdscript @tool # Optional: runs in editor @icon("res://path/to/icon.svg") # Optional: custom icon class_name MyClass # Optional: registers global class extends BaseClass # Required (defaults to RefCounted) ## Signals signal health_changed(old_value, new_value) ## Enums enum State { IDLE, RUNNING, JUMPING } ## Constants const MAX_SPEED = 100.0 ## Exports (inspector properties) @export var speed: float = 50.0 @export_range(0, 100) var health: int = 100 ## Public variables var velocity: Vector2 = Vector2.ZERO ## Private variables (convention: prefix with _) var _internal_state: int = 0 ## @onready variables (initialized in _ready) @onready var sprite: Sprite2D = $Sprite2D ## Static variables static var instance_count: int = 0 ## Built-in virtual methods func _init(): pass func _ready(): pass func _process(delta: float) -> void: pass func _physics_process(delta: float) -> void: pass ## Public methods func take_damage(amount: int) -> void: pass ## Private methods func _calculate_something() -> float: return 0.0 ## Static methods static func get_instance_count() -> int: return instance_count ``` ## Type System ### Type Hints ```gdscript var my_int: int = 5 var my_string: String = "hello" var my_node: Node2D = null var inferred := Vector2(1, 2) # Type inferred as Vector2 func my_func(param: int, optional: String = "default") -> bool: return true ``` ### Typed Arrays and Dictionaries ```gdscript var int_array: Array[int] = [1, 2, 3] var node_array: Array[Node] = [] var typed_dict: Dictionary[String, int] = {"a": 1, "b": 2} ``` ### Casting ```gdscript var node := $Sprite as Sprite2D # Returns null if wrong type if node is Sprite2D: node.texture = some_texture ``` ## Common Patterns ### Signals ```gdscript signal damage_taken(amount: int) func take_damage(amount: int) -> void: health -= amount damage_taken.emit(amount) # Connecting signals func _ready(): $Button.pressed.connect(_on_button_pressed) # With bound arguments $Enemy.died.connect(_on_enemy_died.bind(enemy_name)) func _on_button_pressed(): pass ``` ### Properties (Setters/Getters) ```gdscript var health: int = 100: get: return health set(value): health = clamp(value, 0, max_health) health_changed.emit(health) ``` ### Export Annotations ```gdscript @export var speed: float = 10.0 @export_range(0, 100, 1) var health: int = 100 @export_enum("Walk", "Run", "Jump") var movement: int @export_file("*.png") var texture_path: String @export_dir var save_directory: String @export_node_path("Sprite2D", "Sprite3D") var sprite_path: NodePath @export_flags("Fire", "Water", "Earth", "Wind") var elements: int @export_group("Movement") @export var walk_speed: float @export var run_speed: float @export_subgroup("Physics") @export var gravity: float ``` ### Node References ```gdscript @onready var player := $Player as CharacterBody2D @onready var unique_node := %UniqueNodeName # Scene-unique node # Get nodes dynamically var child = get_node("Path/To/Child") var parent = get_parent() ``` ### Awaiting Signals/Coroutines ```gdscript func async_operation() -> void: await get_tree().create_timer(1.0).timeout print("1 second passed") var result = await some_async_func() print(result) ``` ### Match Statements ```gdscript match state: State.IDLE: play_idle_animation() State.RUNNING: play_run_animation() _: push_warning("Unknown state") # Pattern matching with guards match point: [var x, var y] when x == y: print("On diagonal") [var x, var y]: print("Point: ", x, ", ", y) ``` ### Lambda Functions ```gdscript var double = func(x): return x * 2 print(double.call(5)) # 10 # With type hints var add: Callable = func(a: int, b: int) -> int: return a + b ``` ### Variadic Functions (4.5+) ```gdscript func log_values(prefix: String, ...values: Array) -> void: for v in values: print(prefix, v) ``` ## Built-in Types Reference | Type | Description | |------|-------------| | `null` | Empty value | | `bool` | `true` or `false` | | `int` | 64-bit integer | | `float` | 64-bit float | | `String` | Unicode string | | `StringName` | Interned string (fast comparison) | | `NodePath` | Path to node/property | | `Vector2/Vector2i` | 2D vector | | `Vector3/Vector3i` | 3D vector | | `Rect2` | 2D rectangle | | `Transform2D/Transform3D` | Transformation matrix | | `Color` | RGBA color | | `Array` | Dynamic array | | `Dictionary` | Key-value map | | `Callable` | Function reference | | `Signal` | Signal reference | ## Memory Management - `RefCounted` subclasses: Automatically freed when no references - `Node` subclasses: Must call `queue_free()` or `free()` - Use `weakref()` to avoid reference cycles ```gdscript var weak = weakref(some_object) if weak.get_ref(): weak.get_ref().do_something() if is_instance_valid(node): node.queue_free() ``` ## Abstract Classes (4.5+) ```gdscript @abstract class_name BaseEnemy extends CharacterBody2D @abstract func attack() -> void class Zombie extends BaseEnemy: func attack() -> void: # Must implement abstract method melee_attack() ``` ## Style Guide ### Naming Conventions | Type | Convention | Example | |------|------------|---------| | File names | snake_case | `yaml_parser.gd` | | Class names | PascalCase | `class_name YAMLParser` | | Node names | PascalCase | `Camera3D`, `Player` | | Functions | snake_case | `func load_level():` | | Variables | snake_case | `var particle_effect` | | Signals | snake_case (past tense) | `signal door_opened` | | Constants | CONSTANT_CASE | `const MAX_SPEED = 200` | | Enum names | PascalCase | `enum Element` | | Enum members | CONSTANT_CASE | `{EARTH, WATER, AIR}` | ### Private Members Prepend underscore for private/virtual methods and variables: ```gdscript var _internal_state: int = 0 func _calculate_damage() -> int: return 0 ``` ### Code Order Organize scripts in this order: 1. `@tool` (if needed) 2. `@icon` 3. `class_name` 4. `extends` 5. `## Documentation comments` 6. Signals 7. Enums 8. Constants 9. `@export` variables 10. Public variables 11. Private variables 12. `@onready` variables 13. Virtual methods (`_init`, `_ready`, `_process`, etc.) 14. Public methods 15. Private methods 16. Inner classes ### Formatting Rules **Indentation**: Use tabs, not spaces. **Blank lines**: - Two blank lines between functions/class definitions - One blank line to separate logical sections within functions **Line length**: Keep under 100 characters (prefer 80). **Comments**: ```gdscript # Regular comment (space after #) ## Documentation comment #var disabled_code # No space for commented code #region Region Name #endregion ``` **Multiline statements** - Use parentheses, put `and`/`or` at line start: ```gdscript if ( is_valid and has_permission and not is_disabled ): do_something() ``` **Enums** - One item per line: ```gdscript enum State { IDLE, RUNNING, JUMPING, } ``` ## Static Typing Static typing catches errors at compile time, improves autocompletion, and enables performance optimizations. ### Basic Syntax ```gdscript # Variables var health: int = 100 var velocity: Vector2 = Vector2.ZERO var player: CharacterBody2D # Type inference with := var speed := 10.0 # Inferred as float var position := Vector2.ZERO # Inferred as Vector2 # Constants (type optional but recommended for arrays) const MAX_SPEED: float = 200.0 const ITEMS: Array[String] = ["sword", "shield"] # Function parameters and return types func calculate_damage(base: int, multiplier: float) -> int: return int(base * multiplier) # Void return type func apply_damage(amount: int) -> void: health -= amount ``` ### Typed Collections ```gdscript # Typed arrays var enemies: Array[Enemy] = [] var scores: Array[int] = [10, 20, 30] # Typed dictionaries (Godot 4.4+) var inventory: Dictionary[String, int] = {"gold": 100} var stats: Dictionary[StringName, float] = {} # Typed for loops for enemy: Enemy in enemies: enemy.take_damage(10) for name: String in ["Alice", "Bob"]: print(name) ``` ### Type Casting ```gdscript # Safe casting with 'as' (returns null if wrong type) var sprite := $Sprite as Sprite2D if sprite: sprite.texture = new_texture # Type checking with 'is' if node is CharacterBody2D: var body: CharacterBody2D = node body.move_and_slide() # Casting node references for safe lines @onready var timer := $Timer as Timer @onready var player := $Player as CharacterBody2D ``` ### Safe vs Unsafe Lines The editor marks lines green (safe) or grey (unsafe): ```gdscript # Safe - type is known var timer: Timer = $Timer as Timer # Unsafe - type could fail at runtime var timer = $Timer # No type info ``` ### Typed Global Scope Methods Use typed versions for better performance: ```gdscript # Prefer typed versions var node: Node = instance_from_id(id) as Node # Safe var clamped: int = clampi(value, 0, 100) # Typed int clamp var lerped: float = lerpf(0.0, 1.0, 0.5) # Typed float lerp # Typed math functions var abs_val: int = absi(-5) var max_val: float = maxf(a, b) var min_val: int = mini(a, b) ``` ### Safe Property/Method Access ```gdscript # Unsafe - warnings if property doesn't exist on type func process_node(node: Node2D) -> void: if "custom_property" in node: node.custom_property = 10 # UNSAFE_PROPERTY_ACCESS warning # Safe - cast first, then access func process_node_safe(node: Node2D) -> void: if node is MyScript: var typed: MyScript = node as MyScript typed.custom_property = 10 # Safe ``` ### Limitations ```gdscript # Cannot type individual array/dict elements inline var arr = [1, "two", 3.0] # Mixed types - can't specify # Nested typed collections not supported var nested: Array[Array[int]] # ERROR - not allowed # Use this instead var nested: Array[Array] = [[1, 2], [3, 4]] ``` ### Recommended Editor Settings Enable in **Project Settings > Debug > GDScript**: - `UNTYPED_DECLARATION` - Warn on untyped variables - `INFERRED_DECLARATION` - Prefer explicit over inferred types - `UNSAFE_PROPERTY_ACCESS` - Warn on unsafe property access - `UNSAFE_METHOD_ACCESS` - Warn on unsafe method calls Enable in **Editor Settings > Text Editor > Completion**: - **Add Type Hints** - Auto-add types on completion ## Best Practices 1. **Use static typing** - Improves performance and catches errors 2. **Prefer `@onready`** - For node references instead of `_ready()` assignments 3. **Use signals** - For decoupled communication between nodes 4. **Avoid `get_node()` in `_process()`** - Cache references with `@onready` 5. **Use `StringName` for frequent comparisons** - `&"my_string"` 6. **Group exports** - Use `@export_group` and `@export_subgroup` 7. **Document with `##`** - Double hash for documentation comments 8. **Declare locals near first use** - Don't declare at top of function 9. **Use past tense for signals** - `health_depleted`, `door_opened` 10. **Match file name to class** - `YAMLParser` → `yaml_parser.gd`