强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Godot 4 GDScript 教程 / 函数与 Lambda

函数与 Lambda

函数是 GDScript 的核心构建单元。Godot 4 中函数系统得到了显著增强,引入了 Lambda 表达式、Callable 对象和 await 协程,让代码组织更加灵活。本章详细介绍 GDScript 2.0 的函数系统。

1. 函数定义

1.1 基本语法

# 基本函数
func say_hello() -> void:
    print("Hello, Godot!")

# 带参数的函数
func greet(name: String) -> void:
    print(f"Hello, {name}!")

# 带返回值的函数
func add(a: int, b: int) -> int:
    return a + b

# 多参数带默认值
func spawn_enemy(
    enemy_type: String,
    position: Vector2 = Vector2.ZERO,
    level: int = 1,
    is_boss: bool = false
) -> void:
    print(f"生成 {enemy_type} 于 {position} Lv.{level}" + (" [BOSS]" if is_boss else ""))

# 调用示例
func _ready() -> void:
    say_hello()
    greet("Player")
    var result := add(10, 20)
    print(f"10 + 20 = {result}")
    spawn_enemy("slime", Vector2(100, 200))
    spawn_enemy("dragon", Vector2(500, 300), 10, true)

1.2 参数传递

# 值类型参数(int, float, bool, String, Vector2 等)是值传递
func modify_value(val: int) -> void:
    val = 999  # 不会影响原始变量

# 引用类型参数(Node, Array, Dictionary 等)是引用传递
func modify_array(arr: Array) -> void:
    arr.append(4)  # 会影响原始数组
    # 但是 arr = [1,2,3] 这样赋值不会影响原始数组

func _ready() -> void:
    var x := 10
    modify_value(x)
    print(x)  # 输出: 10 (未改变)
    
    var numbers := [1, 2, 3]
    modify_array(numbers)
    print(numbers)  # 输出: [1, 2, 3, 4] (已修改)

1.3 返回值

# 单返回值
func get_health() -> int:
    return health

# 多返回值(使用 Array)
func get_position_and_velocity() -> Array:
    return [position, velocity]

# 使用 Dictionary 返回多个值
func get_stats() -> Dictionary:
    return {
        "health": health,
        "mana": mana,
        "attack": attack
    }

# 使用命名元组模式
func get_transform_info() -> Array:
    var pos := Vector2(100, 200)
    var rot := 45.0
    var scl := Vector2(2, 2)
    return [pos, rot, scl]

func _ready() -> void:
    # 解构多返回值
    var info := get_position_and_velocity()
    var pos: Vector2 = info[0]
    var vel: Vector2 = info[1]
    
    # Dictionary 方式
    var stats := get_stats()
    print(f"HP: {stats['health']}, MP: {stats['mana']}")

2. 静态函数

2.1 static 函数

class_name MathUtils

# 静态函数 - 不需要实例即可调用
static func clamp_vector2(value: Vector2, min_val: float, max_val: float) -> Vector2:
    return Vector2(
        clampf(value.x, min_val, max_val),
        clampf(value.y, min_val, max_val)
    )

static func lerp_angle_deg(a: float, b: float, t: float) -> float:
    return rad_to_deg(lerp_angle(deg_to_rad(a), deg_to_rad(b), t))

static func random_point_in_circle(radius: float) -> Vector2:
    var angle := randf() * TAU
    var dist := sqrt(randf()) * radius
    return Vector2(cos(angle), sin(angle)) * dist

static func ease_out_quad(t: float) -> float:
    return 1.0 - (1.0 - t) * (1.0 - t)

static func ease_in_out_cubic(t: float) -> float:
    if t < 0.5:
        return 4.0 * t * t * t
    else:
        return 1.0 - pow(-2.0 * t + 2.0, 3) / 2.0

# 使用静态函数
func _ready() -> void:
    var clamped := MathUtils.clamp_vector2(Vector2(1000, 500), 0, 800)
    var random_pos := MathUtils.random_point_in_circle(200.0)
    var eased := MathUtils.ease_out_quad(0.5)

2.2 static 变量

class_name GameSettings

# 静态变量 - 全局共享
static var master_volume: float = 1.0
static var music_volume: float = 0.8
static var sfx_volume: float = 1.0
static var difficulty: String = "normal"

static func apply_settings() -> void:
    AudioServer.set_bus_volume_db(
        AudioServer.get_bus_index("Master"),
        linear_to_db(master_volume)
    )

static func get_difficulty_multiplier() -> float:
    match difficulty:
        "easy": return 0.75
        "normal": return 1.0
        "hard": return 1.5
        "nightmare": return 2.0
    return 1.0

