Skip to content

Add object_created_in_editor signal - #122058

Open
KoBeWi wants to merge 1 commit into
godotengine:masterfrom
KoBeWi:NOTIFICATION_PR_CREATED_AGAIN
Open

Add object_created_in_editor signal#122058
KoBeWi wants to merge 1 commit into
godotengine:masterfrom
KoBeWi:NOTIFICATION_PR_CREATED_AGAIN

Conversation

@KoBeWi

@KoBeWi KoBeWi commented Aug 3, 2026

Copy link
Copy Markdown
Member

What problem(s) does this PR solve?

Closes (maybe) godotengine/godot-proposals#7593
Helps #119020
Helps https://chat.godotengine.org/channel/editor?msg=5NLvSXn9Tse6xdcEp

Additional information

This PR salvages #81217 and it's mostly the same, except the new notification is sent in instantiate_object_properties(), so nodes receive it in all cases when PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT is handled. It also handles a few missed cases: when a script is dropped to make node, when scene root is created from favorites and when new scene is created using non-custom node.

See #81217 for rationale on this solution, especially my last comment.

EDIT:
Changed to a signal in EditorInterface instead.

Comment thread doc/classes/Node.xml Outdated
Notification received right after the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
</constant>
<constant name="NOTIFICATION_EDITOR_CREATED" value="9005">
Notification received right after the node has been created in the editor. This notification is only sent when a node is created using the Godot editor UI and will not occur when nodes are created by scripts or instantiating packed scenes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide a few examples. Sorry for not doing so myself as of writing.

Suggested change
Notification received right after the node has been created in the editor. This notification is only sent when a node is created using the Godot editor UI and will not occur when nodes are created by scripts or instantiating packed scenes.
Notification received right after the node has been created using the editor's interface, such as via _ . This notification is not sent when the node is created by scripts or when instantiating packed scenes.

Or similar. I am not sure about the wording I suggested, either. I just know a few examples are a given.

@KoBeWi
KoBeWi force-pushed the NOTIFICATION_PR_CREATED_AGAIN branch from 7578e03 to 6358cce Compare August 4, 2026 21:47
Comment thread doc/classes/Node.xml Outdated
Notification received right after the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
</constant>
<constant name="NOTIFICATION_EDITOR_CREATED" value="9005">
Notification received right after the node has been created using the editor's interface, such as via Add Child Node or as a root of a new scene. This notification is not sent when the node is created by scripts or when instantiating packed scenes.

@Calinou Calinou Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also mention this case (which surprised me a bit at first, but it makes sense):

Suggested change
Notification received right after the node has been created using the editor's interface, such as via Add Child Node or as a root of a new scene. This notification is not sent when the node is created by scripts or when instantiating packed scenes.
Notification received right after the node has been created using the editor's interface, such as via Add Child Node or as a root of a new scene. This notification is not sent when the node is created by scripts, when instantiating packed scenes or when duplicating the node in the editor.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commented, but the comment is gone 🤦‍♂️

I don't think it's surprising when duplicating a node. It'd be more surprising if it was otherwise, because duplicating nodes could lead to losing some properties.

@Calinou Calinou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally, it works as expected. Code looks good to me.

@Leonardo-Ma

Copy link
Copy Markdown

Could reasoning and possible approaches be expanded? The linked issue is very specific about scripts overriding engine defaults, but it's being suggested as a new approach instead of changing engine node default values to avoid breaking compatibility with previous versions. Something I don't see being explained in the issue, here, in the salvaged PR or in any of the PRs/issues that are being referenced to this (the linked chat message also doesn't open for me, so can't check that).

If it's also only applied when created by the editor, doesn't that mean two versions of every single instance to be maintained? That seems a nightmare?

@KoBeWi

KoBeWi commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

it's being suggested as a new approach instead of changing engine node default values to avoid breaking compatibility with previous versions.

Well, that's just one possible usage. #119020 does change properties, but they are not intended to be defaults; they are meant to make the node more usable in the editor out of the box. The customized "defaults" should be irrelevant outside the editor.

For changes that work around compatibility, the code should be explicitly marked with #ifndef DISABLE_DEPRECATED, so it's easy to identify as such. And we do have a similar mechanism already, for project settings when creating new projects.

godot/editor/editor_node.cpp

Lines 8418 to 8420 in ac7ef92

