Hi, there are some things you need to do in order to get nodes instanced through tool scripts to show up.
By default, nodes or scenes added with Node.add_child(node) are not visible in the Scene tree dock and are not persisted to disk. If you wish the node or scene to be visible in the scene tree dock and persisted to disk when saving the scene, you need to set the child node’s owner property to the currently edited scene root.
Thank you for your answers.
I added the line to set the owner, it partially solve my issue.
I was initially confused on how to get it to work, but I finally noticed that I actually have another problem. This condition seems to prevent my code from being executed:
if !collision_shape:
It is to check if my collision already has been built. Isn’t that variable supposed to be null? (I already tried explicitly assigning null value, it doesn’t change anything)
var collision_shape: CollisionShape3D
I don’t get what I’m doing wrong.
Here is all the code:
ERROR: res://addons/gravity_systems/gravity_volumes/gravity_volume_planet.gd:78 - Invalid access to property or key ‘edited_scene_root’ on a base object of type ‘null instance’.
I think that the GravityVolume node might not be inside the scene tree when that code is run. The example in the docs puts the code to assign the owner into the _ready function to avoid that issue.
Ok, thank you, I now have split my construction function in two (construct and update) and now only construct the collision from the _ready function, and I added checks everywhere to make sure the collision_shape exists:
@tool
class_name PlanetGravityVolume
extends GravityVolume
## Radius at which the gravity will be maximum if gradient is enabled.
@export var radius: float = 5.0:
set(new_value):
radius = new_value
#construct_collision_shape()
update_collision_shape()
var collision_shape: CollisionShape3D = null
func _ready() -> void:
construct_collision_shape()
func construct_collision_shape() -> void:
# Add Sphere collision shape
if !collision_shape:
collision_shape = CollisionShape3D.new()
#var shape := SphereShape3D.new()
collision_shape.shape = SphereShape3D.new()
add_child(collision_shape)
collision_shape.owner = get_tree().edited_scene_root
print("collision shape added")
update_collision_shape()
func update_collision_shape() -> void:
if collision_shape:
collision_shape.shape.radius = radius
But the new issue I discovered, is that a new CollisionShape3D is added each time I reload the scene, as collision_shape assigned value isn’t persistent.
So, I guess my next step is to update my construct_collision_shape() function to check for an existing child:
I started pointing out things and ended up having to rewrite a fair amount to get it working with no errors. It now also deals with accidentally deleting the SphereShape3D resource or the CollisionShape3D node. (Although you’ll have to reload the scene or change the radius to recreate them.)
This version does not create multiple CollisionShape3D nodes when you reload the level, and deals with all the edge cases I could think of. I added comments on the changes where I thought it would be helpful.
@tool
class_name GravityVolume extends Area3D
## Radius at which the gravity will be maximum if gradient is enabled.
@export var radius: float = 5.0:
set(new_value):
if not is_node_ready(): # When entering the scene (i.e. being loaded, this property is run - we don't want that)
await ready
radius = new_value
if collision_shape_3d and collision_shape_3d.shape: #Second check in case you accidentally erase the shape in the editor and don't notice
collision_shape_3d.shape.radius = radius
else:
construct_collision_shape() #Just in case you accidentally deleted this node in the editor by mistake.
var collision_shape_3d: CollisionShape3D #Made the variable name more descriptive to reduce cognitive load (code readability)
func _ready() -> void:
ready.connect(_on_ready)
# This needs to be run after the node is fully loaded in the tree, otherwise collision_shape_3d is null even though it will exist in milliseconds
func _on_ready() -> void:
if not collision_shape_3d:
construct_collision_shape()
func construct_collision_shape() -> void:
# Add Sphere collision shape
# Detects if any CollisionShape3D node exists, which means adding other
# child nodes to this in the editor won't cause problems.
for node in get_children():
if node is CollisionShape3D:
collision_shape_3d = node
if not collision_shape_3d.shape: #In case the collision shape got deleted
collision_shape_3d.shape = SphereShape3D.new()
collision_shape_3d.shape.radius = radius
break
if not collision_shape_3d:
collision_shape_3d = CollisionShape3D.new()
collision_shape_3d.name = "CollisionShape3D" #Added name to pretty up the scene tree
collision_shape_3d.shape = SphereShape3D.new()
collision_shape_3d.shape.radius = radius
collision_shape_3d.tree_exited.connect(_on_collision_shape_deleted)
add_child(collision_shape_3d)
if Engine.is_editor_hint():
collision_shape_3d.owner = get_tree().edited_scene_root
func _on_collision_shape_deleted() -> void:
collision_shape_3d.tree_exited.disconnect(_on_collision_shape_deleted)
collision_shape_3d = null
#Deleted update_collision_shape() because the check is only needed one place and otherwise it is one line of code
Great, thank a lot!
I’m new to Godot Engine, I learned a lot reading your notes and checking how you rewrote this. That’s really helpful!
For those reading this later, another useful line I added after constructing the collision shape to make the collision shape unique: collision_shape_3d.shape.resource_local_to_scene = true
Without it, all instances would share the same shape.