对象
实例化
我们之前讲过,类就像是蓝图——只是“设计图纸”,它并不是一个真实存在的实体,也不能直接使用。所以如果我们想使用一个类,就得先“造出来”。也就是说,我们要 创建这个类的一个实例,也可以称为“对象
在 GDScript 中,我们使用 类名.new() 来调用构造函数,创建出这个类的实例。如果构造函数有参数,记得一定要传入!
创建出来的对象会存储在变量中,这个变量持有这个对象的“引用”,变量的类型也就是这个类。
# 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 命名的脚本,或者想要在运行时“动态创建”的情况
var MyClass: GDScript = load("res://item.gd")
func _ready():
var new_item = MyClass.new("item_1")当我们有了一个对象,就可以通过 对象.类成员 的方式来访问它的属性和方法
print(wood.name) # 输出:wood
wood.do_something() # 调用方法(如果有的话)Self关键字
在 GDScript 中,self 表示“当前类的对象”。如果你在方法里写了一堆变量,有些变量名可能跟类的成员变量冲突了,这时候就可以用 self.属性名 明确指的是类成员而不是局部变量
class_name ItemStack
var item: String
var amount: int
func _init(item: String, amount: int = 0) -> void:
self.item = item # 赋值号左边是类成员,右边是方法的参数
self.amount = amount不仅如此,self 也可以作为返回值返回当前对象,比如在构建链式调用时就非常常见
# 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() 来把它从内存中删掉:
var node = Node2D.new()
add_child(node)
var obj = Object.new()
# 用完之后就释放掉
obj.free()
node.queue_free() # Node及其子类使用 queue_free 释放即使节点对象不在场景树中,也需要使用
queue_free(),它会等在当前帧末尾从内存中彻底移除。如果是自己手动 new 出来的非节点对象,则直接调用
free()。
虽然大多数对象都要手动销毁,但也有例外!GDScript 中的某些类——比如 RefCounted、Resource 及其子类——使用了 引用计数 机制来进行内存管理。
当一个对象的引用计数降为 0(没人再用它)时,它就会自动销毁!而不需要手动调用free
这类自动管理的对象,特别适合用于数据、配置、DTO(数据传输对象)等等
虽然引用计数听起来很香,但它有个致命弱点:循环引用。
假设 A 引用了 B,B 又引用了 A,那么即使外部没人引用它们,它们也彼此“抱着不放”
在这种情况下,引用计数永远不会降到 0,导致对象永远不会被释放,从而造成 内存泄漏
所以,如果你需要存储数据对象,请优先考虑让它们继承自 RefCounted 或 Resource,而不是 Object,这样你就可以享受自动销毁的便利。 当然,避免循环引用 依然是最重要的!
GDScript中对象的本质
对象是怎么查数据的?
当我们想知道一个对象有没有 position 这个属性时,它不会立刻回答你,而是会这样想:
“嗯……我自己的脚本里有没有?没有?那我去问我爹(基类)看看……还没有?那就接着往上问……”
对象会一层层往上查,直到追溯到 Object 根类为止,这个过程就像翻一层层资料,查找速度自然比数组和字典慢一点点。
而且每次访问对象的属性,都会经历一轮这样的“追问式查找”——它要先确认这个东西存在哪儿,再去读它的值
访问对象里的数据,比访问数组和字典要慢一些,特别是在 GDScript 中。
GDScript 比 C++ 慢,是因为每次取个值都像在开会,问一圈才知道答案。
GDScript:每次操作都走查找系统(慢)
C#:有些操作会编译优化(快一点),但调用引擎类内容还是会变慢
C++:最直接(最快),但使用起来比较困难
为什么要用 Object
虽然慢一点,但对象是你代码里最灵活的积木块!你可以用它做更多事:
功能更强(控制) 对象不仅能装数据,还能装逻辑、信号和回调 —— 适合构建更复杂的结构,比如角色、AI、任务系统等。
结构清晰(可读性) 对象的属性比较规范,就算没值,也不用担心“这个变量到底有没有?”
复用方便(继承) 如果你已经有一个类似的数据结构,只要继承它再加点东西,就能快速打造新结构。数组和字典做不到这一点。
我可以用对象做啥?
你可以用对象来构建:
自定义链表、栈、队列、树结构
有逻辑的结构,比如“背包”、“怪物生成器”
拥有行为和事件响应的游戏系统
举个例子,如果你要做一个技能树,Array 和 Dictionary 很快就会力不从心,这时候用对象来自定义每个节点的数据与逻辑,就轻松多了
“那我用 Node 不就好了?”
Node 是 Object 的子类,但它自带了很多游戏相关的功能,比如位置、渲染、输入检测等等。如果你只是想构建个纯数据结构,用 Node 反而有点“多余”和“笨重”。
这时候自己写个继承 RefCounted或Resource 的简洁类就刚刚好!
练习
定义一个
Fruit类,它有三个属性:name(名字)、color(颜色)和is_ripe(是否成熟)。 写出一个带三个参数的_init()构造函数,在构造函数内初始化对象的所有属性,并尝试在_ready()中实例化几个不同的水果。设计一个
Pet类,有属性name(名字)、species(物种) 和age(年龄),有具有三个参数的_init()构造函数,需要在构造函数内初始化所有属性,定义一个get_pet_info()方法返回一个字符串,用以获取宠物的信息 要求在主脚本中实例化几个宠物对象,并把它们添加进一个数组中,最后遍历这个数组并调用get_pet_info方法打印每个宠物的信息。