// This is used to set better defaults for new projects without affecting existing projects.
// Keep the list alphabetically sorted.
HashMap<String, Variant> EditorNode::get_initial_settings() {

If it's also only applied when created by the editor, doesn't that mean two versions of every single instance to be maintained? That seems a nightmare?

There is a risk if it's overused I guess. I think the editor defaults should be mentioned in the docs.

However this PR only introduces the notification, the details on its usage in the engine are yet to be decided in the subsequent PRs. There are just numerous cases where it can be useful.

@kitbdev

kitbdev commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

This leads to adding blocks of editor only code in each Node that uses it.

Users can add nodes to a scene in the editor from code, and if they want to use editor defaults they may try to send the notification manually. The proposal mentions wanting the notification from code, and this would be a workaround for that. However, instantiate_object_properties doesn't get called so it may be inaccurate.

I wanted to mention an alternative approach to consider, since I haven't really seen it brought up.
We could have a single method in editor code that takes the new Node as an argument and sets the default values, eg:

void set_editor_defaults(Node *p_node) {
	Label *label = Object::cast_to<Label>(p_node);
	if (label) {
		label->set_text("Label");
	}
	Decal *decal = Object::cast_to<Decal>(p_node);
	//etc
}

There can be a signal emitted on EditorNode from instantiate_object_properties that connects to this method (in EditorNode or maybe a new file b/c of includes). Other places / modules can connect to it too to configure their nodes separately.
We can emit a signal on EditorInterface for users to connect to. And probably expose a method that calls instantiate_object_properties in case the user makes nodes in the editor and wants editor defaults.

This way editor-only code isn't added to each node. Users wouldn't have to make their Nodes tool scripts to have editor defaults, it can be in a separate tool script.
This would also allow users to override the defaults of any Node that gets created in the editor, which seems useful for plugins to configure defaults for a project.
There are some drawbacks though, like more merge conflicts in this method, and you need to find this to find the defaults for a node.
Private variables also won't be accessible, but since the purpose is to set editor default values, it shouldn't need private variables.


Should we have this for Resources too?
If a Resource is part of a Node the Node can set the default of the Resource, but that is different than changing the editor defaults of Resources themselves.
It may need to be a separate notification(/signal) anyway so we can wait until its needed.

Also cc @godotengine/xr since the proposal is mainly about XR.

@Mickeon

Mickeon commented Aug 5, 2026

Copy link
Copy Markdown
Member

Admittedly we have a very chaotic setup for editor-only code which would be nice to re-evaluate.

@KoBeWi

KoBeWi commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

We could have a single method in editor code that takes the new Node as an argument and sets the default values

That sounds like sensible approach.

A signal could be even connected in plugins, so e.g. particle plugin could be responsible for setting particle defaults, so we don't have a single super method for handling everything.

One downside is that many callbacks might get connected to such signal, each checking whether the provided node is of expected type, which might eventually make instantiating nodes slower. Although it does not cause problems with settings_changed(), so it's probably fine.

Another thing is that the order of callbacks for signal can't be reasonably guaranteed, unlike for a notification. Multiple callbacks customizing one node can easily mess up each other's settings.

@KoBeWi
KoBeWi force-pushed the NOTIFICATION_PR_CREATED_AGAIN branch from 6358cce to 8a5a75e Compare August 10, 2026 13:22
@KoBeWi

KoBeWi commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

Changed notification to a signal and fixed some cases where object wasn't initialized.

Comment thread editor/editor_data.cpp
// This is a situation where listing a subclass before a parent class makes sense (e.g. "NoiseTexture2D,Texture2D").
Object *prop = ClassDB::instantiate(String(pi.class_name).get_slicec(',', 0));
p_object->set(pi.name, prop);
instantiate_object_properties(prop);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is notable change. Objects instantiated here are technically created by the editor, so this allows to initialize them recursively.

Comment thread doc/classes/EditorInterface.xml Outdated
@KoBeWi
KoBeWi requested a review from Calinou August 10, 2026 13:25
@KoBeWi
KoBeWi force-pushed the NOTIFICATION_PR_CREATED_AGAIN branch from 8a5a75e to 7074172 Compare August 10, 2026 13:26
@KoBeWi KoBeWi changed the title Add NOTIFICATION_EDITOR_CREATED Add object_created_in_editor signal Aug 10, 2026
Comment thread doc/classes/EditorInterface.xml Outdated
@Calinou

Calinou commented Aug 11, 2026

Copy link
Copy Markdown
Member

With this new approach, is it still possible to implement #122066 in a way that doesn't break encapsulation?

#ifdef TOOLS_ENABLED
void Decal::_notification(int p_what) {
	switch (p_what) {
		case NOTIFICATION_READY: {
			EditorInterface::get_singleton()->connect("object_created_in_editor", Callable(this, "_object_created_in_editor"));
		} break;
	}
}

void Decal::_object_created_in_editor(Object *p_object) {
	print_line("test");

	// Set improved defaults for newly created Decal nodes in the editor.
	// This does not affect Decals created by code, or Decals created in older versions of Godot.
	Decal* d = Object::cast_to<Decal>(p_object);
	if (d) {
		d->set_size(Vector3(2.0, 1.0, 2.0));
		d->set_upper_fade(0.0);
		d->set_lower_fade(0.0);
	}
}
#endif

For this to compile, I need to #include "editor/editor_interface.h", but Decal is part of scene/. (Either way, it doesn't seem to get called anyway.)

@KoBeWi

KoBeWi commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

It's not possible in the class itself. You'd have to add DecalEditorPlugin, which handles the signal. It's more involved, but avoids adding editor-specific code to scene classes.

Either way, it doesn't seem to get called anyway.

You should use callable_mp(), Callable() is only for registered methods.

@Calinou

Calinou commented Aug 12, 2026

Copy link
Copy Markdown
Member

It's not possible in the class itself. You'd have to add DecalEditorPlugin, which handles the signal. It's more involved, but avoids adding editor-specific code to scene classes.

Thanks for the advice, I updated #122066 accordingly 🙂

@Mickeon

Mickeon commented Aug 14, 2026

Copy link
Copy Markdown
Member

Y'know, now that I think about it, both the signal and the notification don't have to be mutually exclusive features.

Because, yes, with a signal we do not break encapsulation and allow addons to inject their own code, but one could argue some users would rather not be bothered to make their own editor plugin for a few tweaks on object creation.
The logic could be present in the original script to keep code less spread out, and this may need to run in the editor with @tool, anyway, for other miscellaneous custom behavior.

Either way we would have to choose one or the other within Godot's codebase.

@Repiteo
Repiteo requested a review from a team August 14, 2026 16:48
@KoBeWi

KoBeWi commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

You don't really need a plugin to use this signal in your scripts, you only have to avoid using EditorInterface directly.

@tool
class_name TestClass extends Node

func _init() -> void:
	if Engine.has_singleton(&"EditorInterface"):
		Engine.get_singleton(&"EditorInterface").object_created_in_editor.connect(initialize_editor_stuff, CONNECT_ONE_SHOT)

func initialize_editor_stuff(object: Object):
	if object == self:
		print("Test")

This works and does not break scripts at runtime. It's a bit more involved than notification though.

@KoBeWi
KoBeWi force-pushed the NOTIFICATION_PR_CREATED_AGAIN branch from 1a53167 to 89e05cb Compare August 14, 2026 19:28

@kitbdev kitbdev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested the signal and method, looks good.
Creating Scripts and Shaders don't emit the signal, but that seems fine.

  • For the main use case of godotengine/godot-proposals#7593, having godotengine/godot-proposals#338 might be a cleaner solution.
    But for the purposes of changing defaults without breaking compatibility or letting users set defaults without inheriting the class, this is a good approach. And it allows a workaround for not being able to override inherited defaults.

Comment thread doc/classes/EditorInterface.xml Outdated
@KoBeWi
KoBeWi force-pushed the NOTIFICATION_PR_CREATED_AGAIN branch from 89e05cb to cb0fa6b Compare August 22, 2026 20:51
@romgerman

Copy link
Copy Markdown
Contributor

Creating Scripts and Shaders don't emit the signal, but that seems fine

Why is that if they are still Resources? That special treatment for Scripts is exactly why some things is impossible to do in the editor.

Co-authored-by: Malcolm Nixon <Malcolm.nixon@gmail.com>
@KoBeWi
KoBeWi force-pushed the NOTIFICATION_PR_CREATED_AGAIN branch from cb0fa6b to d2aeb69 Compare August 23, 2026 11:48
@KoBeWi
KoBeWi requested review from a team as code owners August 23, 2026 11:48
@KoBeWi

KoBeWi commented Aug 23, 2026

Copy link
Copy Markdown
Member Author

Fixed Scripts and Shaders. It was an oversight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants