Skip to content

函数

显然,我们将所有的游戏逻辑都写 _ready _process 并不现实,并且我们也总不能都使用复制粘贴来复用之前代码。

在编码时也总会遇到很多重复逻辑的代码,这时,函数就派上了用场。

gdscript
func is_upper(s: String) -> bool:
    if s.length() != 1: 
        return false
    if not s.is_valid_ascii_identifier():
        return false
    if s.is_valid_int() or s == "_":
        return false
    return s.to_upper() == s

语法

gdscript
func 函数名(参数1: 参数类型, 参数2: 参数类型) -> 返回类型:
    pass

函数名和参数名应为合法标识符

函数名不能和已有变量和函数重名

函数可以有零或多个参数,参数之间不能够重名

空实现函数应该使用pass,来确保语法正确

若函数体只含一行语句,则可以将函数及其函数体缩在同一行语句内编写

gdscript
func hello_world(): print("Hello World")

参数

参数一般是函数逻辑中所必须要或可能要用到的数据

相当于委托工厂生产产品,必须要交付工厂所需的原材料

函数可以没有参数,也可以有多个参数,多个参数间用逗号分割

参数可以同变量一样在函数内部直接使用

可以为参数指定类型也可以不指定类型,不指定类型时如变量一样其类型是Variant,调用函数传参时也会缺少类型检查

若想要接受任意类型的参数,可以不显式指定参数类型,或指定类型为Variant

参数始终按值传递,修改参数不会影响原本的值

引用类型参数传递的是引用的副本,为其赋予一个新的实例不会修改原本的对象

gdscript
func _ready():
    var node = Node.new()
    node.name = "a"
    print(node) # 输出 a:<Node#实例id>
    print(node.name) # 输出 a
    foo(node)
    print(node) # 输出同第四行
    print(node.name) # 输出 a
    
    var b = 100
    print(b) # 输出 100
    foo_1(b)
    print(b) # 输出 100
    
func foo(a: Node):
    a = Node.new() # 为参数赋予一个新的实例
    a.name = "b" # 不再是同一个实例,属性的修改不会共享
    
func foo_1(a: int):
    a = 10

可选参数

可以为函数的参数提供默认值,使之成为可选参数

相当于委托工厂生产产品,必须要交付其原材料,但是有些原材料工厂它自己有

可选参数只能在所有必选参数之后

可以有多个可选参数

可选参数同样可以指定类型,使用自动类型推断

不能指定向某个可选参数传参,必须从左到右依次传入

gdscript
func foo(a, b, c = 3.14, d: int = 10, e := true):
    pass

可变参数

可变参数不限制可传入的参数数量,每个参数之间使用逗号间隔

在GDScript中称做剩余参数(Rest Parameters)

在Godot 4.5中,新增了对GDScript函数可变参数的支持

  • 在参数名前加上三个点,声明参数为可变参数

  • 参数类型必须为一个数组

  • 参数必须位于整个参数列表最末尾

  • 每个函数只能声明一个可变参数列表

之后你可以在函数内遍历该数组以获取调用方传入的剩余参数列表

gdscript
func f(a: int, b: int = 0, ...args: Array):
    prints(a, b, args)

func _ready() -> void:
    f(1) # 1 0 []
    f(1, 2) # 1 2 []
    f(1, 2, 3) # 1 2 [3]
    f(1, 2, 3, 4) # 1 2 [3, 4]
    f(1, 2, 3, 4, 5) # 1 2 [3, 4, 5]

返回值

可以为函数指定返回类型,用以给调用方提供信息,到时候对面就能使用这个值

相当于委托工厂生产产品,工厂要把产出的成品交给你

当不指定返回类型时,函数的返回值取决于具体实现,如果实现中没有返回任何值,那么函数的返回值为null

不指定返回类型时,函数可以返回任何值也可以不返回值

显式指定返回类型时,所有代码路径必须返回一个值,但void除外

void代表无返回值,可以使用 return 关键字提前返回结束函数的执行,但不能返回任何值。

gdscript
func foo() -> void:
    return
    print("Hello World!") # 不会被执行

返回使用return关键字,其后跟上一个表达式,表示返回的值,有返回类型的函数必须返回与返回值类型相匹配的值。

如果不返回值,后面什么都不需要加上

return将会结束函数的执行,其后的代码都不会被执行

下面的代码示例中并非所有的代码路径都返回一个值,因此会触发报错

要解决这个问题,需要在if语句外返回一个值

gdscript
func foo(s: String) -> bool:
    if s.length() != 1:
        return false

void 函数 必须 返回一个值,如果你的代码具有分支语句(例如 if/else 构造),则所有可能的路径都必须有返回值。例如,如果在 if 块内有一个 return,但在其后没有,则编辑器将抛出一个错误,因为如果该代码块未执行,那么该函数将没有值进行有效返回。

想要函数返回任何类型的值,可以不显式指定返回类型

也可以指定返回类型为Variant,但显式指定返回类型后必须所有代码路径都返回一个Variant

函数只能返回一个值,如果想要返回多个值,可以使用Array或者Dictionary,或嵌套起来

