Skip to content

对象

实例化

我们之前讲过,类就像是蓝图——只是“设计图纸”,它并不是一个真实存在的实体,也不能直接使用。所以如果我们想使用一个类,就得先“造出来”。也就是说,我们要 创建这个类的一个实例,也可以称为“对象

在 GDScript 中,我们使用 类名.new() 来调用构造函数,创建出这个类的实例。如果构造函数有参数,记得一定要传入!

创建出来的对象会存储在变量中,这个变量持有这个对象的“引用”,变量的类型也就是这个类。

gdscript
# item.gd
class_name Item

var name: String

func _init(item_name: String):
    name = item_name

# main.gd
extends Node

func _ready():
    var wood: Item = Item.new("wood")
    var stone: Item = Item.new("stone")

除了直接用 类名.new() 之外,我们还可以通过 加载一个 GDScript 脚本资源 来动态实例化一个类,尤其适用于那些没有用 class_name 命名的脚本,或者想要在运行时“动态创建”的情况

gdscript
var MyClass: GDScript = load("res://item.gd")

func _ready():
    var new_item = MyClass.new("item_1")

当我们有了一个对象,就可以通过 对象.类成员 的方式来访问它的属性和方法

gdscript
print(wood.name)     # 输出:wood
wood.do_something()  # 调用方法(如果有的话)

Self关键字

在 GDScript 中,self 表示“当前类的对象”。如果你在方法里写了一堆变量,有些变量名可能跟类的成员变量冲突了,这时候就可以用 self.属性名 明确指的是类成员而不是局部变量

gdscript
class_name ItemStack

var item: String
var amount: int

func _init(item: String, amount: int = 0) -> void:
    self.item = item # 赋值号左边是类成员,右边是方法的参数
    self.amount = amount

不仅如此,self 也可以作为返回值返回当前对象,比如在构建链式调用时就非常常见

gdscript
# Computer.gd
class_name Computer

var cpu: String
var gpu: String
var ram: int
var storage: int
var has_ssd: bool
    
func display() -> void:
    print("Computer Configuration:")
    print("CPU: %s" % [cpu])    
    print("GPU: %s" % [gpu])
    print("RAM: %s" % [ram])
    print("Storage: %s GB %s" % [storage, "SSD" if has_ssd else "HDD"])
    
# ComputerBuilder.gd    
class_name ComputerBuilder

var _computer: Computer = Computer.new()

func set_cpu(cpu: String) -> ComputerBuilder:
    _computer.cpu = cpu
    return self
    
func set_gpu(gpu: String) -> ComputerBuilder:
    _computer.gpu = gpu
    return self
    
func set_ram(ram: int) -> ComputerBuilder:
    _computer.ram = ram
    return self
    
func set_storage(storage: int, is_ssd: bool) -> ComputerBuilder:
    _computer.storage= storage
    _computer.has_ssd = is_ssd
    return self
    
func build() -> Computer:
    if _computer.cpu.is_empty():
        printerr("CPU is required to build a computer.")
        return null
    return _computer

销毁

在程序运行过程中,我们创建了一个又一个对象,但这些对象不可能永远存在。当一个对象不再被需要,它的“生命周期”就结束了,这时候就要 释放它占用的内存,否则就可能发生内存泄漏的问题

GDScript 本身 不具备传统意义上的垃圾回收(GC)机制,所以大多数对象在你用完后需要手动释放。

我们可以调用 对象.free() 来把它从内存中删掉:

gdscript
var node = Node2D.new()
add_child(node)

var obj = Object.new()

# 用完之后就释放掉
obj.free()
node.queue_free()  # Node及其子类使用 queue_free 释放
  • 即使节点对象不在场景树中,也需要使用 queue_free(),它会等在当前帧末尾从内存中彻底移除。

  • 如果是自己手动 new 出来的非节点对象,则直接调用 free()

虽然大多数对象都要手动销毁,但也有例外!GDScript 中的某些类——比如 RefCountedResource 及其子类——使用了 引用计数 机制来进行内存管理。

当一个对象的引用计数降为 0(没人再用它)时,它就会自动销毁!而不需要手动调用free

这类自动管理的对象,特别适合用于数据、配置、DTO(数据传输对象)等等

虽然引用计数听起来很香,但它有个致命弱点:循环引用

假设 A 引用了 B,B 又引用了 A,那么即使外部没人引用它们,它们也彼此“抱着不放”

在这种情况下,引用计数永远不会降到 0,导致对象永远不会被释放,从而造成 内存泄漏

所以,如果你需要存储数据对象,请优先考虑让它们继承自 RefCountedResource,而不是 Object,这样你就可以享受自动销毁的便利。 当然,避免循环引用 依然是最重要的!

GDScript中对象的本质

对象是怎么查数据的?

当我们想知道一个对象有没有 position 这个属性时,它不会立刻回答你,而是会这样想:

“嗯……我自己的脚本里有没有?没有?那我去问我爹(基类)看看……还没有?那就接着往上问……”

对象会一层层往上查,直到追溯到 Object 根类为止,这个过程就像翻一层层资料,查找速度自然比数组和字典慢一点点。

而且每次访问对象的属性,都会经历一轮这样的“追问式查找”——它要先确认这个东西存在哪儿,再去读它的值

访问对象里的数据,比访问数组和字典要慢一些,特别是在 GDScript 中。

GDScript 比 C++ 慢,是因为每次取个值都像在开会,问一圈才知道答案。

  • GDScript:每次操作都走查找系统(慢)

  • C#:有些操作会编译优化(快一点),但调用引擎类内容还是会变慢

  • C++:最直接(最快),但使用起来比较困难


为什么要用 Object

虽然慢一点,但对象是你代码里最灵活的积木块!你可以用它做更多事:

  1. 功能更强(控制) 对象不仅能装数据,还能装逻辑、信号和回调 —— 适合构建更复杂的结构,比如角色、AI、任务系统等。

  2. 结构清晰(可读性) 对象的属性比较规范,就算没值,也不用担心“这个变量到底有没有?”

  3. 复用方便(继承) 如果你已经有一个类似的数据结构,只要继承它再加点东西,就能快速打造新结构。数组和字典做不到这一点。


我可以用对象做啥?

你可以用对象来构建:

  • 自定义链表、栈、队列、树结构

  • 有逻辑的结构,比如“背包”、“怪物生成器”

  • 拥有行为和事件响应的游戏系统

举个例子,如果你要做一个技能树,ArrayDictionary 很快就会力不从心,这时候用对象来自定义每个节点的数据与逻辑,就轻松多了


“那我用 Node 不就好了?”

Node 是 Object 的子类,但它自带了很多游戏相关的功能,比如位置、渲染、输入检测等等。如果你只是想构建个纯数据结构,用 Node 反而有点“多余”和“笨重”。

这时候自己写个继承 RefCountedResource 的简洁类就刚刚好!

练习

  1. 定义一个 Fruit 类,它有三个属性:name(名字)、color(颜色)和 is_ripe(是否成熟)。 写出一个带三个参数的 _init() 构造函数,在构造函数内初始化对象的所有属性,并尝试在 _ready() 中实例化几个不同的水果。

  2. 设计一个 Pet 类,有属性 name(名字)、species(物种) 和 age(年龄),有具有三个参数的 _init() 构造函数,需要在构造函数内初始化所有属性,定义一个get_pet_info()方法返回一个字符串,用以获取宠物的信息 要求在主脚本中实例化几个宠物对象,并把它们添加进一个数组中,最后遍历这个数组并调用get_pet_info方法打印每个宠物的信息。