Godot v4.4.1.stable.mono - Windows 11 (build 26100)
Question
For example, we have the following scenario of scene inheritance:
B ← C ← D
When at some point you realize a scene A as parent would make sense, can you somehow change B to inherit from A to have a resulting chain of:
A ← B ← C ← D
I know of the feature “New Inherited Scene” but did not find anything to do this afterwards. If it is not possible, is there maybe any workaround by manually fiddling with the files, etc.?
@MightyPhil no they are talking about Scene inheritance, not Class inheritance.
@lord_stuff no, there is no easy way to do this. You have to re-create all inherited objects. While the scene inheritance feature is useful, it is finicky. If you change anything in any of the yellow nodes in an inherited scene, it no longer inherits from the parent scene, and changes made upstream do not affect it.
There are ways to get around this, but it depends on how complicated your parent scene is. For example if I want to create a scene and make it inheritable, and it only has one or two nodes extra, I will construct those nodes in code. Then I can inherit from the class instead of the scene.
All the nodes with yellow names are inherited and should be changed in the Enemy scene. All the grey-named nodes are unique to my Zombie scene and I can change them there. Changing any of the yellow node in the Zombie scene will prevent the scene from getting any further changes made to the Enemy scene in the future.
The behavior of scenes has many similarities to classes, so it can make sense to think of a scene as a class. Scenes are reusable, instantiable, and inheritable groups of nodes. Creating a scene is similar to having a script that creates nodes and adds them as children using add_child().
Nah, I think it is fine to think scene as class.
The most special thing about a scene, it seems, is it can be edited inside godot.
Yes you can think of a scene as a class. In fact, it is an instantiated object of a class.
If you are a beginner to Godot that’s a good way to look at it.
Ok.
It is exactly like that. Except that instead of creating Enemy in the Godot editor using its scene tree, you have to create it all in code in the enemy_2d.gd file. And also you cannot see or edit any of those nodes in the Zombie scene unless the game is playing. The best you can do is turn it into a @tool script and add a bunch of code to allow you to edit those invisible child nodes.
Thank you both for the responses and the explanations.
I feared that it would not be possible to change scene inheritance afterwards. Recreating all will be impossible at some point. So it is better to steer clear of scene inheritance at all, actually, to not risk that.
Scene inheritance is an editor feature. It doesn’t really exist at runtime. In fact even scenes do not exist as entities at runtime in the scene tree. It’s just a large tree of nodes.
A scene can contain multiple scripts. One for each node.
Also, the base node of a scene is always made from code - either Godot engine (C++) code, or an attached script in whatever language you’re using. This Curved Terrain plugin is a perfect example of that. (See below)
The Curved Terrain plugin I mentioned above was four nodes in a scene that I turned into a @tool script. This way it could be used as a complex scene, but added as a single node to a project. It’s a Path2D with four additional nodes: Polygon2D, Line2D, StaticBody2D, and StaticBody2D hanging off it. The drawback is only the features that are raised up through code on the four hidden nodes can actually be manipulated.
@tool
class_name CurvedTerrain2D extends Path2D
## The image to fill the inside
@export var fill_texture: Texture2D
## The image to use as the edge of the terrain.
@export var edge_texture: Texture2D
## The thickness of the edge terrain. Adjusting this will change the look and smoothness of the edge.
@export var edge_depth: float = 30.0
@export_category("Collision")
## The physics layers this [CurvedTerrain2D] is in. Curved terrain objects can exist in one or more of 32 different layers. See also [member CurvedTerrain2D.mask].
## [br][br][b]Note:[/b] Object A can detect a contact with object B only if object B is in any of the layers that object A scans. See Collision layers and masks in the documentation for more information.
@export_flags_2d_physics var layer: int = 1
## The physics layers this [CurvedTerrain2D] scans. Curved terrain objects can scan one or more of 32 different layers. See also [member CurvedTerrain2D.layer].
## [br][br][b]Note:[/b] Object A can detect a contact with object B only if object B is in any of the layers that object A scans. See Collision layers and masks in the documentation for more information.
@export_flags_2d_physics var mask: int = 1
## Turns visible collision shapes on and off in the editor. Has no effect in the game.
@export var show_collision_shape: bool = false
var polygon_2d: Polygon2D
var line_2d: Line2D
var collision_static_body_2d: StaticBody2D
var collision_polygon_2d: CollisionPolygon2D
func _ready() -> void:
curve.bake_interval = 20
polygon_2d = Polygon2D.new()
polygon_2d.texture_repeat = CanvasItem.TEXTURE_REPEAT_ENABLED
add_child(polygon_2d)
line_2d = Line2D.new()
line_2d.texture_repeat = CanvasItem.TEXTURE_REPEAT_ENABLED
line_2d.texture_mode = Line2D.LINE_TEXTURE_TILE
add_child(line_2d)
collision_static_body_2d = StaticBody2D.new()
add_child(collision_static_body_2d)
collision_polygon_2d = CollisionPolygon2D.new()
collision_static_body_2d.add_child(collision_polygon_2d)
texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_regenerate()
func _process(_delta: float) -> void:
if not Engine.is_editor_hint():
set_process(false)
return
_regenerate()
func _regenerate() -> void:
if !curve or curve.point_count == 0:
collision_polygon_2d.polygon = []
polygon_2d.polygon = []
line_2d.points = []
return
var points = curve.get_baked_points()
var collider_points = curve.get_baked_points()
if points.size() > 1:
points.append(points[1])
collision_static_body_2d.collision_layer = layer
collision_static_body_2d.collision_mask = mask
polygon_2d.polygon = points
polygon_2d.texture = fill_texture
line_2d.points = points
line_2d.texture = edge_texture
line_2d.width = edge_depth
if collider_points.size() > 2:
collision_polygon_2d.polygon = collider_points
collision_polygon_2d.visible = show_collision_shape
This is going to be the next best solution for you if you don’t want to use an inherited scene. In my opinion, they have their uses. However @MightyPhil 's suggestion of composition over inheritance is a good alternative. Node objects make implementing composition very easy.
People often confuse scenes for node script classes because it’s typically the case that a root node in a scene has a script attached and that script class/name often matches the scene name. However there is no actual requirement for this nor are scenes in any way dependent on script classes. A scene is just a pre-packaged group (branch) of nodes. Those nodes can be configured in any way, each may or may not run a script.
What’s also interesting is that scenes do not actually exist at runtime in the scene tree. Once a packed scene has been instantiated and added to a tree, there’s no structure that holds this group of nodes together as a scene. The single thing that can loosely hint that a node was created as a part of a scene is its owner property, which contains a reference to the scene root node, but this can be changed at whim by user script at runtime. So there are no guarantees by engine that the owner property actually points to the scene root.