Ben
Ben

Reputation: 8991

Instantiated nodes don't appear in node tree when running in editor, using @tool

I have a TerrainManager which has a child node called Parts.

I have an array of packed scenes (currently only two: Part1 and Part2). The TerrainManager randomly selects parts from this array and positions them sequentially.

I'm able to test this in the editor too.

When playing, the method works fine and the nodes are added to the tree; however, I'm having trouble debugging because in the editor the Parts are there, but they don't appear in the node tree.

I have added outputs to share whether the nodes are added to the tree, and have tested the path to the nodes too, which both appear to be correct - I just can't see them in the tree at all and therefore can't select them in the editor.

Terrain_Manager.gd:

@tool
extends Node
class_name TerrainManager

@export var terrain_parts : Array[PackedScene]
@export var terrain_part_container : Node2D

const MAX_PARTS = 10

var end_position : Vector2 = Vector2(0, 0)

func _ready() -> void:
    if Engine.is_editor_hint():
        return
        
    fill_container()

## Add a new segment to the terrain
func add_segment() -> void:
    var random_part = terrain_parts.pick_random().duplicate() as PackedScene
    var part = random_part.instantiate()
    part.name="TerrainPartInstance_%s" % part.name

    ## Add to container
    terrain_part_container.add_child(part)

    ## Reposition to the end of the last segment
    if terrain_part_container.get_child_count() > 1:
        var _part = terrain_part_container.get_child(terrain_part_container.get_child_count() - 2) as TerrainPart
        end_position += _part.global_position_end - _part.global_position_start
    
    part.position = end_position + part.global_position_start

## DEBUG TOOLS:
@export var request_refresh : bool = false : set = _empty_and_fill_container
func _empty_and_fill_container(_set : bool) -> void:
    _empty_container(true)
    fill_container()

@export var request_empty : bool = false : set = _empty_container
func _empty_container(_set : bool) -> void:
    for child in terrain_part_container.get_children():
        child.queue_free()
    
    end_position = Vector2(0, 0)

Terrain_Part.gd:

@tool
extends Node
class_name TerrainPart

@export var _anchor : SS2D_Shape_Anchor
@export var _shape : SS2D_Shape_Open

func _ready() -> void:
    if Engine.is_editor_hint():
        print("Checking that %s is in tree: %s" % [self.name, self.is_inside_tree()])
        return


var global_position_start : Vector2 : get = _get_global_position_start
func _get_global_position_start() -> Vector2:
    _validate()
    return _get_ss2d_point_at_index(0).position

var global_position_end : Vector2 : get = _get_global_position_end
func _get_global_position_end() -> Vector2:
    _validate()
    return _get_ss2d_point_at_index(-1).position


func round_first_point() -> void:
    var _first_point : SS2D_Point = _get_ss2d_point_at_index(0)
    _first_point.position = Vector2(round(_first_point.position.x), round(_first_point.position.y))

func round_last_point() -> void:
    var _last_point : SS2D_Point = _get_ss2d_point_at_index(-1)
    _last_point.position = Vector2(round(_last_point.position.x), round(_last_point.position.y))

func _get_ss2d_point_at_index(_index : int) -> SS2D_Point:
    return _shape._points._points[_shape._points._point_order[_index]]

# TOOLS:
@export var validate : bool = false : set = _validate
func _validate(_val : bool = false) -> void:
    round_first_point()
    round_last_point()
    set_bezier_handles(-1, Vector2(-250,0), Vector2(250,0))
    set_bezier_handles(0, Vector2(-250,0), Vector2(250,0))

func set_bezier_handles(_index : int, _value : Vector2, _value2 : Vector2) -> void:
    var _point : SS2D_Point = _get_ss2d_point_at_index(_index)
    _point.point_in = _value
    _point.point_out = _value2

Screenshot of editor when running game (expected output - except the node names are not being changed correctly:

Editor output when Playing the scene, where instances are properly added to the tree

Output when using TerrainManager.request_refresh in editor:

Checking that TerrainPartInstance_Part1 is in tree: true
Checking that TerrainPartInstance_Part2 is in tree: true
Checking that @Node2D@42549 is in tree: true
Checking that @Node2D@42550 is in tree: true
Checking that @Node2D@42551 is in tree: true
Checking that @Node2D@42552 is in tree: true
Checking that @Node2D@42553 is in tree: true
Checking that @Node2D@42554 is in tree: true
Checking that @Node2D@42555 is in tree: true
Checking that @Node2D@42556 is in tree: true

Screenshot of editor after running the above:

Editor, showing output and visual of parts loaded correctly, but nothing appears under "Parts" in the node tree

Upvotes: 1

Views: 1247

Answers (1)

Theraot
Theraot

Reputation: 40295

You need to set the owner of your nodes created from the @tool script for them to show up in the editor (and also be saved with the scene).

Making the owner of your nodes the root of the edited scene tells Godot that those nodes are part of that scene.

To reiterate, these are the effects of the owner property:

  • Your nodes will be saved when their owner is saved (because they are part of the scene).
  • They will appear in the editor (because they are part of the scene).

Note: owner is also mentioned in the documentation of add_child, and in the title "Instancing Scenes" of the article Running code in the editor.


This is good if you want to generate the nodes in the editor, and not in runtime. Put another way, if you set the owner then in runtime you will have the nodes that were created and saved in the editor (because they are part of the scene).

Now, if you do all the generation in editor, you might not want to do it in runtime.

For the benefit of people finding this answer, I'll also mention that you can detect if your code is running in the ditor with Engine.is_editor_hint().

Alternatively, you can check if the nodes already exist, and not create them if they are already there.

On a similar note, be aware that signal connections done from a @tool script will be persisted too, so you might want to check if the connection is already made before making it.


See also Display scene with scripted changes in the editor.

Upvotes: 3

Related Questions