关于 gdscript-patterns
面向 Godot 4.3+ 开发者的 GDScript 参考技能,提供类型、协程、Lambda、模式匹配、注解和常见惯用法的示例与说明。内容也涵盖常见陷阱及较新的语言特性。
主要功能
- 01静态类型与类型化集合
- 02await 与协程安全
- 03Lambda、匹配与常用惯用法
- 04导出注解与类定义
- 05常见陷阱及新语言特性
适用场景
- 01编写带类型提示的 GDScript
- 02处理信号等待与协程生命周期
- 03组织 Inspector 属性和脚本类
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill gdscript-patterns安装后在 Claude Code 中输入/gdscript-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/abstract-classes.mdcommon-idioms.mdexport-annotations.mdlambda-functions.mdsuper-in-virtual-methods.mdvariadic-functions.md详细介绍
Godot 4.3+ 中的 GDScript 模式
所有示例均适用于 Godot 4.3+,不使用已弃用的 API。
相关技能: gdscript-advanced 提供面向生产环境的深度内容(性能惯用法、元编程、@tool 生命周期、基于性能分析器的惯用法),godot-code-review 介绍风格规则和反模式,csharp-godot 介绍从 GDScript 到 C# 的转换,state-machine 介绍状态模式,event-bus 介绍信号架构。
注意: 本技能专为 GDScript 设计。如需了解 C# 模式,请参阅 csharp-godot 和 csharp-signals。
1. 静态类型
类型提示
始终添加类型提示——它们能在解析时捕获错误、改进自动补全并提升性能。
# 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 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)
使用 as 和 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
启用严格类型警告
在 Project > Project Settings > Debug > GDScript 中:
| 警告 | 效果 |
|---|---|
UNTYPED_DECLARATION | 对任何未指定类型的变量或参数发出警告 |
INFERRED_DECLARATION | 对 := 发出警告(建议使用显式类型) |
UNSAFE_CAST | 将未指定类型(Variant)的值转换为某个类型时发出警告 |
UNSAFE_CALL_ARGUMENT | 参数类型是形参类型的超类型时发出警告(将 Node 传入 Node2D 参数) |
在团队项目中,将警告设为 Error 以严格执行。
覆写时的类型化返回继承
⚠️ Godot 4.7 中的变更: 覆写具有类型化返回值的方法时,现在会继承其返回类型,因此未显式使用
return语句的覆写会报错。请在覆写末尾添加return null(或类型化返回值)。请参阅 4.7 迁移指南。
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 与协程
等待信号
await 会暂停函数,直到信号触发后再继续执行。该函数会成为协程。
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()
等待并获取返回值
# 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")
计时器模式
# 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)
协程安全
# 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
节点被释放后,其自身的协程不会恢复执行,因此在 await 后检查 is_instance_valid(self) 毫无作用。
3. Lambda 函数
Lambda 是用于回调、排序和筛选的内联匿名函数,通过 .call() 调用。参考内容涵盖单行和多行语法、内联信号连接(包括 CONNECT_ONE_SHOT)、Array 方法(filter、map、reduce、any、all、sort_custom)以及闭包(局部变量按值捕获)。
语法、信号、
Array方法和闭包示例,请参阅 references/lambda-functions.md。
4. Match / 模式匹配
GDScript 的 match 类似于 switch,但支持模式匹配。
基本 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)
模式类型
# 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 会将变量公开到检查器中。提示变体(@export_range、@export_enum、@export_file)可限制编辑器中的输入。使用 @export_group 和 @export_subgroup 进行整理。节点和资源导出使用 NodePath / 类型化 Resource 引用。
完整的导出注解目录(基本导出、范围/提示变体、分组、节点和 Resource 导出),请参阅 references/export-annotations.md。
6. 内部类与 class_name
class_name
将脚本注册为全局类名——无需 preload 即可在任何位置使用。
# 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] = []
内部类
# 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()
当你重写引擎会调用的虚方法(_ready、_process、_input 等),且父类也实现了该方法时,请调用 super(),以衔接父类的行为。忘记调用 super._ready() 是导致“我的基类初始化没运行”这类问题的最常见原因。
完整模式(问题 / 修复)、C# 中等效的
base.X()写法,以及因遗漏 super 调用导致的问题示例,请参阅 references/super-in-virtual-methods.md。
8. 常见惯用写法
常见的小型模式:三元表达式(value if cond else other)、printf 风格的字符串格式化("%s %d" % [a, b])、空值 / 空内容检查(is_instance_valid 与 != null 的区别、空 Array / String 检查)、Dictionary 访问(get(key, default))、Array 操作(Array.has、Array.find、Array.has_all),以及通过 set 和 get 访问器实现的 setget。
每种惯用写法的完整代码示例,请参阅 references/common-idioms.md。
9. 注解参考
| 注解 | 用途 |
|---|---|
@export | 在 Inspector 中显示变量 |
@export_range | 数值范围滑块 |
@export_enum | 从字符串列表中选择的下拉菜单 |
@export_file | 文件路径选择器 |
@export_dir | 目录选择器 |
@export_multiline | 多行文本框 |
@export_group | Inspector 中的分组标题 |
@export_subgroup | 子分组标题 |
@export_category | 类别分隔符 |
@onready | 节点就绪时初始化,即在 _ready() 函数体运行之前 |
@tool | 在编辑器中运行脚本 |
@icon | 脚本的自定义图标 |
@warning_ignore | 忽略下一行的特定警告 |
@static_unload | 允许释放静态变量 |
10. 常见问题
| 症状 | 原因 | 修复方法 |
|---|---|---|
as 转换静默返回 null | 类型不匹配——as 不会报错 | 先用 is 检查,再进行转换 |
| await 永远不会恢复执行 | 信号从未发出,或此节点已被释放(协程被丢弃) | 等待前先检查条件;让慢速信号与计时器竞速 |
| Lambda 对变量所做的更改丢失 | 局部变量按值捕获 | 将共享状态保存在成员变量、Array 或 Dictionary 中 |
UNTYPED_DECLARATION 警告大量出现 | 已启用警告,但代码库尚未进行类型标注 | 逐步添加类型;对旧代码使用 @warning_ignore |
| 类型化数组赋值失败 | 将未类型化的 Array 或 Array[Subclass] 赋给 Array[Type] 变量(不支持协变;但接受子类 元素) | 从一开始就声明为类型化数组,或使用 typed.assign(other) |
@onready 为 null | 在 _ready() 运行前访问 | 切勿在 _init() 或变量声明中访问 @onready 变量 |
| Match 没有进入任何分支 | 没有匹配的模式,也没有 _: 通配分支 | 始终添加 _: 默认分支 |
class_name 冲突 | 两个脚本使用了相同的 class_name | 使用唯一名称;在 Project 中检查重复项 |
| 导出分组应用到了错误的变量 | 分组作用域会持续到下一个分组 | 添加新的 @export_group(""),结束当前分组作用域 |
子类中的父类 _ready() 逻辑没有运行 | 子类的 _ready() 中缺少 super() 调用 | 将 super() 添加为第一行;参见第 7 节 |
type_exists() 被标记为已弃用 | 在 Godot 4.7 中已弃用 | 改用 ClassDB.class_exists() |
⚠️ Godot 4.7 中的变更: 全局
type_exists()函数已弃用——将type_exists("Sprite2D")替换为ClassDB.class_exists("Sprite2D")。请参阅 GH-116899。
11. 可变参数函数(Godot 4.5+)
Godot 4.5 新增了通过 ...args 接收尾随参数数组的功能。参数会被收集到一个 Array 中。适用于 printf 风格的辅助函数,以及无需重载即可灵活使用的 API。
完整语法、常见模式,以及何时优先使用重载的说明,请参阅 references/variadic-functions.md。
12. 抽象类和方法(Godot 4.5+)
@abstract 注解可阻止直接实例化类,并强制子类实现所有带有 @abstract 注解的方法(类似于 C# 的 abstract 关键字)。
基类的完整模式和子类实现规则,请参阅 references/abstract-classes.md。
13. 实现检查清单
- 所有变量、参数和返回值类型都具有显式类型提示
- 尽可能使用类型化数组(
Array[Type])代替未指定类型的Array - 在
await之后使用的其他节点,都要通过is_instance_valid()重新检查(self无需检查) - 连接到信号的 Lambda 应保持简单——复杂逻辑放到具名方法中
-
match语句包含_:默认分支 -
@export变量使用适当的提示(@export_range、@export_enum等) - 使用
@export_group将 Inspector 属性整理到逻辑分组中 - 仅对需要全局可见性的脚本使用
class_name - 类型无法确定时,先使用
is进行类型检查,再使用as进行类型转换 - 带有 setter 的属性会验证并限制值的范围
- 重写虚方法以扩展非内置基类时,会调用
super() - 尾随参数数量不固定时,使用可变参数函数(
...args)(Godot 4.5+) - 不可实例化的基类使用
@abstract;必需的方法使用@abstract func(Godot 4.5+)