I want to be able to edit properties from other nodes on root node. I have a bunch of GPUParticles and I don’t want to click and scroll every time.
I got pretty far with EditorInspectorPlugin and EditorProperty, but I can’t find a way to pass the specific object and property to EditorProperty function set_object_and_property.
Is there perhaps a way that I can add a button to property context menu or some other option?
@tool
extends Node
@onready var sprite_2d: Sprite2D = $Sprite2D
var props_to_expose: Array[String] = ["texture", "offset", "centered"]
func _get(property: StringName) -> Variant:
if not is_instance_valid(sprite_2d):
return null
if property.begins_with("other/"):
var parts = property.split("/")
if parts[1] in props_to_expose:
return sprite_2d.get(parts[1])
return null
func _set(property: StringName, value: Variant) -> bool:
if not is_instance_valid(sprite_2d):
return false
if property.begins_with("other/"):
var parts = property.split("/")
if parts[1] in props_to_expose:
sprite_2d.set(parts[1], value)
return true
return false
func _get_property_list() -> Array[Dictionary]:
var props: Array[Dictionary]
if is_instance_valid(sprite_2d):
var other_props = sprite_2d.get_property_list()
for other_prop in other_props:
if other_prop.name in props_to_expose:
props.append({
"name": "other/%s" % other_prop.name,
"type": other_prop.type,
"hint": other_prop.hint,
"hint_string": other_prop.hint_string,
"usage": PROPERTY_USAGE_EDITOR,
})
return props
Let me clarify what I’m asking. Let’s say that I have a scene like this:
– Node3D
—— GPUParticles3D
—— GPUParticles3D2
—— etc.
I want to have some properties and sub-properties of GPUParticles3D (i.e. properties from its process material) shown on Node3D. EditorInspectorPlugin lets me add EditorProperty controls and EditorProperty can edit a property given an object and a property name.
So, for example, by passing GPUParticles3D as object and lifetime as property to set_object_and_property function, I can edit this property from Node3D.
My problem is that I don’t want to manually type these object and property paths in some sort of plugin control, instead I want to add a more convient shortcut like how you can right click and favorite a property.
Update: I’ve also run into some issues with EditorProperty as well. While an editable property does show, the value seems to have nothing to do with what’s in GPUParticles3D and it seems that it’s trying to look for the property in the original node (Node3D). I think a viable option is to have a Node3D with custom get/set functions that redirect to referenced objects. But the problem of not having a shortcut to object/property paths still persists.
Inspector emits property_selected. Intercept it, check if alt (or some other hotkey) is pressed and re-create this property’s double in your main node.
There are no sub-properties. If you mean properties of resources then you can right click on the resource and select “Edit” and the resource object will become the edited object. Now the signal will catch clicks on its properties. Not as elegant as doing it nested but still better than manually entering names.
Not sure why they chose for the signal to ignore clicks on nested resource properties.
It turns out that I didn’t need an inspector plugin, a custom script for the root node did the trick. I provided custom functions for _get, _set and _get_property_list functions which handle various references added through property_selected signal.
One final problem is that I lose these references after closing and reopening a scene in editor. I don’t know if there’s such a thing as ‘object path’ so that I can store them as metadata and find them again when opening a scene.
Here it is for anyone that end up with a similar question:
# quick_access.gd
signal changed
class Reference:
var object: Object
var property: String
var property_info: Dictionary
var reference_name: String
var references: Array[Reference]
func toggle_reference(object: Object, property: String) -> void:
if property.begins_with("quick_access_"):
for ref in references:
if ref.reference_name == property:
references.erase(ref)
return
var order: int = 1
for ref in references:
if ref.object == object and ref.property == property:
references.erase(ref)
return
if ref.property == property:
order += 1
var ref := Reference.new()
ref.object = object
ref.property = property
var list := object.get_property_list()
for p in list:
if p["name"] == property:
ref.property_info = p
if order > 1:
ref.reference_name = "quick_access_%s_%d" % [property, order]
else:
ref.reference_name = "quick_access_%s" % property
references.push_back(ref)
func get_references_as_property_list() -> Array[Dictionary]:
if references.is_empty():
return []
var list: Array[Dictionary] = [{
"name": "Quick Access",
"type": TYPE_NIL,
"hint_string": "quick_access",
"usage": PROPERTY_USAGE_GROUP,
}]
for ref in references:
var prop := ref.property_info
prop["name"] = ref.reference_name
list.append(prop)
return list
func setup() -> void:
if Engine.is_editor_hint():
EditorInterface.get_inspector().property_selected.connect(on_property_selected)
func on_property_selected(property: String) -> void:
var obj := EditorInterface.get_inspector().get_edited_object()
if !obj:
return
if Input.is_key_pressed(KEY_ALT) and Input.is_key_pressed(KEY_CTRL):
toggle_reference(obj, property)
changed.emit()
func get_reference(reference_name: String) -> Variant:
for ref in references:
if ref.reference_name == reference_name:
return ref.object.get(ref.property)
return null
func set_reference(reference_name: String, value: Variant) -> bool:
for ref in references:
if ref.reference_name == reference_name:
ref.object.set(ref.property, value)
return true
return false
# root.gd
@tool
extends Node
var quick_access := preload("res://scripts/quick_access.gd").new()
func _ready() -> void:
quick_access.setup()
quick_access.changed.connect(on_quick_access_changed)
func _get_property_list() -> Array[Dictionary]:
return quick_access.get_references_as_property_list()
func _get(property: StringName) -> Variant:
var r = quick_access.get_reference(property)
if r:
return r
return null
func _set(property: StringName, value: Variant) -> bool:
if quick_access.set_reference(property, value):
return true
return false
func on_quick_access_changed():
notify_property_list_changed()
Perhaps utilize two NodePath objects. One leads to the node and the other is mis(used) to store the “path” from the node to the nested resource/property. You can even use a single plain string with some simple custom syntax like node/node/node.resource.resource.property, then parse this on startup and build the doubles. If you declare those paths as @export_storage (or set the equivalent usage flag via _get_property_list()), Godot will automatically save them with the scene so you won’t need to manually put them into metadata.
In any case just make sure you clean up properly whenever a doubled node/resource is deleted.
That said, property_selected gives only an object and a property, so I’m not sure if there’s a better way to find the node that the object is stored in, other than going iterating through all nodes and their properties recursively.
It’d require a little bit of processing but it’s definitely doable. You won’t need to iterate through all if you track the state of the inspector at all times. There are several other signals that can help you with that although it would probably be best to do it as a plugin.
But even recursive bruteforce iteration wouldn’t be much of a problem as you only need to do it when creating a property double.