第八章:函数与方法
"函数是代码复用的基石,好的函数设计是优秀程序的标志。"
函数让我们能够将代码组织成可复用的模块,是编程中最重要的概念之一。本章将详细介绍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的函数系统:
- 函数定义:基本语法、命名规范、调用方式
- 参数系统:位置参数、默认参数、命名参数
- 返回值:return语句、类型声明、返回多值
- 内置回调:生命周期、处理循环、输入、绘制
- Lambda表达式:匿名函数、闭包、函数式编程
- 静态函数:无需实例的工具函数
- Callable:函数引用、高阶函数
- 协程:await、异步操作
- 最佳实践:设计原则、错误处理、文档
函数是代码组织的基本单位,掌握好函数将让你的代码更加清晰、可维护。下一章我们将学习面向对象编程,进一步提升代码的组织能力。
上一章:控制流程
下一章:面向对象编程