11 KiB
11 KiB
name, description
| name | description |
|---|---|
| godot-gdscript | 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
@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
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
var int_array: Array[int] = [1, 2, 3]
var node_array: Array[Node] = []
var typed_dict: Dictionary[String, int] = {"a": 1, "b": 2}
Casting
var node := $Sprite as Sprite2D # Returns null if wrong type
if node is Sprite2D:
node.texture = some_texture
Common Patterns
Signals
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)
var health: int = 100:
get:
return health
set(value):
health = clamp(value, 0, max_health)
health_changed.emit(health)
Export Annotations
@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
@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
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
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
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+)
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
RefCountedsubclasses: Automatically freed when no referencesNodesubclasses: Must callqueue_free()orfree()- Use
weakref()to avoid reference cycles
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+)
@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:
var _internal_state: int = 0
func _calculate_damage() -> int:
return 0
Code Order
Organize scripts in this order:
@tool(if needed)@iconclass_nameextends## Documentation comments- Signals
- Enums
- Constants
@exportvariables- Public variables
- Private variables
@onreadyvariables- Virtual methods (
_init,_ready,_process, etc.) - Public methods
- Private methods
- 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:
# 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:
if (
is_valid
and has_permission
and not is_disabled
):
do_something()
Enums - One item per line:
enum State {
IDLE,
RUNNING,
JUMPING,
}
Static Typing
Static typing catches errors at compile time, improves autocompletion, and enables performance optimizations.
Basic Syntax
# 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
# 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
# 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):
# 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:
# 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
# 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
# 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 variablesINFERRED_DECLARATION- Prefer explicit over inferred typesUNSAFE_PROPERTY_ACCESS- Warn on unsafe property accessUNSAFE_METHOD_ACCESS- Warn on unsafe method calls
Enable in Editor Settings > Text Editor > Completion:
- Add Type Hints - Auto-add types on completion
Best Practices
- Use static typing - Improves performance and catches errors
- Prefer
@onready- For node references instead of_ready()assignments - Use signals - For decoupled communication between nodes
- Avoid
get_node()in_process()- Cache references with@onready - Use
StringNamefor frequent comparisons -&"my_string" - Group exports - Use
@export_groupand@export_subgroup - Document with
##- Double hash for documentation comments - Declare locals near first use - Don't declare at top of function
- Use past tense for signals -
health_depleted,door_opened - Match file name to class -
YAMLParser→yaml_parser.gd