⚠️ 注意: 静态函数不能访问实例变量或调用非静态函数。静态变量在所有实例间共享。

3. Lambda 表达式

3.1 基本 Lambda

# Lambda 表达式(匿名函数)
var greet = func(name: String) -> void:
    print(f"Hello, {name}!")

# 调用 Lambda
func _ready() -> void:
    greet.call("Player")
    
    # 立即调用
    func(x: int) -> int:
        return x * 2
    .call(5)  # 返回 10,但结果无法获取(Godot 4.x 限制)
    
    # 存储 Lambda
    var multiply = func(a: int, b: int) -> int:
        return a * b
    var result: int = multiply.call(3, 4)
    print(f"3 * 4 = {result}")

3.2 Lambda 作为回调

# Lambda 用作回调函数
extends Node

func _ready() -> void:
    var timer := Timer.new()
    timer.wait_time = 1.0
    timer.autostart = true
    add_child(timer)
    
    # 使用 Lambda 连接信号
    var counter := 0
    timer.timeout.connect(func():
        counter += 1
        print(f"计时器触发: {counter}次")
        if counter >= 5:
            timer.stop()
    )

# Lambda 作为函数参数
func process_items(items: Array, callback: Callable) -> void:
    for item in items:
        callback.call(item)

func _process_items_example() -> void:
    var names := ["Alice", "Bob", "Charlie"]
    
    process_items(names, func(name: String) -> void:
        print(f"处理: {name}")
    )

3.3 Lambda 捕获变量

# Lambda 可以捕获外部变量(闭包)
extends Node

func create_counter(start: int = 0) -> Callable:
    var count := start
    var counter = func() -> int:
        count += 1
        return count
    return counter

func create_multiplier(factor: int) -> Callable:
    return func(value: int) -> int:
        return value * factor

func _ready() -> void:
    # 计数器闭包
    var counter := create_counter(10)
    print(counter.call())  # 11
    print(counter.call())  # 12
    print(counter.call())  # 13
    
    # 乘法器闭包
    var double := create_multiplier(2)
    var triple := create_multiplier(3)
    print(double.call(5))  # 10
    print(triple.call(5))  # 15

💡 提示: Lambda 表达式可以捕获外部变量形成闭包,这在创建回调和高阶函数时非常有用。

4. Callable 对象

4.1 Callable 基础

extends Node

# 创建 Callable
var callable_1: Callable = my_function          # 从函数创建
var callable_2: Callable = Callable(self, "my_function")  # 显式创建
var callable_3: Callable = func(): pass         # Lambda

func my_function() -> void:
    print("函数被调用")

# Callable 方法
func _ready() -> void:
    # 调用
    callable_1.call()
    
    # 检查
    print(callable_1.is_valid())        # true
    print(callable_1.get_method())      # "my_function"
    print(callable_1.get_object())      # self (节点引用)
    
    # 带参数调用
    var add = func(a: int, b: int) -> int:
        return a + b
    print(add.call(3, 4))  # 7
    
    # 绑定参数
    var add_5 = add.bind(5)
    print(add_5.call(10))  # 15 (10 + 5)

4.2 Callable 与信号

extends Node

signal health_changed(old_value: int, new_value: int)
signal died

@onready var timer: Timer = $Timer

func _ready() -> void:
    # 连接信号使用 Callable
    health_changed.connect(_on_health_changed)
    died.connect(func(): print("角色死亡"))
    
    # 绑定额外参数
    health_changed.connect(_on_health_changed_with_context.bind("玩家"))
    
    # 一次性连接
    died.connect(_on_died_once, CONNECT_ONE_SHOT)
    
    # Lambda 连接
    var damage_count := 0
    health_changed.connect(func(old_val: int, new_val: int):
        if new_val < old_val:
            damage_count += 1
            print(f"受伤次数: {damage_count}")
    )

func _on_health_changed(old_value: int, new_value: int) -> void:
    print(f"生命值变化: {old_value} -> {new_value}")

func _on_health_changed_with_context(
    old_value: int, new_value: int, context: String
) -> void:
    print(f"{context} 生命值: {old_value} -> {new_value}")

func _on_died_once() -> void:
    print("只执行一次的死亡处理")

5. 信号与 Callable

# 信号连接的新语法
extends CharacterBody2D

signal damaged(amount: int)
signal healed(amount: int)
signal level_up(new_level: int)

@onready var area: Area2D = $HurtBox
@onready var anim_player: AnimationPlayer = $AnimationPlayer

