Skip to content

继承

继承的基本思想是基于对某个基类的扩展,制定出一个新的派生类,派生类可以继承基类所有的属性和方法,也可以增加基类所不具备的属性和方法,或者重写基类中的方法

在 GDScript 中,一个类可以继承自:

  • 全局类(如 Node, Resource, CharacterBody2D 等等)

  • 另一个类文件(直接用路径字符串)

  • 某个类文件中的内部类

不允许多重继承(继承多个类)

继承使用extends关键字,如果没有显式指定继承的类,则该类默认继承自RefCounted,所以在一些情况下可以不写 extends

gdscript
extends SomeClass # 继承一个全局类

extends "somefile.gd" # 继承某个无名类

extends "somefile.gd".SomeInnerClass # 继承某个无名类的内部类

继承之后,该类就能直接访问父类的所有成员。

你自然也能在声明全局类/内部类的同时继承某一类,class_nameextends必须写在代码文件首行!

extendsclass_name既可以写在同一行,也可以写成两行,但都必须位于脚本文件开头!

classextends必须写在同一行!

gdscript
class_name Player
extends CharacterBody2D

class Item extends Resource:
    pass

子类中不可以声明和父类同名但参数不同的方法(构造函数除外),也不能声明与父类同名的属性(不论方法或属性是不是静态的)

可以使用is运算符来检查给定的实例是否继承自某个类

gdscript
# 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

gdscript
func some_func(x):
    super(x)  # 调用父类中同名的 some_func 方法

隐式super调用super(...))只能写在方法中,它会调用基类中的同名方法,隐式super所在的方法必须也在基类中有被定义,也需要在super的括号内传入必要的参数

也可以使用 显式 super调用 来访问指定方法,可在 super 关键字后用英文句点连接父类方法名(带括号):

gdscript
func overriding():
    return 0 # 这会覆盖父类的方法

func dont_override():
    return super.overriding() # 这将调用父类中定义的方法

同样,你也无法在静态方法中调用父类的实例方法

继承中的构造函数

gdscript
# 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调用,调用父类构造函数并传递所需的参数

如果你需要子类的初始化逻辑与父类不同,你可以不在构造函数中调用父类的构造函数,但你也应当确保父类的必要成员能够被初始化

练习

  1. 请创建一个类 Animal,它包含一个 name 字段和一个空实现的 speak() 方法,再创建一个类 Cat 继承自 Animal,并添加一个新字段 fur_color

  2. 基于上面的 Animal 类,在 Cat 类中重写基类的 speak() 方法,使它输出 "Meow!"

  3. Animal 类中加入带参数的 _init(name: String) 构造函数, 然后在 Cat 中定义自己的 _init(name: String, fur_color: String),调用 super(name) 来初始化 name 字段。