462 lines
11 KiB
Markdown
462 lines
11 KiB
Markdown
---
|
|
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`
|