func _ready() -> void:
    # 新 Callable 语法
    area.body_entered.connect(_on_body_entered)
    area.area_entered.connect(_on_area_entered)
    
    # Lambda 连接
    damaged.connect(func(amount: int):
        print(f"受到 {amount} 点伤害")
    )
    
    # 信号链 - 一个信号触发另一个信号
    healed.connect(func(_amount: int):
        # 治疗时播放特效
        anim_player.play("heal_effect")
    )

func _on_body_entered(body: Node2D) -> void:
    if body.is_in_group("enemies"):
        var damage: int = body.get("attack_damage") if body.has_method("get") else 10
        take_damage(damage)

func _on_area_entered(area: Area2D) -> void:
    if area.is_in_group("health_pickup"):
        heal(25)
        area.queue_free()

func take_damage(amount: int) -> void:
    damaged.emit(amount)

func heal(amount: int) -> void:
    healed.emit(amount)

6. await 协程

6.1 基本 await

# await 等待信号
extends Node

@onready var timer: Timer = $Timer

func start_countdown(seconds: int) -> void:
    for i in range(seconds, 0, -1):
        print(f"倒计时: {i}")
        await get_tree().create_timer(1.0).timeout
    print("开始!")

# 等待动画完成
func play_attack_animation() -> void:
    var anim_player: AnimationPlayer = $AnimationPlayer
    anim_player.play("attack")
    await anim_player.animation_finished
    print("攻击动画完成")

# 等待自定义信号
signal loaded

func load_data() -> void:
    print("开始加载...")
    # 模拟加载
    await get_tree().create_timer(2.0).timeout
    loaded.emit()

func wait_for_load() -> void:
    await loaded
    print("数据加载完成")

6.2 await 与协程模式

extends CharacterBody2D

# 协程式移动
func move_to(target: Vector2, duration: float) -> void:
    var start_pos := global_position
    var elapsed := 0.0
    
    while elapsed < duration:
        var t := elapsed / duration
        global_position = start_pos.lerp(target, t)
        elapsed += get_process_delta_time()
        await get_tree().process_frame
    
    global_position = target

# 序列化动作
func perform_combo() -> void:
    print("连招开始")
    await attack("slash")
    await get_tree().create_timer(0.2).timeout
    await attack("thrust")
    await get_tree().create_timer(0.3).timeout
    await attack("uppercut")
    print("连招完成")

func attack(anim_name: String) -> void:
    var anim_player: AnimationPlayer = $AnimationPlayer
    anim_player.play(anim_name)
    await anim_player.animation_finished

# 带超时的等待
func wait_with_timeout(seconds: float) -> bool:
    await get_tree().create_timer(seconds).timeout
    return true

# 使用自定义信号的协程
signal input_received(input_vector: Vector2)

func wait_for_input() -> Vector2:
    var input := await input_received
    return input

6.3 await 注意事项

# ⚠️ await 的常见陷阱

# 陷阱 1: await 后对象可能已被释放
func bad_example() -> void:
    await get_tree().create_timer(1.0).timeout
    # 节点可能已从场景树移除
    queue_free()  # 可能报错

# 正确做法:检查 is_instance_valid
func good_example() -> void:
    await get_tree().create_timer(1.0).timeout
    if not is_instance_valid(self):
        return
    queue_free()

# 陷阱 2: 多个 await 的执行顺序
func multiple_awaits() -> void:
    # 这两个是顺序执行的
    await get_tree().create_timer(1.0).timeout
    print("1秒后")
    await get_tree().create_timer(1.0).timeout
    print("2秒后")

# 陷阱 3: await 在 for 循环中
func loop_with_await() -> void:
    for i in range(5):
        await get_tree().create_timer(0.5).timeout
        print(f"步骤 {i + 1}")

⚠️ 注意: 使用 await 时,代码在信号发出后才会继续执行。如果节点在等待期间被释放,会导致错误。始终检查 is_instance_valid(self)

7. 函数式编程模式

7.1 数组的函数式操作

# map - 转换每个元素
func _ready() -> void:
    var numbers := [1, 2, 3, 4, 5]
    
    # 平方每个数
    var squared := numbers.map(func(x: int) -> int:
        return x * x
    )
    print(squared)  # [1, 4, 9, 16, 25]
    
    # 转换为字符串
    var strings := numbers.map(func(x: int) -> String:
        return f"Item-{x}"
    )
    print(strings)  # ["Item-1", "Item-2", ...]

# filter - 过滤元素
    var even := numbers.filter(func(x: int) -> bool:
        return x % 2 == 0
    )
    print(even)  # [2, 4]

# reduce - 聚合
    var sum := numbers.reduce(func(acc: int, x: int) -> int:
        return acc + x
    , 0)
    print(sum)  # 15

