Skip to content
Skills Library
Programming · Learning & explanation

gdscript-patterns

/gdscript-patterns

Use when writing GDScript — static typing, await/coroutines, lambdas, match patterns, export annotations, inner classes, and common idioms

Licence
MIT
Last repository change
24.10.2026

Assessment and security

100/ 100Class: good
Format25 / 25
Frontmatter with name and description, valid name, description up to 1,024 characters, name matching the folder.
Discoverability25 / 25
Description explains when to use the skill, has a reasonable length and uses third person.
Content35 / 35
At least 150 words and at most 500 lines, headings, step lists, examples or code.
Hygiene15 / 15
No placeholders such as TODO, no Windows paths, with a licence.
Riskno signals detected in the scan
Files in this package
7
Scripts
0
HTML comments
0
URLs in SKILL.md
2
Deprecation

No signals detected. Repository changed on 24.10.2026, without references to retired models or APIs.

Licence

MIT, open: use and modify while retaining author attribution.

What it does and when to use it

Use when writing GDScript — static typing, await/coroutines, lambdas, match patterns, export annotations, inner classes, and common idioms

Original author description

Use when writing GDScript — static typing, await/coroutines, lambdas, match patterns, export annotations, inner classes, and common idioms

How it works

  1. 1. Static Typing
  2. 2. Await & Coroutines
  3. 3. Lambda Functions
  4. 4. Match / Pattern Matching
  5. 5. Export Annotations
  6. 6. Inner Classes & classname
  7. 7. super() in Virtual Methods
  8. 8. Common Idioms
  9. 9. Annotations Reference
  10. 10. Common Pitfalls
Original SKILL.md headings · 389 lines, 1,895 words
SKILL.md preview
---
name: gdscript-patterns
description: Use when writing GDScript — static typing, await/coroutines, lambdas, match patterns, export annotations, inner classes, and common idioms
---

# GDScript Patterns in Godot 4.3+

All examples target Godot 4.3+ with no deprecated APIs.

> **Related skills:** **gdscript-advanced** for production-grade depth (performance idioms, metaprogramming, @tool lifecycle, profiler-driven idioms), **godot-code-review** for style rules and anti-patterns, **csharp-godot** for GDScript-to-C# translation, **state-machine** for state patterns, **event-bus** for signal architecture.

> **Note:** This skill is GDScript-specific by design. For C# patterns, see **csharp-godot** and **csharp-signals**.

---

## 1. Static Typing

### Type Hints

Always add type hints — they catch bugs at parse time, improve autocomplete, and boost performance.

```gdscript
# Variables
var health: int = 100
var speed: float = 200.0
var player_name: String = "Hero"
var direction: Vector2 = Vector2.ZERO

# Constants
const MAX_HEALTH: int = 100
const GRAVITY: float = 980.0

# Functions — parameters and return type
func take_damage(amount: int) -> void:
    health -= amount

func get_direction() -> Vector2:
    return Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")

# Inferred typing with :=
var pos := Vector2(100, 200)     # inferred as Vector2
var items := []                  # inferred as Array (untyped)
var count := 0                   # inferred as int
```

### Typed Collections

```gdscript
# Typed arrays — only accepts the specified type
var enemies: Array[Enemy] = []
var scores: Array[int] = [10, 20, 30]
var names: Array[String] = ["Alice", "Bob"]

# Typed dictionaries (Godot 4.4+)
var inventory: Dictionary[String, int] = {"sword": 1, "potion": 5}

# Typed loop variable
for enemy: Enemy in enemies:
    enemy.take_damage(10)

# Typed array methods work with type safety
var filtered: Array[Enemy] = enemies.filter(func(e: Enemy) -> bool: return e.health > 0)
```

### Casting with `as` and `is`

```gdscript
# 'is' — type check (returns bool)
func _on_body_entered(body: Node2D) -> void:
    if body is Player:
        var player: Player = body as Player
        player.take_damage(10)

# 'as' — cast (returns null on failure, no error)
var sprite := get_node("Sprite") as Sprite2D
if sprite:
    sprite.modulate = Color.RED

# Prefer 'is' check + cast over bare 'as' to avoid null surprises
```

### Enabling Strict Typing Warnings

In **Project > Project Settings > Debug > GDScript**:

