第八章:函数与方法

第八章:函数与方法

"函数是代码复用的基石,好的函数设计是优秀程序的标志。"

函数让我们能够将代码组织成可复用的模块,是编程中最重要的概念之一。本章将详细介绍GDScript中函数的定义、参数、返回值以及各种高级用法。


8.1 函数定义

8.1.1 基本语法

# 基本函数定义
func say_hello():
    print("Hello!")

# 带参数的函数
func greet(name):
    print("Hello, " + name + "!")

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

# 带类型注解的函数(推荐)
func add_typed(a: int, b: int) -> int:
    return a + b

8.1.2 函数命名规范

# 使用snake_case命名
func calculate_damage()
func get_player_health()
func set_position()

# 布尔函数使用is_/has_/can_前缀
func is_alive() -> bool
func has_weapon() -> bool
func can_attack() -> bool

# 私有函数前缀下划线
func _internal_update()
func _calculate_internal()

8.1.3 函数调用

# 简单调用
say_hello()

# 带参数调用
greet("Godot")

# 获取返回值
var result = add(10, 20)

# 链式调用
var processed = get_data().process().format()

# self调用(通常可省略)
self.say_hello()

8.2 参数详解

8.2.1 位置参数

func create_enemy(name: String, health: int, damage: int):
    print("创建敌人:", name)
    print("生命值:", health)
    print("攻击力:", damage)

# 调用时按位置传参
create_enemy("Goblin", 100, 15)

8.2.2 默认参数

func create_player(name: String, health: int = 100, mana: int = 50):
    print("名称:", name)
    print("生命:", health)
    print("魔法:", mana)

# 调用方式
create_player("Hero")           # 使用默认值
create_player("Mage", 80)       # 覆盖health
create_player("Tank", 150, 30)  # 覆盖两个

# 默认参数必须在后面
func invalid(a = 10, b):  # 错误!
    pass

8.2.3 命名参数(Godot 4.1+)

func spawn_enemy(
    type: String,
    position: Vector2 = Vector2.ZERO,
    health: int = 100,
    hostile: bool = true
):
    pass

# 使用命名参数
spawn_enemy("goblin", health = 50)
spawn_enemy("boss", position = Vector2(100, 200), health = 500)
spawn_enemy(type = "slime", hostile = false)

8.2.4 可变参数

GDScript没有直接的可变参数语法,但可以使用数组:

# 使用数组接收多个参数
func sum_all(numbers: Array) -> int:
    var total = 0
    for n in numbers:
        total += n
    return total

# 调用
var result = sum_all([1, 2, 3, 4, 5])

# 使用字典接收配置
func configure(options: Dictionary):
    var width = options.get("width", 800)
    var height = options.get("height", 600)
    var fullscreen = options.get("fullscreen", false)

8.2.5 参数传递机制

# 基本类型:值传递
func modify_int(x: int):
    x = 100  # 不影响外部

var num = 10
modify_int(num)
print(num)  # 仍然是10

# 对象类型:引用传递
func modify_array(arr: Array):
    arr.append(4)  # 影响外部

var my_array = [1, 2, 3]
modify_array(my_array)
print(my_array)  # [1, 2, 3, 4]

# 如果不想修改原数组,传递副本
modify_array(my_array.duplicate())

8.3 返回值

8.3.1 return语句

func get_damage() -> int:
    return 10

# 多个return
func get_status(health: int) -> String:
    if health <= 0:
        return "死亡"
    if health < 25:
        return "濒死"
    if health < 50:
        return "受伤"
    return "健康"

# 无返回值函数
func log_message(msg: String) -> void:
    print(msg)
    # 不需要return,或使用 return(不带值)

8.3.2 返回类型声明

# 声明返回类型
func calculate() -> float:
    return 3.14

# 返回void
func do_something() -> void:
    print("done")

# 返回任意类型
func get_value() -> Variant:
    if condition:
        return 10
    else:
        return "text"

8.3.3 返回多个值

# 使用数组
func get_minmax(numbers: Array) -> Array:
    return [numbers.min(), numbers.max()]

var result = get_minmax([3, 1, 4, 1, 5])
var min_val = result[0]
var max_val = result[1]

# 使用字典
func get_player_stats() -> Dictionary:
    return {
        "health": 100,
        "mana": 50,
        "stamina": 75
    }

var stats = get_player_stats()
print(stats.health)

# 使用类/结构
class Stats:
    var health: int
    var mana: int

func get_stats() -> Stats:
    var s = Stats.new()
    s.health = 100
    s.mana = 50
    return s

8.4 内置回调函数

Godot提供了许多自动调用的回调函数。

8.4.1 生命周期回调

