GDScript 入门笔记(五):函数

函数是写代码时第一个需要认真掌握的"组织工具"。把一段逻辑封装成函数,以后就能反复调用,不用复制粘贴。GDScript 的函数语法比 C# 简洁很多,没有访问修饰符,类型也可以省略,但提供了足够的灵活性。这篇笔记把我自己学习函数时的经验和踩过的坑整理出来,尽量详细一点。

一、函数的基本定义

GDScript 里定义函数用 func 关键字,后面跟函数名和括号,参数放在括号里,最后用冒号结束,函数体缩进。

gdscript 复制代码
func 函数名(参数1, 参数2):
    # 函数体
    return 返回值  # 可选

最简单的函数:

gdscript 复制代码
func say_hello():
    print("你好")

调用的时候直接写函数名加括号:

gdscript 复制代码
say_hello()

命名习惯

我个人习惯函数名用蛇形命名法(snake_case),也就是所有小写字母,单词之间用下划线连接,比如 get_player_health()spawn_enemy()。Godot 官方示例也是这个风格,保持统一就好。函数名最好以动词开头,让调用者一眼知道它是干什么的。

二、参数与返回值

函数可以接收参数,也可以返回结果。参数和返回值都可以不写类型,但强烈建议写上(后面细说)。

参数

参数写在括号里,多个参数用逗号分隔:

gdscript 复制代码
func greet(name):
    print("你好," + name)

greet("Tom")

如果参数没有类型注解,你可以传任何类型的值进去,GDScript 会动态处理。但这样容易在运行时出错,因为你可能传了一个数字给一个期望字符串的函数。

返回值

return 返回结果。函数可以没有返回值,此时可以省略 return,或者写 return 不带任何值。如果函数中途需要提前退出,也可以直接用 return

gdscript 复制代码
func add(a, b):
    return a + b

var result = add(3, 5)  # 8

三、类型注解

GDScript 4 支持给函数的参数和返回值加上类型注解,写法是参数名后加冒号和类型,函数末尾用 -> 类型 指定返回类型。

gdscript 复制代码
func add(a: int, b: int) -> int:
    return a + b

如果不返回值,用 -> void

gdscript 复制代码
func log_message(msg: String) -> void:
    print(msg)

为什么建议写类型注解?

  1. 编辑器补全更智能 :类型明确后,当你写 a. 时编辑器能列出 int 类型的方法和属性,不至于瞎猜。
  2. 提前发现错误:如果你在函数里返回了错误类型,或者调用时传了错误类型,编辑器会在保存时就报错,而不是等运行到那一行才崩。
  3. 性能更好:静态类型在 GDScript 中运行更快,因为引擎不需要在运行时反复检查类型。

但也要注意,类型注解不是万能的。有些复杂类型(比如返回一个节点引用)写起来比较长,如果嫌麻烦,关键的地方写注解就够了。

四、默认参数值

GDScript 支持给参数设置默认值。调用时如果不传这个参数,就使用默认值。这个特性很常用,可以减少函数重载的需求。

gdscript 复制代码
func spawn_enemy(type: String = "normal", count: int = 1):
    for i in range(count):
        print("生成敌人:" + type)

spawn_enemy()               # 两个参数都用默认值
spawn_enemy("elite")        # type 为 "elite",count 默认 1
spawn_enemy("boss", 1)      # 都指定

注意:带默认值的参数必须放在参数列表的最后面。下面的写法会直接报错:

gdscript 复制代码
func bad_func(a = 1, b):   # 错误:带默认值的参数不能在前面
    pass

五、可变参数

有时候你不确定函数会接收多少个参数,比如写一个求和函数,想传任意个数字。GDScript 支持可变参数,在参数名前加 ...,函数内部这些参数会被打包成一个数组。

gdscript 复制代码
func sum_all(...numbers: int) -> int:
    var total = 0
    for n in numbers:
        total += n
    return total

print(sum_all(1, 2, 3))   # 6
print(sum_all(10, 20))     # 30
print(sum_all())           # 0,一个参数都没传时数组为空

注意

  • 可变参数必须放在参数列表最后。
  • 一个函数只能有一个可变参数。
  • 类型注解写在 ...参数名 后面,表示每个参数都应该是该类型。如果省略类型注解,可以传任意类型。

六、局部变量与作用域

函数内部定义的变量是局部变量,只在函数内部有效,函数执行完就销毁。外面访问不到。

gdscript 复制代码
func my_func():
    var local_var = 10
    print(local_var)  # 正常

# 在这里写 local_var 会报错:未定义

局部变量和成员变量同名的问题

脚本中,在函数外面定义的变量是成员变量,整个脚本都能访问。但如果在函数里给一个和成员变量同名的变量赋值,GDScript 默认会创建一个新的局部变量,而不是修改成员变量。

gdscript 复制代码
var health = 100   # 成员变量

func take_damage(amount):
    var health = 50        # 这是新的局部变量,不是上面的成员变量
    self.health -= amount  # 用 self. 明确指定成员变量

func _ready():
    take_damage(10)
    print(health)          # 输出 90,因为成员变量被正确修改了

如果直接写 health = 50,它只会改变局部变量,成员变量的 100 依然保留。所以我的习惯是:不要让局部变量和成员变量同名 ,避免混淆。如果一定要操作成员变量,加上 self. 前缀更明确。

七、函数重载的替代方案

C# 支持方法重载:同一个函数名可以定义多个版本,参数不同。GDScript 不支持这种重载,但有几种替代方式。

1. 使用默认参数

如果只是参数数量不同,用默认参数最方便。

gdscript 复制代码
func attack(damage: int = 10, critical: bool = false):
    # 不传参数时使用默认值

