Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to expose nodes for direct access in instantiated scenes #84018

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

yahkr
Copy link
Contributor

@yahkr yahkr commented Oct 26, 2023

Updated 11/1/2024

Description

This pull request implements a feature that significantly enhances Godot's scene editing capabilities. It allows specific nodes within a scene to be exposed, making them visible and allowing their properties to be overridden when the scene is instantiated elsewhere. I believe it is an improved version of editable children.

  • By exposing only relevant nodes, the scene tree remains uncluttered, which is especially useful for creating reusable scenes (Custom containers, Generic windows).
  • This PR adds the ability to expose nodes on import, for use when wanting to
    expose parts of a scene (such as the hand bone of a character for equipping).

Note

  • Node exposure only propagates up one level of instantiation
  • Nodes that are exposed can be re-exposed

Important

The use of unique names has been removed from this PR (#84018 (comment)) as there were too many issues with it, once #86960 is merged this PR should function like originally planned

Example

For a simple scene like the following, we expose the Sprite2D, the resulting tscn looks like this:

image
image

[node name="scene_0" type="Node2D"]

[node name="Parent" type="Node2D" parent="."]

[node name="Exposed_0" type="Sprite2D" parent="Parent"]
+ exposed_in_owner = true
texture = ExtResource("1_mdjal")

+[exposed path="Parent/Exposed_0"]

In another scene we instantiate this simple scene and override the exposed node's rotation property and add a child node to the exposed node. We also modify the color of the exposed node to orange

image
image

.tscn with this pr using exposed nodes:

[node name="scene_1" type="Node2D"]

[node name="scene_0_instance" parent="." instance=ExtResource("1_mdjal")]

[node name="Exposed_0" parent="scene_0_instance/Parent" index="0"]
self_modulate = Color(1, 0.666667, 0, 1)

[node name="Child_Of_Exposed" type="Sprite2D" parent="scene_0_instance/Parent/Exposed_0" index="0"]
position = Vector2(100, 30)
scale = Vector2(0.5, 0.5)
texture = SubResource("CompressedTexture2D_3sd7o")

.tscn with editable children and doing the same thing:
image

[node name="scene_1" type="Node2D"]

[node name="scene_0_instance" parent="." instance=ExtResource("1_mdjal")]

[node name="Exposed_0" parent="scene_0_instance/Parent" index="0"]
self_modulate = Color(1, 0.666667, 0, 1)

[node name="Child_Of_Exposed" type="Sprite2D" parent="scene_0_instance/Parent/Exposed_0" index="0"]
position = Vector2(100, 30)
scale = Vector2(0.5, 0.5)
texture = SubResource("CompressedTexture2D_3sd7o")

+ [editable path="scene_0_instance"]

Sample Project

TODO

  • Continue to test

I think that this pr addresses the following proposals:

@AThousandShips AThousandShips added this to the 4.x milestone Oct 26, 2023
@fire fire changed the title first pass - proof of concept Expose nodes inside of an instantiated scene instead of "Editable Children" - proof of concept Oct 26, 2023
@jcostello
Copy link
Contributor

This will work with imported gltf scenes?

@yahkr
Copy link
Contributor Author

yahkr commented Oct 27, 2023

This will work with imported gltf scenes?

I havn't looked into that aspect, but would be a nice feature to add to this as well

@AThousandShips
Copy link
Member

Also please update your branch by rebasing instead of merging, important skill to get used to with contributing, see the pr workflow for details

@AThousandShips
Copy link
Member

You have reset your branch and this closes the PR, if you update your branch this can be reopened

@AThousandShips AThousandShips removed this from the 4.x milestone Nov 23, 2023
@yahkr
Copy link
Contributor Author

yahkr commented Nov 24, 2023

Sorry, still trying to get a grip on correct way of updating my repo from main while keeping my changes. I've pushed my new changes up. This includes a somewhat functional version of this pr.

@yahkr yahkr reopened this Nov 24, 2023
@AThousandShips AThousandShips added this to the 4.x milestone Nov 26, 2023
@yahkr yahkr force-pushed the exposed_in_owner branch 7 times, most recently from 3233eac to 5257a35 Compare December 3, 2023 19:19
@timoschwarzer
Copy link
Contributor

Works perfectly so far! The only minor issue I've found is that it's possible to edit exposed nodes that are no longer there if you have previously selected it:

Screencast.From.2024-10-30.17-11-43.webm

@yahkr
Copy link
Contributor Author

yahkr commented Oct 30, 2024

Works perfectly so far! The only minor issue I've found is that it's possible to edit exposed nodes that are no longer there if you have previously selected it:

fixed! thanks!

@rabbithawk256
Copy link

2024-10-30-godot windows editor x86_64-1aC2A
I don't really know what's going on here - I'm using branch exposed_in_owner (which I'm assuming is the latest release) and any additional nodes within the exposed slot are lost, and the following output is displayed in the console -