# 节点实例化时调用(构造函数)
func _init():
    print("对象创建")

# 节点进入场景树时调用
func _enter_tree():
    print("进入场景树")

# 节点及所有子节点准备好后调用
func _ready():
    print("节点准备就绪")

# 节点退出场景树时调用
func _exit_tree():
    print("退出场景树")

8.4.2 处理回调

# 每帧调用(受暂停影响)
func _process(delta: float):
    # delta是距离上一帧的时间(秒)
    position.x += speed * delta

# 固定时间步调用(用于物理)
func _physics_process(delta: float):
    # 默认每秒60次
    velocity += gravity * delta
    move_and_slide()

8.4.3 输入回调

# 处理输入事件
func _input(event: InputEvent):
    if event is InputEventKey:
        if event.pressed and event.keycode == KEY_SPACE:
            jump()

# 未处理的输入
func _unhandled_input(event: InputEvent):
    # 只处理未被其他节点消费的输入
    pass

# GUI输入(用于Control节点)
func _gui_input(event: InputEvent):
    if event is InputEventMouseButton:
        if event.button_index == MOUSE_BUTTON_LEFT:
            handle_click()

8.4.4 绘制回调

# 自定义绘制(需要继承CanvasItem)
func _draw():
    draw_line(Vector2(0, 0), Vector2(100, 100), Color.RED)
    draw_circle(Vector2(50, 50), 25, Color.BLUE)
    draw_rect(Rect2(10, 10, 50, 30), Color.GREEN)

# 触发重绘
func update_visuals():
    queue_redraw()

8.5 Lambda表达式(匿名函数)

8.5.1 基本语法

# 基本lambda
var double = func(x): return x * 2

# 调用lambda
print(double.call(5))  # 10

# 多参数lambda
var add = func(a, b): return a + b
print(add.call(3, 4))  # 7

# 多行lambda
var complex = func(x):
    var result = x * 2
    result += 10
    return result

8.5.2 作为回调使用

# 信号连接
button.pressed.connect(func(): print("按钮被点击"))

# 数组操作
var numbers = [1, 2, 3, 4, 5]

# map
var doubled = numbers.map(func(x): return x * 2)
# [2, 4, 6, 8, 10]

# filter
var evens = numbers.filter(func(x): return x % 2 == 0)
# [2, 4]

# reduce
var sum = numbers.reduce(func(acc, x): return acc + x, 0)
# 15

# any/all
var has_big = numbers.any(func(x): return x > 3)  # true
var all_positive = numbers.all(func(x): return x > 0)  # true

# 排序
var items = [{"name": "b"}, {"name": "a"}, {"name": "c"}]
items.sort_custom(func(a, b): return a.name < b.name)

8.5.3 闭包

# lambda可以捕获外部变量
func create_counter() -> Callable:
    var count = 0
    return func():
        count += 1
        return count

var counter = create_counter()
print(counter.call())  # 1
print(counter.call())  # 2
print(counter.call())  # 3

# 捕获当前值
func create_callbacks() -> Array:
    var callbacks = []
    for i in range(3):
        var index = i  # 捕获当前值
        callbacks.append(func(): print(index))
    return callbacks

8.6 静态函数

8.6.1 定义静态函数

class_name MathUtils

# 静态函数不需要实例化即可调用
static func clamp_value(value: float, min_val: float, max_val: float) -> float:
    return clamp(value, min_val, max_val)

static func lerp_angle(from: float, to: float, weight: float) -> float:
    return lerp_angle(from, to, weight)

static func random_direction() -> Vector2:
    var angle = randf() * TAU
    return Vector2(cos(angle), sin(angle))

8.6.2 使用静态函数

# 通过类名调用
var result = MathUtils.clamp_value(150, 0, 100)
var dir = MathUtils.random_direction()

# 静态函数不能访问实例成员
static func example():
    # print(self.some_var)  # 错误!
    # some_method()  # 错误!
    pass

8.7 函数引用和Callable

8.7.1 获取函数引用

# 获取函数引用
var my_func: Callable = some_function

# 调用引用的函数
my_func.call()
my_func.call(arg1, arg2)

# 获取方法引用
var method_ref = self.my_method
var other_ref = other_object.other_method

8.7.2 Callable操作

var callable = some_function

# 检查是否有效
if callable.is_valid():
    callable.call()

# 绑定参数
var bound = callable.bind(arg1, arg2)
bound.call()  # 相当于 callable.call(arg1, arg2)

# 解绑参数
var unbound = bound.unbind(1)  # 移除最后一个绑定的参数

# 延迟调用
callable.call_deferred()

8.7.3 函数作为参数

# 接受函数作为参数
func apply_to_all(items: Array, operation: Callable) -> Array:
    var result = []
    for item in items:
        result.append(operation.call(item))
    return result

