Is Scene Inheritance Viable?

by mica - April 28th, 2025

One of Godot’s oddest features must be scene inheritance.

Any scene in Godot can be extended and used as the base for another scene.

Scene inheritance example, showing a basic Scene A and a Scene B that extends A. (Scene inheritance example)

The nodes in yellow are carried over from scene A to B. You could think of this as creating a kind of “scene template”.

But, at the time of writing, it is Godot’s black sheep:

  1. They are buggy (I’ll describe why later)
  2. They seem redundant (GDScript already has inheritance?)
  3. They lack proper documentation
  4. They are pretty intimidating to be honest

As such, their inclusion in Godot’s feature set is odd. And, the community’s general response to scene inheritance is to avoid it!

But what could we use them for? Is there anything that they could excel at? Must we fear what we not understand?

Allow me to take you on my journey of discovering that scene inheritance can indeed be a very viable data model!


Resources

For me…

Godot development is a balancing act of data and systems.

I want to use simple data models for content, like holding an entire weapon definition in one folder (visual and behavior).

To this extent, Godot expects you to use custom Resources.

Define a custom Resource class, add whatever exports you’d like, and then they can be manipulated in the inspector.

They are very versatile! However, Resources have maintenance limitations for gameplay modelling.

To demonstrate, let’s use data to define a badass scythe.

A picture of our badass scythe, along with an inspector view of multiple parameters: model, damage, swing duration, grab point, swing sound, and hit sound. (Badass Scythe)

Yes… our scythe truly is badass.

This data model is textbook: we define the 3D model in another scene, the gameplay damage/swing duration, the point where our character holds the weapon, and the swing/hit sounds.

The root node of the player weapon scene might have a script like this for initializing itself:

class_name Weapon
extends Node3D

@onready var swing_sfx: AudioStreamPlayer3D = %SwingSFX
@onready var hit_sfx: AudioStreamPlayer3D = %HitSFX

var model: Node3D
var def: WeaponDefinition

func set_weapon(_def: WeaponDefinition):
	def = _def

	model = _def.model.instantiate()
	add_child(model)

	position = _def.grab_point

	swing_sfx.stream = _def.swing_sound
	hit_sfx.stream = _def.hit_sound

And this works! So here’s why it sucks:

The inspector view of our badass scythe, with many added audio params for volume, unit size, falloff attenauation, panning strength, cutoff frequency, and cutoff volume. (There are some exports missing)

@export_group("Swing Sound")
@export var swing_sound: AudioStream
@export_range(-80.0, 6.0, 0.1, "suffix:dB") var swing_sound_volume := 0.0
@export_range(0.1, 100.0, 0.1) var swing_sound_unit_size := 12.0
@export var swing_sound_attenuation_model := AudioStreamPlayer3D.ATTENUATION_LOGARITHMIC
@export_range(0.0, 3.0, 0.1) var swing_sound_panning_strength := 1.0
@export_range(1, 20500, 1, "suffix:Hz") var swing_sound_cutoff_hz := 5000
@export_range(-80, 0, 0.1, "suffix:dB") var swing_sound_cutoff_db := 0

@export_group("Hit Sound")
@export var hit_sound: AudioStream
@export_range(-80.0, 6.0, 0.1, "suffix:dB") var hit_sound_volume := 0.0
@export_range(0.1, 100.0, 0.1) var hit_sound_unit_size := 12.0
@export var hit_sound_attenuation_model := AudioStreamPlayer3D.ATTENUATION_LOGARITHMIC
@export_range(0.0, 3.0, 0.1) var hit_sound_panning_strength := 1.0
@export_range(1, 20500, 1, "suffix:Hz") var hit_sound_cutoff_hz := 5000
@export_range(-80, 0, 0.1, "suffix:dB") var hit_sound_cutoff_db := 0
func set_weapon(_def: WeaponDefinition):
	...

	swing_sfx.stream = _def.swing_sound
	swing_sfx.attenuation_model = _def.swing_sound_attenuation_model
	swing_sfx.volume_db = _def.swing_sound_volume
	swing_sfx.unit_size = _def.swing_sound_unit_size
	swing_sfx.panning_strength = _def.swing_sound_panning_strength
	swing_sfx.attenuation_filter_cutoff_hz = _def.swing_sound_cutoff_hz
	swing_sfx.attenuation_filter_db = _def.swing_sound_cutoff_db

	hit_sfx.stream = _def.hit_sound
	hit_sfx.attenuation_model = _def.hit_sound_attenuation_model
	hit_sfx.volume_db = _def.hit_sound_volume
	hit_sfx.unit_size = _def.hit_sound_unit_size
	hit_sfx.panning_strength = _def.hit_sound_panning_strength
	hit_sfx.attenuation_filter_cutoff_hz = _def.hit_sound_cutoff_hz
	hit_sfx.attenuation_filter_db = _def.hit_sound_cutoff_db

