Classes, Inheritance & Inner Classes

13.1 Every script is a class

A .gd file defines a class. extends sets the base class (defaults to RefCounted if omitted). class_name registers it globally so other scripts can use it as a type and the editor shows it in the *Create Node* dialog.

# res://scripts/character.gd
class_name Character
extends CharacterBody2D

signal died

@export var max_health := 100
var health := max_health

func take_damage(amount: int) -> void:
	health -= amount
	if health <= 0:
		die()

func die() -> void:
	died.emit()
	queue_free()

13.2 Inheritance and super

Override a method by defining one with the same name (signature must be compatible).

Engine virtual methods (_ready, _process) in parent scripts are not automatically skipped — in Godot 4, a child's _ready() overrides the parent's; call super() to run both.

Godot 3 note: 3.x used .take_damage(amount) to call the parent method and automatically called parent _ready/_process callbacks. Godot 4 uses super and requires explicit calls.

# res://scripts/boss.gd
class_name Boss
extends Character          # extend a class_name...
# extends "res://scripts/character.gd"   # ...or a script path

var phase := 1

func take_damage(amount: int) -> void:
	super.take_damage(amount / 2)       # call the parent implementation
	if health < max_health / 2 and phase == 1:
		phase = 2

func die() -> void:
	print("The boss falls!")
	super()                             # shorthand for calling the same method on parent

13.3 Constructors: _init

_init runs when the object is created (before it enters the tree, before @onready).

Scripts attached to nodes in scenes are instantiated with no arguments, so give every _init parameter a default (or avoid _init parameters for node scripts).

Call the parent constructor with super(args) inside _init if needed.

class_name Item
extends RefCounted

var name: String
var price: int

func _init(p_name: String = "Unknown", p_price: int = 0) -> void:
	name = p_name
	price = p_price

# Usage
var sword := Item.new("Sword", 150)

13.4 Inner classes

Inner classes are accessible from other scripts via the outer class: Player.Stats.new().

extends Node

class Stats:
	var strength := 10
	var agility := 10

	func power() -> int:
		return strength * 2 + agility

class Weapon extends RefCounted:
	var damage: int
	func _init(d: int) -> void:
		damage = d

func _ready() -> void:
	var s := Stats.new()
	print(s.power())
	var w := Weapon.new(25)

13.5 Object, RefCounted, Resource, Node — memory rules

You call free() manually Rare; low-level Last reference disappears (automatic) Data/helper classes Refcounted; can be saved to disk Data assets (Chapter 26)

You call queue_free() (or its parent is freed) Anything in the scene tree

Never call free() on a node during physics callbacks or signals — prefer queue_free(). Watch out for reference cycles between RefCounted objects (they leak); use weakref() to break them.

var n := Node.new()
add_child(n)       # parent now owns it; freed with parent
n.queue_free()     # safely delete at end of frame

13.6 Abstract classes (4.5+)

Shape.new() is an error; abstract classes still work as types. On older versions, emulate with a method that calls push_error("not implemented").

@abstract
class_name Shape
extends RefCounted

@abstract func area() -> float     # no body; subclasses must implement

class_name Circle extends Shape    # (in another file)
var r := 1.0
func area() -> float: return PI * r * r

13.7 Duck typing and introspection

Prefer typed checks (if body is Character:) where possible — they are faster and safer.

if body.has_method("take_damage"):
	body.take_damage(10)
if "health" in body:                   # property check
	print(body.health)
body.call("take_damage", 10)
body.set("speed", 300); body.get("speed")
var script: Script = get_script()
print(get_property_list().size())