# 使用
func double(x): return x * 2
func square(x): return x * x

var numbers = [1, 2, 3, 4, 5]
print(apply_to_all(numbers, double))  # [2, 4, 6, 8, 10]
print(apply_to_all(numbers, square))  # [1, 4, 9, 16, 25]

# 使用lambda
print(apply_to_all(numbers, func(x): return x + 10))

8.8 协程与await

8.8.1 await基础

# 等待信号
func wait_for_click():
    await button.pressed
    print("按钮被点击了!")

# 等待定时器
func wait_seconds(seconds: float):
    await get_tree().create_timer(seconds).timeout
    print("等待结束")

# 等待帧
func wait_frame():
    await get_tree().process_frame
    print("下一帧")

8.8.2 异步函数

# 异步加载资源
func load_level_async(path: String):
    ResourceLoader.load_threaded_request(path)
    
    while true:
        var status = ResourceLoader.load_threaded_get_status(path)
        if status == ResourceLoader.THREAD_LOAD_LOADED:
            break
        await get_tree().process_frame
    
    var scene = ResourceLoader.load_threaded_get(path)
    get_tree().change_scene_to_packed(scene)

# 顺序执行异步操作
func game_sequence():
    print("开始游戏序列")
    
    await fade_in()
    await show_title()
    await wait_for_input()
    await fade_out()
    await load_level()
    
    print("序列完成")

8.8.3 自定义信号与await

signal operation_completed(result)

func async_operation():
    # 模拟异步操作
    await get_tree().create_timer(1.0).timeout
    operation_completed.emit("完成!")

func wait_for_operation():
    async_operation()  # 开始操作
    var result = await operation_completed  # 等待完成
    print("结果:", result)

8.9 最佳实践

8.9.1 函数设计原则

# 1. 单一职责:一个函数只做一件事
func calculate_damage(base: int, multiplier: float) -> int:
    return int(base * multiplier)

# 不好的示例:做了太多事
func attack_enemy_and_play_sound_and_show_effects():
    pass

# 2. 保持简短:函数应该能在一屏内看完
# 如果函数太长,考虑拆分

# 3. 清晰的命名:函数名应该描述它做什么
func get_player_health() -> int  # 好
func gph() -> int  # 不好

# 4. 避免副作用:纯函数更容易测试和理解
func pure_add(a: int, b: int) -> int:
    return a + b  # 没有副作用

# 5. 合理使用类型注解
func process_data(data: Dictionary) -> Array[String]:
    pass

8.9.2 错误处理

# 使用断言检查前置条件
func divide(a: float, b: float) -> float:
    assert(b != 0, "除数不能为零")
    return a / b

# 返回可空值
func find_item(name: String) -> Item:
    for item in items:
        if item.name == name:
            return item
    return null  # 未找到

# 使用Result模式
func load_config() -> Dictionary:
    if not FileAccess.file_exists(CONFIG_PATH):
        return {"error": "文件不存在"}
    # ...
    return {"success": true, "data": config}

8.9.3 文档注释

## 计算两点之间的伤害衰减
##
## 根据距离计算伤害值,距离越远伤害越低。
## [br][br]
## [param base_damage]: 基础伤害值
## [param distance]: 到目标的距离
## [param falloff_start]: 开始衰减的距离
## [param falloff_end]: 伤害为0的距离
## [br]
## [return]: 实际伤害值
func calculate_falloff_damage(
    base_damage: int,
    distance: float,
    falloff_start: float = 100.0,
    falloff_end: float = 500.0
) -> int:
    if distance <= falloff_start:
        return base_damage
    if distance >= falloff_end:
        return 0
    
    var falloff_range = falloff_end - falloff_start
    var falloff_distance = distance - falloff_start
    var multiplier = 1.0 - (falloff_distance / falloff_range)
    
    return int(base_damage * multiplier)

本章小结

本章我们全面学习了GDScript的函数系统:

  1. 函数定义:基本语法、命名规范、调用方式
  2. 参数系统:位置参数、默认参数、命名参数
  3. 返回值:return语句、类型声明、返回多值
  4. 内置回调:生命周期、处理循环、输入、绘制
  5. Lambda表达式:匿名函数、闭包、函数式编程
  6. 静态函数:无需实例的工具函数
  7. Callable:函数引用、高阶函数
  8. 协程:await、异步操作
  9. 最佳实践:设计原则、错误处理、文档

函数是代码组织的基本单位,掌握好函数将让你的代码更加清晰、可维护。下一章我们将学习面向对象编程,进一步提升代码的组织能力。


上一章:控制流程

下一章:面向对象编程

← 返回目录