THIS MAKES ME DIZZY!! Maybe we could make a custom AudioStreamPlayer3DData resource to hold these parameters instead, but we still haven’t resolved the opaque preview issue.

Let me reiterate:

Godot development is a balancing act of data and systems.

In this case, I feel as though Resources have failed us. Our data and systems are clashing, and we are forced to design through a very opaque interface. And representing something simple like audio requires a lot of boilerplate.

In general, custom Resources are suboptimal for modeling nodes. If only there was a Resource that was designed for modeling nodes…

… Hey, PackedScenes are Resources!


Scenes

Let’s not reinvent the wheel here. Let’s use a scene to model our badass scythe.

A scene view of our badass scythe. The root node holds a Model, GrabPoint, SwingSFX and HitSFX nodes. (This represents the tricky WeaponDefinition data)

Suddenly, our problems go away:

Unfortunately, this approach backslides and introduces new issues:

For this scenario, Scenes are better than Resources, yet imperfect. There is a lot of maintenance involved in creating new weapons and adding features to existing ones. God will kill me for adding ambient sounds after I make Badass Scythe #37.

We are stuck making scenes with no reference nodes, and we must suffer through the maintenance hell of adding any feature late in development.

If only there was a way for us to create a “scene template” for the scythe. Then, we could work off of this template for creating a new weapon, or modify the base template to add a MissSFX node.

This is precisely what Scene Inheritance is fantastic for.


Scene Inheritance

Scene Inheritance allows us to create “base” scenes for templating pieces of content.

We can assume that all weapons will have a hit sound, a hurt sound, a grab point, etc etc. So we can define these nodes in our base weapon scene!

Then, we can extend from it later to easily setup a new weapon, or add new nodes in this base scene to quickly implement new features.

We also are given new tools at our disposal: if we create a new audio bus for weapon sounds, then we can modify AudioStreamPlayer3D.bus on the base scene, and the change is (ideally) propagated to all weapons.

Here’s a quick example for how far you could take this: what if we have different weapon categories?

A deep tree of scene inheritance. The root weapon_base.tscn is extended by different categories: pencil_base.tscn, sword_base.tscn, and scythe_base.tscn. These categories respectively extend into weapon_lead.tscn, weapon_dagger.tscn, and weapon_badass.tscn. (Layers of weapon scenes)

Our pencils, swords, and scythes may have exclusive functionality. Scythes may require special Control UI for charging, or pencils could have a rapid attack mechanism that could be modelled within the weapon scene itself.

And all of these categories can share the expectations that weapon_base.tscn declares.

If you’re still confused on when you would want to use scene inheritance, I have a golden rule for you:

If your data models themselves start to model a scene, consider using scenes or scene inheritance instead.


GOD, IF ONLY IT WORKED.

Scene Inheritance, being both undocumented and underutilized, gets no attention, and remains unstable.

From my research, it appears that one of the main appeals of scene inheritance (clean property propagation) is bugged in nested scene inheritance. See these issues for more information: #1 #2. There may be other issues I don’t know about yet either.

This sucks so bad!! On paper, scene inheritance could be a development game changer!

Still, this pattern can be utilized. There’s a few workarounds:

I also wish that scene inheritance had more refactoring options, such as splitting a base scene out of an existing one, or mapping an existing scene onto a base scene.


Conclusion

Scene Inheritance is a strong way of developing and maintaining node heirarchies throughout the development lifecycle, making them very viable for developing content in mass.

Unfortunately, they are currently littered with issues. However, they are still not as bad as they are made out to be — under the hood, model imports can use inherited scenes, and I consider this to be one of the more stable parts of Godot.

I have also had personal success using them in my own projects, even if maintaining them is harder than they should be, due to having to manually reverify all property propagation.

Hopefully raising awareness about their functionality will help bring more attention to improving and documenting them. I believe they could become a very viable tool for a Godot developer.

back to blog