| Warning | Effect |
|-------------------------|---------------------------------------------|
| `UNTYPED_DECLARATION` | Warns on any untyped variable/parameter |
| `INFERRED_DECLARATION` | Warns on `:=` (prefers explicit types) |
| `UNSAFE_CAST` | Warns when an untyped (`Variant`) value is cast to a type |
| `UNSAFE_CALL_ARGUMENT` | Warns when an argument's type is a supertype of the parameter's (`Node` into a `Node2D` parameter) |

> Set warnings to **Error** for strict enforcement in team projects.

### Typed Return Inheritance in Overrides

> ⚠️ **Changed in Godot 4.7:** Methods that override a method with a typed return now inherit the return type, so an override without an explicit `return` statement becomes an error. Add `return null` (or a typed return value) at the end of the override. See the [4.7 migration guide](https://docs.godotengine.org/en/latest/tutorials/migrating/upgrading_to_godot_4.7.html).

```gdscript
class Enemy:
    var weapon: Node
    func get_weapon() -> Node:
        return weapon

class UnarmedEnemy extends Enemy:
    func get_weapon():  # 4.7+: inherits -> Node from Enemy
        return null     # explicit return now required — omitting it is an error
```

---

## 2. Await & Coroutines

### Awaiting Signals

`await` pauses the function until a signal fires, then resumes. The function becomes a coroutine.

```gdscript
func death_sequence() -> void:
    $AnimationPlayer.play("death")
    await $AnimationPlayer.animation_finished  # pauses here

    $Sprite2D.visible = false
    await get_tree().create_timer(1.0).timeout  # wait 1 second

    queue_free()
```

### Awaiting with Return Values

```gdscript
# Signal that passes data
signal dialogue_choice_made(choice: int)

func show_dialogue(options: Array[String]) -> int:
    # ... display UI ...
    var choice: int = await dialogue_choice_made
    return choice

# Caller:
func _on_npc_interact() -> void:
    var result := await show_dialogue(["Yes", "No"])
    if result == 0:
        print("Player said yes")
```

### Timer Patterns

```gdscript
# One-shot delay
await get_tree().create_timer(0.5).timeout

# Repeating with await (simple but blocks the function)
for i in 5:
    do_something()
    await get_tree().create_timer(0.2).timeout

# Non-blocking timer — use SceneTreeTimer or Tween instead
get_tree().create_timer(2.0).timeout.connect(_on_delayed_action)
```

### Coroutine Safety

```gdscript
# DANGER: `target` may be freed while this coroutine waits
func unsafe_coroutine(target: Node2D) -> void:
    await get_tree().create_timer(5.0).timeout
    target.position = Vector2.ZERO  # error: previously freed instance

# SAFE: re-check other objects after every await
func safe_coroutine(target: Node2D) -> void:
    await get_tree().create_timer(5.0).timeout
    if not is_instance_valid(target):
        return
    target.position = Vector2.ZERO
```

A freed node's own coroutine never resumes, so `is_instance_valid(self)` after `await` guards nothing.

---

## 3. Lambda Functions

Lambdas are inline anonymous functions for callbacks, sorting, and filtering, called with `.call()`. The reference covers single- and multi-line syntax, inline signal connections including `CONNECT_ONE_SHOT`, `Array` methods (`filter`, `map`, `reduce`, `any`, `all`, `sort_custom`), and closures, where locals are captured by value.

> See [references/lambda-functions.md](references/lambda-functions.md) for the syntax, signal, `Array` method, and closure examples.

---

## 4. Match / Pattern Matching

GDScript's `match` is like `switch` but with pattern support.

### Basic Match

```gdscript
match state:
    State.IDLE:
        play_idle()
    State.RUNNING:
        play_run()
    State.JUMPING, State.FALLING:  # multiple patterns
        play_air()
    _:  # default (wildcard)
        push_warning("Unknown state: %s" % state)
```

### Pattern Types

```gdscript
# Literal patterns
match value:
    42:
        print("The answer")
    "hello":
        print("Greeting")
    true:
        print("Boolean true")

# Binding pattern — captures value into a variable
match command:
    ["move", var direction]:
        move(direction)
    ["attack", var target, var damage]:
        attack(target, damage)

# Array pattern
match input:
    [1, 2, 3]:
        print("Exact match")
    [1, ..]:
        print("Starts with 1")
    [var first, _, var last]:
        print("First: %s, Last: %s" % [first, last])

# Dictionary pattern
match event:
    {"type": "damage", "amount": var amt}:
        take_damage(amt)
    {"type": "heal", "amount": var amt}:
        heal(amt)

# Nested condition inside a branch
match enemy_type:
    "boss":
        if health < 50:
            enter_rage_mode()
        else:
            normal_attack()
```

---

## 5. Export Annotations

`@export` exposes a variable to the Inspector. Hint variants (`@export_range`, `@export_enum`, `@export_file`) constrain editor input. Use `@export_group` and `@export_subgroup` to organize. Node and Resource exports use NodePath / typed Resource references.

> See [references/export-annotations.md](references/export-annotations.md) for the full export annotation catalog (basic exports, range/hint variants, groups, node and Resource exports).

---

## 6. Inner Classes & class_name

### class_name

Register a script as a global class name — available everywhere without `preload`.

```gdscript
# item_data.gd
class_name ItemData
extends Resource

@export var name: String
@export var icon: Texture2D
@export var value: int

# Now usable anywhere:
# var item: ItemData = ItemData.new()
# var items: Array[ItemData] = []
```

### Inner Classes

```gdscript
# Define a class inside another script
class HitResult:
    var damage: int
    var critical: bool
    var knockback: Vector2

    func _init(dmg: int, crit: bool, kb: Vector2 = Vector2.ZERO) -> void:
        damage = dmg
        critical = crit
        knockback = kb

# Usage
func calculate_hit() -> HitResult:
    var crit := randf() < 0.2
    var dmg := 10 * (2 if crit else 1)
    return HitResult.new(dmg, crit, Vector2.RIGHT * 50)
```

---

## 7. super() in Virtual Methods

When you override a virtual method that the engine calls (`_ready`, `_process`, `_input`, etc.) and your parent class also implements it, call `super()` to chain the parent's behavior. Forgetting to call `super._ready()` is the most common cause of "my base class init didn't run" bugs.

> See [references/super-in-virtual-methods.md](references/super-in-virtual-methods.md) for the full pattern (problem / fix), the C# `base.X()` equivalent, and a catalog of bugs from missing super calls.

---

## 8. Common Idioms

The recurring small patterns: ternary expressions (`value if cond else other`), printf-style string formatting (`"%s %d" % [a, b]`), null/empty checks (`is_instance_valid` vs `!= null`, empty Array/String checks), Dictionary access (`get(key, default)`), Array operations (`Array.has`, `Array.find`, `Array.has_all`), setget via `set` and `get` accessors.

> See [references/common-idioms.md](references/common-idioms.md) for full code examples of each idiom.

---

## 9. Annotations Reference

| Annotation | Purpose |
|-----------------------|--------------------------------------------|
| `@export` | Expose variable in Inspector |
| `@export_range` | Numeric with slider |
| `@export_enum` | Dropdown from string list |
| `@export_file` | File path picker |
| `@export_dir` | Directory picker |
| `@export_multiline` | Multi-line text box |
| `@export_group` | Group heading in Inspector |
| `@export_subgroup` | Subgroup heading |
| `@export_category` | Category divider |
| `@onready` | Initialize when the node is ready, just before its `_ready()` body runs |
| `@tool` | Run script in editor |
| `@icon` | Custom icon for the script |
| `@warning_ignore` | Suppress specific warning on next line |
| `@static_unload` | Allow static variables to be freed |

---

## 10. Common Pitfalls

| Symptom | Cause | Fix |
|---------------------------------------|----------------------------------------------|------------------------------------------------------------------|
| `as` cast silently returns `null` | Type mismatch — `as` doesn't error | Use `is` check first, then cast |
| Await never resumes | Signal never emitted, or this node was freed (coroutine dropped) | Check the condition before awaiting; race slow signals against a timer |
| Lambda's change to a variable is lost | Locals are captured by value | Keep shared state in a member, `Array` or `Dictionary` |
| `UNTYPED_DECLARATION` warnings flood | Warning enabled but codebase isn't typed | Type incrementally; use `@warning_ignore` for legacy code |
| Typed array assignment fails | An untyped `Array` or an `Array[Subclass]` assigned to an `Array[Type]` variable (no covariance; subclass *elements* are accepted) | Declare it typed from the start, or `typed.assign(other)` |
| `@onready` is `null` | Accessed before `_ready()` runs | Never access `@onready` vars in `_init()` or variable declarations |
| Match doesn't enter any branch | No matching pattern and no `_:` wildcard | Always add `_:` default branch |
| `class_name` conflict | Two scripts with same `class_name` | Use unique names; check for duplicates in Project |
| Export group applies to wrong vars | Group scope continues until next group | Add a new `@export_group("")` to end the group scope |
| Parent `_ready()` logic doesn't run in child | Missing `super()` call in child's `_ready()` | Add `super()` as first line; see Section 7 |
| `type_exists()` flagged as deprecated | Deprecated in Godot 4.7 | Use `ClassDB.class_exists()` instead |

> ⚠️ **Changed in Godot 4.7:** The global `type_exists()` function is deprecated — replace `type_exists("Sprite2D")` with `ClassDB.class_exists("Sprite2D")`. See [GH-116899](https://github.com/godotengine/godot/pull/116899).

---

## 11. Variadic Functions (Godot 4.5+)

Godot 4.5 added trailing-argument arrays via `...args`. The args are collected into an `Array`. Useful for printf-style helpers and flexible APIs without overloads.

> See [references/variadic-functions.md](references/variadic-functions.md) for the full syntax, common patterns, and notes on when to prefer overloads.

---

## 12. Abstract Classes and Methods (Godot 4.5+)

The `@abstract` annotation prevents direct instantiation of a class and forces subclasses to implement any `@abstract`-annotated method (similar to C#'s `abstract` keyword).

> See [references/abstract-classes.md](references/abstract-classes.md) for full base-class patterns and subclass implementation rules.

---

## 13. Implementation Checklist

- [ ] All variables, parameters, and return types have explicit type hints
- [ ] Typed arrays (`Array[Type]`) are used instead of untyped `Array` where possible
- [ ] Other nodes used after an `await` are re-checked with `is_instance_valid()` (`self` needs no check)
- [ ] Lambdas connected to signals are simple — complex logic goes in named methods
- [ ] `match` statements include a `_:` default branch
- [ ] `@export` variables use appropriate hints (`@export_range`, `@export_enum`, etc.)
- [ ] `@export_group` organizes Inspector properties into logical sections
- [ ] `class_name` is only used for scripts that need global visibility
- [ ] `is` type check precedes `as` cast when the type isn't guaranteed
- [ ] Properties with setters validate and clamp values
- [ ] Overridden virtual methods call `super()` when extending non-built-in base classes
- [ ] Variadic functions (`...args`) used when the number of trailing arguments is open-ended (Godot 4.5+)
- [ ] Base classes that must not be instantiated use `@abstract`; required methods use `@abstract func` (Godot 4.5+)
Files in this package (7)
  • SKILL.md 14 KB
  • references/abstract-classes.md 3 KB
  • references/common-idioms.md 3 KB
  • references/export-annotations.md 2 KB
  • references/lambda-functions.md 2 KB
  • references/super-in-virtual-methods.md 3 KB
  • references/variadic-functions.md 2 KB

Versions

RepositoryScoreLicence
This page · jame581/godotprompter100MIT
zenobi-us/dotfiles100MIT

Database snapshot: 13.11.2026 · package version c76db1bf6b9d (SHA-256 digest of the entire package)

Downloads in the last 30 days: 0 · ranking

Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.

Similar skills

  • 100godot-gdscript
    Write idiomatic GDScript for Godot 4.7: static typing, the node lifecycle (_ready/_process/_physics_process), @export/@onready/@tool annotations, signals, and await for asynchronous flow. Use when editing .gd scripts in a Godot project (project.godot), writing or debugging…
  • 100gdscript-advanced
    Use when writing production-grade GDScript — performance idioms, metaprogramming, @tool lifecycle, async pitfalls, signal/Callable trade-offs, profiler-driven idioms, and common pitfalls
  • 90godot-gdscript-mastery
    Expert GDScript landmine guidance: static typing opcodes, signal-up/call-down, %UniqueName/@onready lifecycle, Callable bind/unbind, await sequences, typed collections, and safe Dictionary iteration. Use for code review, refactoring hot paths, or project standards. Trigger…
  • 100gdscript-advanced
    Use when writing production-grade GDScript — performance idioms, metaprogramming, @tool lifecycle, async pitfalls, signal/Callable trade-offs, profiler-driven idioms, and common pitfalls
  • 100godot4-gdscript
    Writes idiomatic, typed Godot 4 GDScript and avoids the Godot 3 patterns that AI models and old tutorials still produce. Covers static typing, @export/@onready, signals with callables, await, scene composition, autoloads, resources, input actions, CharacterBody2D/3D movement,…
  • 100godot-gdscript-patterns
    Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or learning GDScript best practices.

Feedback and reports

Your message goes to Bartosz Sokoliński. An email address is needed only for a reply. See the privacy policy.