BACKGROUND:

I'm designing a tool script for a custom procedural mesh node ("PolySprite3D"), that regenerates an ArrayMesh for itself whenever it's parameters are updated. Using exports and setters, I've been able to detect most parameter changes just fine.

The node script also contains an array of a custom Resource ("PolySurface"), which define the submeshes the PolySprite3D uses for generation. When that array is resized, the PolySprite3D is able to detect that it's changed as expected. But when the PolySurfaces inside that array have their parameters changed, it doesn't propagate back up to the PolySprite3D's array setter, which is how I'm detecting changes.

PolySprite3D:
(The node, which currently detects changes to the following array's size, but not changes to the custom resources it contains)

@tool
extends MeshInstance3D
class_name PolySprite3D

@export var surfaces : Array[PolySurface]:
	set (val):
		surfaces = val
		notify_property_list_changed()
		update()

func update():
	# Mesh generation code goes here

PolySurface:
(The custom resource contained in the array, which is currently not able to signal it's parent node)

@tool
extends Resource
class_name PolySurface

@export var poly_id : int = 0:
	set (val):
		poly_id = val
		notify_property_list_changed()
		emit_changed()

QUESTION:

How can I get the setter inside the PolySurface custom resource to signal the PolySprite3D containing it, that their parameters have changed? The setter itself works, but manually emitting the "changed" signal doesn't seem to propagate back up to the parent node. I can't figure out how to connect a custom signal between a Resource and a Node either, and getting the resource to call a function in the parent node is against best practices.

Have I missed something obvious about signals? What's the best way to get an array of unique custom resources to tell this parent node to regenerate it's ArrayMesh using the new parameters?

  • You need to connect the PolySurfaces' signals to the parent class, in your surfaces set function try something like:

    for surface in surfaces:
        if not surface.changed.is_connected(_on_PolySurface_changed):
            surface.changed.connect(_on_PolySurface_changed)

    (Or maybe just update instead of _on_PolySurface_changed, not sure what all needs to happen there.)

    Also emit_changed() looks wrong. If the signal is called changed then you'd typically emit it with changed.emit(), although maybe that's a call to a function you have defined elsewhere which actually emits the signal.

You need to connect the signal to the callback function.

You need to connect the PolySurfaces' signals to the parent class, in your surfaces set function try something like:

for surface in surfaces:
    if not surface.changed.is_connected(_on_PolySurface_changed):
        surface.changed.connect(_on_PolySurface_changed)

(Or maybe just update instead of _on_PolySurface_changed, not sure what all needs to happen there.)

Also emit_changed() looks wrong. If the signal is called changed then you'd typically emit it with changed.emit(), although maybe that's a call to a function you have defined elsewhere which actually emits the signal.

    soundgnome

    I'd thought "emit_changed()" was a function specific to exported Resources in Godot 4, but it turns out it's an alias for changed.emit(), and indeed sends the signal "changed".

    Added that iterative connector to the array setter, and hooked it directly to the update function, and it seems to be working! Much obliged!