继承
继承的基本思想是基于对某个基类的扩展,制定出一个新的派生类,派生类可以继承基类所有的属性和方法,也可以增加基类所不具备的属性和方法,或者重写基类中的方法
在 GDScript 中,一个类可以继承自:
全局类(如
Node,Resource,CharacterBody2D等等)另一个类文件(直接用路径字符串)
某个类文件中的内部类
不允许多重继承(继承多个类)
继承使用extends关键字,如果没有显式指定继承的类,则该类默认继承自RefCounted,所以在一些情况下可以不写 extends
extends SomeClass # 继承一个全局类
extends "somefile.gd" # 继承某个无名类
extends "somefile.gd".SomeInnerClass # 继承某个无名类的内部类继承之后,该类就能直接访问父类的所有成员。
你自然也能在声明全局类/内部类的同时继承某一类,class_name或extends必须写在代码文件首行!
extends和class_name既可以写在同一行,也可以写成两行,但都必须位于脚本文件开头!
而class和extends必须写在同一行!
class_name Player
extends CharacterBody2D
class Item extends Resource:
pass子类中不可以声明和父类同名但参数不同的方法(构造函数除外),也不能声明与父类同名的属性(不论方法或属性是不是静态的)
可以使用is运算符来检查给定的实例是否继承自某个类
# item.gd
class_name Item extends Resource
# sword.gd
class_name Sword extends Item
# main.gd
extends Node
func _ready():
var item = Item.new()
var sword = Sword.new()
print(item is Resource) # true
print(sword is Item) # true
print(sword is Resource) # true
print(sword is Node) # false
print(item is Sword) # false我应该继承哪个类?
在 GDScript 中,如果不显式写 extends,脚本默认继承自 RefCounted,这是最常用的基类之一,因为它自动进行引用计数,无需手动管理内存。
什么时候选什么类?看这里
需要与场景树交互时:继承
Node或其子类。它们能加入场景树,参与场景树生命周期(如_ready、_process)以及信号系统。仅作为数据结构或对象容器时:使用
RefCounted。它适合脚本逻辑、纯数据处理,不需要场景绑定。如果是可以保存到磁盘的资源对象(如配置、技能表、数据模板):考虑继承
Resource,这样可以轻松保存为.tres或.res文件。如果你需要创建更底层、精细控制内存的数据结构,Godot 现有的类都不合适:可以继承
Object,但你需要自己处理内存释放和生命周期,开发上更高级也更危险!
重写(override)与super关键字
当你在子类中定义了和父类相同签名的方法时,会自动覆盖父类的方法。
这时在这个类的实例上调用这个方法时就会使用重写的逻辑,而不是父类的逻辑
如果你还想在重写时调用父类的逻辑,可以使用 super:
func some_func(x):
super(x) # 调用父类中同名的 some_func 方法隐式super调用(super(...))只能写在方法中,它会调用基类中的同名方法,隐式super所在的方法必须也在基类中有被定义,也需要在super的括号内传入必要的参数
也可以使用 显式 super调用 来访问指定方法,可在 super 关键字后用英文句点连接父类方法名(带括号):
func overriding():
return 0 # 这会覆盖父类的方法
func dont_override():
return super.overriding() # 这将调用父类中定义的方法同样,你也无法在静态方法中调用父类的实例方法
继承中的构造函数
# state.gd
class_name State
var entity : Node = null
var message := ""
func _init(e: Node) -> void:
enetity = e
func set_message(msg: String) -> void:
message = m
# idle.gd
class_name IdleState
extends State
func _init(e: Node, m: String) -> void:
super(e)
message = m在GDScript中,若父类定义了有参数的构造函数,子类也会继承该构造函数
如果子类没有显式声明构造函数,则创建实例时会使用父类构造函数,静态构造函数也是如此,否则使用子类重写的逻辑
子类可以具有和父类不同参数的构造函数,你可以使用隐式super调用,调用父类构造函数并传递所需的参数
如果你需要子类的初始化逻辑与父类不同,你可以不在构造函数中调用父类的构造函数,但你也应当确保父类的必要成员能够被初始化
练习
请创建一个类
Animal,它包含一个name字段和一个空实现的speak()方法,再创建一个类Cat继承自Animal,并添加一个新字段fur_color。基于上面的
Animal类,在Cat类中重写基类的speak()方法,使它输出"Meow!"在
Animal类中加入带参数的_init(name: String)构造函数, 然后在Cat中定义自己的_init(name: String, fur_color: String),调用super(name)来初始化name字段。