Install
$ agentstack add skill-jame581-godotprompter-gdscript-patterns ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
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.
# 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
# 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
# '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 on unsafe as casts | | UNSAFE_CALL_ARGUMENT | Warns when passing wrong type to a function |
> Set warnings to Error for strict enforcement in team projects.
2. Await & Coroutines
Awaiting Signals
await pauses the function until a signal fires, then resumes. The function becomes a coroutine.
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
# 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
# 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
# DANGER: node may be freed while awaiting
func unsafe_coroutine() -> void:
await get_tree().create_timer(5.0).timeout
position = Vector2.ZERO # crash if node was freed during wait!
# SAFE: check validity after await
func safe_coroutine() -> void:
await get_tree().create_timer(5.0).timeout
if not is_instance_valid(self):
return
position = Vector2.ZERO
3. Lambda Functions
Lambdas are inline anonymous functions, useful for callbacks, sorting, filtering.
Basic Syntax
# Single-expression lambda
var double := func(x: int) -> int: return x * 2
# Multi-line lambda
var greet := func(name: String) -> void:
print("Hello, %s!" % name)
print("Welcome!")
# Calling a lambda
double.call(5) # returns 10
greet.call("Player")
With Signals
# Inline signal connection (one-off use)
$Button.pressed.connect(func(): print("Button pressed!"))
# With arguments
$Timer.timeout.connect(func():
health -= 1
if health bool: return n % 2 == 0)
# [2, 4, 6, 8]
# Map — transform each element
var doubled: Array[int] = numbers.map(func(n: int) -> int: return n * 2)
# [2, 4, 6, 8, 10, 12, 14, 16]
# Reduce — accumulate into single value
var total: int = numbers.reduce(func(acc: int, n: int) -> int: return acc + n, 0)
# 36
# Any / All
var has_negative: bool = numbers.any(func(n: int) -> bool: return n bool: return n > 0)
# Sort with custom comparison
var items: Array[Dictionary] = [{"name": "B", "value": 2}, {"name": "A", "value": 1}]
items.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a["value"] Callable:
var count := start
return func() -> int:
count += 1
return count
var counter := create_counter(0)
print(counter.call()) # 1
print(counter.call()) # 2
4. Match / Pattern Matching
GDScript's match is like switch but with pattern support.
Basic Match
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
# 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 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
# 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() 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 node enters tree, just before `_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 node freed | Check `is_instance_valid(self)` after await; ensure signal fires |
| Lambda captures stale variable | Loop variable captured by reference | Copy to local var before lambda: `var local := i` |
| `UNTYPED_DECLARATION` warnings flood | Warning enabled but codebase isn't typed | Type incrementally; use `@warning_ignore` for legacy code |
| Typed array rejects valid items | Item type doesn't match exactly | Ensure items match the declared type (no implicit upcasting) |
| `@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 |
---
## 12. 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.
---
## 13. 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.
---
## 11. Implementation Checklist
- [ ] All variables, parameters, and return types have explicit type hints
- [ ] Typed arrays (`Array[Type]`) are used instead of untyped `Array` where possible
- [ ] `await` calls are followed by `is_instance_valid(self)` checks when the node could be freed
- [ ] 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+)
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [jame581](https://github.com/jame581)
- **Source:** [jame581/GodotPrompter](https://github.com/jame581/GodotPrompter)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.