2. 在函数内部判断类型

如果参数类型不同,可以在函数体内用 is 判断类型,分别处理。

gdscript 复制代码
func process_value(value):
    if value is int:
        print("处理整数:", value)
    elif value is String:
        print("处理字符串:", value)
    elif value is Array:
        print("处理数组:", value)

3. 使用可变参数

如果参数数量变化很大,可变参数可以接收任意数量的参数,然后在函数里遍历处理。

不过这些方法都会让函数变得臃肿。如果需要处理的情况确实很多,不如拆成几个不同名字的函数,比如 process_int()process_string(),更清晰。

八、函数的传递

在 GDScript 中,函数可以被赋值给变量、作为参数传递、存储在数组里。这类似 C# 的委托,但语法更直接。

函数变量

gdscript 复制代码
func add(a, b):
    return a + b

func subtract(a, b):
    return a - b

var op = add
print(op(3, 5))   # 8

op = subtract
print(op(3, 5))   # -2

注意,把函数赋值给变量时,函数名后面不要加括号 ,加括号会立即调用函数并把结果赋给变量。op = add 是把函数本身赋给 op,然后 op(3,5) 调用它。

函数作为参数传递

函数也可以作为参数传给另一个函数,然后在内部调用。

gdscript 复制代码
func apply_operation(func_ref: Callable, a: int, b: int) -> int:
    return func_ref.call(a, b)

var result = apply_operation(add, 3, 5)  # 8

GDScript 4 中,函数引用类型是 Callable,用 call() 方法调用。不过这种用法在 GDScript 中相对少见,因为还有信号和 lambda 可以做类似的事。知道有这回事就行。

九、lambda 匿名函数

GDScript 4 支持 lambda 表达式,可以创建匿名函数,即没有名字的函数。语法是 func(参数): 返回值

gdscript 复制代码
var square = func(x): return x * x
print(square(5))  # 25

lambda 常作为参数传递给其他函数,比如数组的 mapfilter

gdscript 复制代码
var numbers = [1, 2, 3, 4]
var doubled = numbers.map(func(n): return n * 2)
print(doubled)  # [2, 4, 6, 8]

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

lambda 也可以有多行,但为了可读性,尽量保持简短。如果要写复杂逻辑,还是定义普通函数更好。

十、内置回调函数

在 Godot 中,每个节点脚本都有一些由引擎自动调用的特殊函数,这些也是函数,但我们不需要手动调用,只需要在脚本里实现它们即可。

最常用的几个:

函数名 调用时机
_ready() 节点进入场景树时调用一次,常用于初始化
_process(delta) 每帧调用,delta 是上一帧到这一帧的时间间隔(秒)
_physics_process(delta) 固定物理帧调用,适合物理相关逻辑
_input(event) 有输入事件时调用
_unhandled_input(event) 输入事件未被其他节点处理时调用
_exit_tree() 节点离开场景树时调用

示例:

gdscript 复制代码
extends Node2D

func _ready():
    print("节点准备好了")

func _process(delta):
    # 每帧执行
    position.x += 100 * delta  # 以每秒 100 像素的速度向右移动

这些回调函数是 Godot 脚本的基础,后面讲节点和场景时会更深入。

十一、写函数的几个建议

  1. 函数名用动词开头get_health()set_position()spawn_enemy(),一眼就知道功能。
  2. 函数尽量短小:一个函数只做一件事,方便测试和复用。如果一个函数超过 30 行,考虑拆分成多个小函数。
  3. 参数不要太多:如果参数超过四五个,考虑把它们封装成一个字典或自定义类型,否则调用时容易传错顺序。
  4. 及时写类型注解:重要的函数,参数和返回值都写类型,减少出错,也方便别人阅读。
  5. 善用默认参数:让函数在大多数情况下都能方便地调用,减少重复代码。

十二、总结

函数是组织代码的基本单位,GDScript 的函数语法简单但功能完整。这篇笔记详细介绍了定义、参数、返回值、默认值、可变参数、作用域、重载替代方案、函数作为一等公民、lambda 以及内置回调函数。掌握这些,日常开发基本够用了。建议多写一些自己的工具函数,慢慢积累起来,写游戏逻辑会越来越快。

下一篇我们进入数组的详细用法,看看 GDScript 的数组有哪些实用操作,包括添加、删除、查找、遍历和切片。

相关推荐
wp123_12 小时前
硬件设计笔记:1μH功率电感选型实战——线艺 XFL4020-102MEC 与 国产 pin to pin MVLH4020-1R0M 详细参数测评
笔记·科技·硬件架构·硬件工程
摇滚侠3 小时前
《SpringBoot 3:入门与应用实战》第 14 章 打包与部署 Spring Boot 应用打包 阅读笔记 41
spring boot·笔记·后端
金立基包装胶水3 小时前
纸袋热封胶常见都有哪些问题?
大数据·笔记·其他
谢斯3 小时前
[unity]如何在unity中添加newtonsoft-json
unity·json·游戏引擎
chnyi6_ya3 小时前
论文阅读笔记 | HoneyBee: Data Recipes for Vision-Language Reasoners
论文阅读·笔记
repetitiononeoneday3 小时前
【每日规划与反思】2026.9.4
笔记
江湖人称菠萝包4 小时前
【Windows】《深入浅出Windows API程序设计:编程基础篇》笔记-Chapter1-基础知识
windows·笔记
江湖人称菠萝包4 小时前
【Windows】《深入浅出Windows API程序设计:编程基础篇》笔记-Chapter2-Windows窗口程序
windows·笔记
网络工程小王4 小时前
【LLM开发实验】FastAPI 学习笔记
数据库·笔记·学习·mysql·fastapi