最常见的方法是使用一个Dictionary一个键值对表示一个返回值,调用方只需要查询对应的键值对即可,详见后续的集合章节

调用

gdscript
func add(a:int, b:int) -> int: return a + b
func hello_world() -> void: print("Hello World")
func print_message(msg := "Hello World") -> void: print(msg)

函数的逻辑只有被调用才会被执行,函数会依据调用顺序由上到下依次执行,后面的函数会等待前面的函数执行完之后才会执行

调用函数时必须传入所有的必选参数,使用逗号分割传入的参数,参数的类型必须与函数需求的类型相匹配

gdscript
func _ready() -> void:
    hello_world()
    print_message()
    print(add(1, 2))

对于具有返回值的函数,调用该函数后,你可以使用一个对应类型的变量来接收该函数的返回值,储存其结果。

当然,你也可以不储存函数的返回结果,直接使用其返回值:

  • 直接将返回bool类型的函数调用作为if等语句的条件表达式;

  • 将一个返回int类型的函数调用,传入需求int类型参数的方法;

  • 使用返回float类型的函数调用参与数学运算

gdscript
func _ready() -> void:
    var result: int = add(1, 2)
    print(result) # 3
gdscript
if is_upper("S"):
    print("is upper!")

递归

函数可以在自身中调用自身,这种用法叫做递归(Recursion),适合解决一些分解性问题,比如阶乘或斐波那契数列

但是必须要为递归函数设定一个结束条件,也就是需要在合适的地方return

否则就会陷入无限递归导致堆栈溢出

Stack overflow (stack size: 1024). Check for infinite recursion in your script.

gdscript
func get_all_children(node: Node) -> Array[Node]:
    var nodes: Array[Node] = []
    for child in node.get_children():
        nodes.append(child)
        nodes.append_array(get_all_children(child))
    return nodes
gdscript
func get_scene_root(node: Node) -> Node:
    if node.get_parent() == null or node.get_parent() == get_tree().get_root():
        return node
    return get_scene_root(node.get_parent())
gdscript
func copy_array(arr: Array) -> Array:
    var new_arr = []
    for i in arr:
        if i is Array:
            new_arr.append(copy_array(i))
            continue
        new_arr.append(i)
    return new_arr

调用函数时,会将函数压入调用栈,函数返回时会将函数弹出调用栈

get_scene_root(Level3)和这个场景树结构为例

gdscript
Root (Viewport)
└── Level1
    └── Level2
        └── Level3 (传入的 node)

练习

  1. 实现函数is_even(n: int) -> bool,判断一个整数是否为偶数,如果是偶数返回 true,否则返回 false

  2. 实现函数 first_char(s: String) -> String,返回传入字符串的第一个字符。如果字符串为空,返回 ""

  3. 手动实现函数 max(a: int, b: int) -> int,返回两个整数中的较大值,不能内置的max函数

  4. 手动实现函数 abs_val(n: int) -> int,返回这个整数的绝对值,不能使用内置的abs函数

  5. 实现函数 is_capital_letter(s: String) -> bool,如果字符串是单个大写英文字母,则返回 true

  6. 实现函数is_leap_year(year: int) -> bool,判断某年份是否是闰年(4年一闰,百年不闰,四百年再闰)

  7. 实现函数count_char(s: String, target: String) -> int,返回目标字符target在字符串s中出现的次数

  8. 实现函数find_char(s: String, target: String) -> int,返回目标字符target在字符串s中第一次出现的位置,如果不存在则返回-1

  9. 实现函数strlen(s: String) -> int,返回字符串的长度,不能使用 .length()

  10. 实现函数 count_to(n: int) -> void,递归打印从 1 到 n 的所有整数(每行一个)

    gdscript
    count_to(3)
    # 输出:
    # 1
    # 2
    # 3
  11. 实现函数factorial(n: int) -> int,递归计算 n 的阶乘(n! = n * (n-1)!,特别地, 0! = 1, 1! = 1

    gdscript
    factorial(5) # 返回 120
  12. 实现函数countdown(n: int) -> void,递归打印从 n 到 1

    gdscript
    countdown(3)
    # 输出:
    # 3
    # 2
    # 1
  13. 实现函数fib(n: int) -> int,返回斐波那契数列的第 n 项(从 0 开始)

    gdscript
    fib(0) # 返回 0
    fib(1) # 返回 1
    fib(2) # 返回 1
    fib(3) # 返回 2
    fib(4) # 返回 3

    fib(n) = fib(n-1) + fib(n-2),要注意考虑 n=0 和 n=1 的终止条件

  14. 实现函数reverse(s: String) -> String,返回传入字符串的逆序结果

    gdscript
    reverse("abcd") # 返回 "dcba"
  15. 实现函数 strlen_rec(s: String) -> int,使用递归来计算字符串的长度,不用 .length()for

  16. 实现函数 digit_sum(n: int) -> int,返回一个整数的所有位的和

    gdscript
    digit_sum(1234) # 返回 10 (1+2+3+4)
  17. 实现函数 is_palindrome(s: String) -> bool,递归判断字符串是否是回文(正着反着一样)

    gdscript
    is_palindrome("level") # true
    is_palindrome("hello") # false