mcpField
G
gdscript-patterns

gdscript-patterns

github.com/jame581开发889198 次安装4 天前更新
代码仓库

关于 gdscript-patterns

面向 Godot 4.3+ 开发者的 GDScript 参考技能,提供类型、协程、Lambda、模式匹配、注解和常见惯用法的示例与说明。内容也涵盖常见陷阱及较新的语言特性。

主要功能

  1. 01静态类型与类型化集合
  2. 02await 与协程安全
  3. 03Lambda、匹配与常用惯用法
  4. 04导出注解与类定义
  5. 05常见陷阱及新语言特性

适用场景

  1. 01编写带类型提示的 GDScript
  2. 02处理信号等待与协程生命周期
  3. 03组织 Inspector 属性和脚本类

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jame581/godotprompter --skill gdscript-patterns

安装后在 Claude Code 中输入/gdscript-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
abstract-classes.md
common-idioms.md
export-annotations.md
lambda-functions.md
super-in-virtual-methods.md
variadic-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_groupInspector 中的分组标题
@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+)

评论

支持 Markdown