# any - 是否存在满足条件的元素
    var has_large := numbers.any(func(x: int) -> bool:
        return x > 3
    )
    print(has_large)  # true

# all - 是否所有元素满足条件
    var all_positive := numbers.all(func(x: int) -> bool:
        return x > 0
    )
    print(all_positive)  # true

7.2 自定义高阶函数

# 高阶函数:接受或返回函数的函数
class_name FunctionalUtils

static func pipe(value: Variant, functions: Array[Callable]) -> Variant:
    """管道:按顺序应用多个函数"""
    var result := value
    for fn in functions:
        result = fn.call(result)
    return result

static func compose(functions: Array[Callable]) -> Callable:
    """组合:创建一个依次执行多个函数的新函数"""
    return func(value: Variant) -> Variant:
        var result := value
        for fn in functions:
            result = fn.call(result)
        return result

static func debounce(callback: Callable, delay: float, node: Node) -> Callable:
    """防抖:延迟执行,重复调用时重新计时"""
    var timer: SceneTreeTimer
    return func(args: Variant = null) -> void:
        timer = node.get_tree().create_timer(delay)
        timer.timeout.connect(func():
            callback.call(args)
        , CONNECT_ONE_SHOT)

# 使用示例
func _ready() -> void:
    var double = func(x: int) -> int: return x * 2
    var add_one = func(x: int) -> int: return x + 1
    var to_string = func(x: int) -> String: return str(x)
    
    # 管道
    var result := FunctionalUtils.pipe(5, [double, add_one, to_string])
    print(result)  # "11" (5 -> 10 -> 11 -> "11")
    
    # 组合
    var transform := FunctionalUtils.compose([double, add_one])
    print(transform.call(5))  # 11

8. get_method_list 内省

# 方法内省:在运行时获取对象的方法信息
extends Node

func _ready() -> void:
    # 获取所有方法
    var methods := get_method_list()
    
    # 打印方法信息
    for method in methods:
        print(f"方法: {method['name']}, 参数: {method['args'].size()}, 返回: {method['return']['type']}")
    
    # 检查方法是否存在
    if has_method("custom_function"):
        call("custom_function")
    
    # 搜索特定方法
    var process_methods := methods.filter(func(m: Dictionary) -> bool:
        return m["name"].begins_with("_process")
    )
    print(f"找到 {process_methods.size()} 个 process 方法")

func custom_function() -> void:
    print("自定义函数被调用")

# 动态调用方法
func call_method_on_children(method_name: String, args: Array = []) -> void:
    for child in get_children():
        if child.has_method(method_name):
            child.callv(method_name, args)

9. 游戏开发场景

场景:行为树系统

class_name BehaviorTree
extends Node

## 行为树节点类型
enum Status { SUCCESS, FAILURE, RUNNING }

## 行为树节点
class BTNode:
    var name: String
    var children: Array[BTNode] = []
    
    func tick(blackboard: Dictionary) -> Status:
        return Status.FAILURE

## 行为节点(执行动作)
class BTAction extends BTNode:
    var action: Callable
    
    func _init(action_name: String, action_callable: Callable) -> void:
        name = action_name
        action = action_callable
    
    func tick(blackboard: Dictionary) -> Status:
        return action.call(blackboard)

## 条件节点
class BTCondition extends BTNode:
    var condition: Callable
    
    func _init(cond_name: String, cond_callable: Callable) -> void:
        name = cond_name
        condition = cond_callable
    
    func tick(blackboard: Dictionary) -> Status:
        return Status.SUCCESS if condition.call(blackboard) else Status.FAILURE

## 序列节点(所有子节点都成功才成功)
class BTSequence extends BTNode:
    var current_child: int = 0
    
    func tick(blackboard: Dictionary) -> Status:
        while current_child < children.size():
            var status := children[current_child].tick(blackboard)
            match status:
                Status.FAILURE:
                    current_child = 0
                    return Status.FAILURE
                Status.RUNNING:
                    return Status.RUNNING
                Status.SUCCESS:
                    current_child += 1
        current_child = 0
        return Status.SUCCESS

## 选择节点(任一子节点成功就成功)
class BTSelector extends BTNode:
    var current_child: int = 0
    
    func tick(blackboard: Dictionary) -> Status:
        while current_child < children.size():
            var status := children[current_child].tick(blackboard)
            match status:
                Status.SUCCESS:
                    current_child = 0
                    return Status.SUCCESS
                Status.RUNNING:
                    return Status.RUNNING
                Status.FAILURE:
                    current_child += 1
        current_child = 0
        return Status.FAILURE

10. 扩展阅读


上一章: 05 - 类型化 GDScript 下一章: 07 - 信号系统