scene\resources\packed_scene.cpp:181 - Exposed parent path './Window/%Content/VBoxContainer' for node 'TextureRect' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:181 - Exposed parent path './Window/%Content/VBoxContainer' for node 'Buttons' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons' for node 'HBoxContainer' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer' for node 'Heading' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer' for node 'Form' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/Form' for node 'UsernamePswd' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/Form/UsernamePswd' for node 'Username' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/Form/UsernamePswd/Username' for node 'Label' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/Form/UsernamePswd/Username' for node 'Field' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/Form/UsernamePswd' for node 'Password' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/Form/UsernamePswd/Password' for node 'Label' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/Form/UsernamePswd/Password' for node 'Field' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer' for node 'Error' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer' for node 'HSeparator' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer' for node 'VBoxContainer' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/VBoxContainer' for node 'LoginOptions' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/VBoxContainer' for node 'VSeparator' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/VBoxContainer' for node 'Cancel' has vanished when instantiating: 'res://screens/desktop.tscn'. scene\resources\packed_scene.cpp:188 - Parent path './Window/%Content/VBoxContainer/Buttons/HBoxContainer/VBoxContainer' for node 'LogIn' has vanished when instantiating: 'res://screens/desktop.tscn'.

@yahkr
Copy link
Contributor Author

yahkr commented Oct 30, 2024

@rabbithawk256

Thanks for the report, I think I see whats happened and have pushed a hopeful fix. I wasn't correctly identifying when an exposed parent node did or did not exist anymore. The issue was when a node was added as a child of an exposed node, it creates a path like this:

[node name="base" parent="." instance=ExtResource("1_mdjal")]

[node name="SecondParent" type="Node2D" parent="base/%Exposed" index="0"]

[node name="Child_Of_Child_Of_Exposed" type="Sprite2D" parent="base/%Exposed/SecondParent"]
texture = ExtResource("2_bbwan")

I needed to traverse up the parent path to see if that exposed node is still exposed or not.

Can you please try this again? If it doesn't work I would love a project that reproduces this issue for you.

@rabbithawk256
Copy link

Issue appears to be fixed, cheers! The nodes stay together after restarting Godot, and the terminal output is clear

@timoschwarzer
Copy link
Contributor

I found another interesting bug:

You need:

  • A main scene
  • Another scene that has an AnimationPlayer in it
  • The AnimationPlayer must have at least one track with at least one key. It does not matter which node the track targets

Now instantiate the second scene in the main scene and save. Close the main scene and reopen it. Save again, close and reopen it. You should see random AnimationPlayer nodes appear in the main scene as children of the instantiated scene.

Screencast.From.2024-10-31.20-39-56.webm

This does not happen if I compile from the same commit without this PR.

@yahkr
Copy link
Contributor Author

yahkr commented Oct 31, 2024

I found another interesting bug:

Very strange... diff between PR scene and normal scene

Will continue to dig in :)

EDIT: Figured it out, will work out the fix tomorrow. Thanks for testing!!

@yahkr
Copy link
Contributor Author

yahkr commented Nov 1, 2024

Realizing there are more edge cases I missed that complicate things (at least with using/forcing unique names)

  • nesting exposed nodes with the same name. (works in theory, fails when attempting to find these nest nodes)
  • when saving overridden properties how to keep them relative to the exposed node tree and not the actual tree...
    and example of this would be
    image
    image
    image

when saving scene_2 we get this:

 [node name="%Exposed2" parent="scene_1_instance/scene_0_instance/%Exposed" index="0"]
rotation = 32.0

we would much rather have

 [node name="%Exposed2" parent="scene_1_instance/%Exposed" index="0"]
rotation = 32.0

Since thats all we actually see, and it would prevent issues if scene_0_instance node was renamed. However now we can't find the correct node. Lots to think about here.

Edit: The more I think/explore possible solutions, the more I feel we just need the unique ids PR to make all the features of this pr possible. I possibly could implement a mini version of that PR specifically for exposed nodes, but I feel it would be bad practice to implement this when a better version is on the horizon.

Debating stripping this PR back to not include anything related to unique names and then coming back later...

@timoschwarzer
Copy link
Contributor

I think putting the unique name features to the backlog for now would bet better. Looking at the Unique ID proposal, that doesn't seem to be close to done anytime soon and I think this feature/PR is too useful by itself to completely stall it.

@yahkr
Copy link
Contributor Author

yahkr commented Nov 1, 2024

Thanks everyone for their help/feedback! I went ahead and removed the unique name aspect from this pr and updated the description 😥. Feel free to give it another go and let me know if you run into any more issues.

@KoBeWi
Copy link
Member

KoBeWi commented Nov 1, 2024

Exposing a node inside editable instance will make it also editable in the parent scene and appear as if every node was exposed in the parent scene.

godot.windows.editor.dev.x86_64_d0sN6tGBaK.mp4

Expose.zip

doc/classes/Node.xml Outdated Show resolved Hide resolved
doc/classes/Node.xml Outdated Show resolved Hide resolved
@yahkr
Copy link
Contributor Author

yahkr commented Nov 1, 2024

Exposing a node inside editable instance

I don't think this should be allowed, since exposure should be controlled from the scene in which the node originates, so i will prevent that expose button from appearing when editable children is enabled and it isn't already exposed.

Edit:

@yahkr yahkr force-pushed the exposed_in_owner branch 2 times, most recently from 374379b to da3c90e Compare November 1, 2024 23:27
@KoBeWi
Copy link
Member

KoBeWi commented Nov 2, 2024

You can still expose editable child using the shortcut.

@@ -33,6 +33,7 @@

#include "editor/gui/scene_tree_editor.h"
#include "editor/script_create_dialog.h"
#include "editor_undo_redo_manager.h"
Copy link
Member

Choose a reason for hiding this comment

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

This should be included in cpp file.

_update_children_cache();
Node *const *cptr = data.children_cache.ptr();
int ccount = data.children_cache.size();
for (int i = 0; i < ccount; i++) {
Copy link
Member

@KoBeWi KoBeWi Nov 2, 2024

Choose a reason for hiding this comment

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

Don't iterate on children cache, you can iterate on children directly with

for (KeyValue<StringName, Node *> &KV : data.children) {

Comment on lines +173 to +175
bool exposed_in_owner = false;
bool exposed_in_scene : 1;
bool exposed : 1;
Copy link
Member

Choose a reason for hiding this comment

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

Feels like one of them is redundant. Wouldn't it be possible to infer exposed from exposed_in_owner and exposed_in_scene?

@jcostello
Copy link
Contributor

This will work with imported gltf scenes?

Hey, have you manage to pull this off?

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