在 GDScript 中,只有基本类型(int、float、string 和向量类型)按值传递给函数(值被复制)。其他所有内容(实例、数组、字典等)都作为引用传递。继承Reference 的类(如果未指定任何内容,则为默认值)将在不使用时被释放,但如果从Object手动继承,则也允许手动内存管理。

func use_class(instance): # Does not care about class type
    instance.use() # Will work with any class that has a ".use()" method.

func do_something():
    var instance = SomeClass.new() # Created as reference.
    use_class(instance) # Passed as reference.
    # Will be unreferenced and deleted.

数组

动态类型语言中的数组内部可以包含许多不同的混合数据类型,并且始终是动态的(可以随时调整大小)。比较静态类型语言中的示例数组:

var array = [10, "hello", 40, 60] # Simple, and can mix types.
array.resize(3) # Can be resized.
use_array(array) # Passed as reference.
# Freed when no longer in use.

在动态类型语言中,数组还可以兼作其他数据类型,例如列表:

var array = []
array.append(4)
array.append(5)
array.pop_front()

或无序集合:

var a = 20
if a in [10, 20, 30]:
    print("We have a winner!")

字典

字典是动态类型语言中的强大工具。字典可以将任何值映射到任何其他值,而完全忽略用作键或值的数据类型。与流行的看法相反,它们是高效的,因为它们可以用哈希表来实现。事实上,它们非常高效,以至于某些语言甚至将数组实现为字典。

字典示例:

var d = {"name": "John", "age": 22} # Simple syntax.
print("Name: ", d["name"], " Age: ", d["age"])

字典也是动态的,可以在任何时候以很少的成本添加或删除键:

d["mother"] = "Rebecca" # Addition.
d["age"] = 11 # Modification.
d.erase("name") # Removal.

在大多数情况下,使用字典通常可以更轻松地实现二维数组。这是一个简单的战舰游戏示例:

# Battleship Game

const SHIP = 0
const SHIP_HIT = 1
const WATER_HIT = 2

var board = {}

func initialize():
    board[Vector2(1, 1)] = SHIP
    board[Vector2(1, 2)] = SHIP
    board[Vector2(1, 3)] = SHIP

func missile(pos):
    if pos in board: # Something at that position.
        if board[pos] == SHIP: # There was a ship! hit it.
            board[pos] = SHIP_HIT
        else:
            print("Already hit here!") # Hey dude you already hit here.
    else: # Nothing, mark as water.
        board[pos] = WATER_HIT

func game():
    initialize()
    missile(Vector2(1, 1))
    missile(Vector2(5, 8))
    missile(Vector2(2, 3))

字典也可以用作数据标记或快速结构。虽然 GDScript 的字典类似于 Python 字典,但它也支持 Lua 风格的语法和索引,这使得它对于编写初始状态和快速结构非常有用:

# Same example, lua-style support.
# This syntax is a lot more readable and usable.
# Like any GDScript identifier, keys written in this form cannot start
# with a digit.

var d = {
    name = "John",
    age = 22
}

print("Name: ", d.name, " Age: ", d.age) # Used "." based indexing.

# Indexing

d["mother"] = "Rebecca"
d.mother = "Caroline" # This would work too to create a new key.

For和While循环

在某些静态类型语言中进行迭代可能非常复杂:这通常在动态类型语言中被大大简化:

for s in strings:
    print(s)

容器数据类型(数组和字典)是可迭代的。字典允许迭代键:

for key in dict:
    print(key, " -> ", dict[key])

也可以使用索引进行迭代:

for i in range(strings.size()):
    print(strings[i])

range() 函数可以接受 3 个参数:

range(n) # Will go from 0 to n-1.
range(b, n) # Will go from b to n-1.
range(b, n, s) # Will go from b to n-1, in steps of s.

一些静态类型的编程语言示例:

for (int i = 0; i < 10; i++) {}

for (int i = 5; i < 10; i++) {}

for (int i = 5; i < 10; i += 2) {}

GS中表示成:

for i in range(10):
    pass

for i in range(5, 10):
    pass

for i in range(5, 10, 2):
    pass

向后循环是通过一个负计数器完成的:

for (int i = 10; i > 0; i--) {}

GS变成:

for i in range(10, 0, -1):
    pass

while() 循环在任何地方都相同:

var i = 0

while i < strings.size():
    print(strings[i])
    i += 1

自定义迭代器

您可以在默认的迭代器不适用的情况下创建自定义迭代器,在你的脚本覆盖variant类的_iter_init_iter_next以及_iter_get 。前向迭代器的示例实现如下:

class ForwardIterator:
    var start
    var current
    var end
    var increment

    func _init(start, stop, increment):
        self.start = start
        self.current = start
        self.end = stop
        self.increment = increment

    func should_continue():
        return (current < end)

    func _iter_init(arg):
        current = start
        return should_continue()

    func _iter_next(arg):
        current += increment
        return should_continue()

    func _iter_get(arg):
        return current

它可以像任何其他迭代器一样使用:

var itr = ForwardIterator.new(0, 6, 2)
for i in itr:
    print(i) # Will print 0, 2, and 4.

确保在 _iter_init中重置迭代器的状态,否则使用自定义迭代器的嵌套 for 循环将无法按预期工作。

Duck Typing

从静态类型语言转移到动态语言时,最难掌握的概念之一是鸭子类型。鸭子类型使整体代码设计更加简单和直接,但它是如何工作的并不明显。

举个例子,想象一下这样一种情况,一块大石头从隧道里掉下来,砸碎了路上的一切。在静态类型语言中,rock 的代码类似于:

void BigRollingRock::on_object_hit(Smashable *entity) {

    entity->smash();
}

这样一来,凡是能被石头砸碎的东西,都得继承Smashable。如果一个角色、敌人、一件家具、一块小石头都是可粉碎的,它们就需要从 Smashable 类继承,可能需要多重继承。如果不需要多重继承,那么他们将不得不继承一个像 Entity 这样的公共类。然而,smash()只有在其中一些可以被粉碎的情况下,才将虚拟方法添加到实体中不会很优雅。

对于动态类型语言,这不是问题。鸭子类型确保您只需要smash()在需要的地方定义一个函数,就是这样。无需考虑继承、基类等。

func _on_object_hit(object):
    object.smash()

“当我看到一只像鸭子一样走路,像鸭子一样游泳,像鸭子一样嘎嘎叫的鸟时,我称那只鸟为鸭子”

在这种情况下,它转换为:

“能砸的东西,不管是什么东西,砸了就行。”

被击中的对象可能没有 smash() 函数。一些动态类型语言在方法调用不存在时简单地忽略它(如 Objective C),但 GDScript 更严格,因此检查函数是否存在是可取的:

func _on_object_hit(object):
    if object.has_method("smash"):
        object.smash()

然后,只需定义该方法,岩石接触到的任何东西都可以被粉碎。