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 1

Lets say we have this window scene that we want to re-use everywhere we can exposed the title label and the content nodes:

image

With this PR we can modify the properties of the exposed nodes and append child nodes to them, this lets us create super flexible scenes and use them like so:

image

and this is the same scene with editable children enabled. Far messier and poorer UX

image

Example 2

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

EDIT 11/2/2024:

Yes!

@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

@KoBeWi unfortunately, the new favorites feature does not help here, since it seems to be favoriting properties globally. So when I favorite the position property, it puts it to the top for all Node2Ds. (Which is a questionable design choice in my opinion, because just because the e.g. rotation is important to me on one node, it doesn't mean it's important on all other nodes, but that's off-topic for this issue)

Screencast.From.2024-11-13.13-20-15.mp4

@huwpascoe
Copy link
Contributor

Object groups use metadata rather than making fundamental changes to the node. Is it possible to do the same in this case?

if (p_node->has_meta("_edit_group_")) {

@yahkr
Copy link
Contributor Author

yahkr commented Nov 14, 2024

Object groups use metadata rather than making fundamental changes to the node. Is it possible to do the same in this case?

if (p_node->has_meta("_edit_group_")) {

I've not explored groups much, but looking at how they are saved/loaded it seems pretty similar to how I tackled this.

[node name="Node2D" type="Control" groups=["exposed"]]
layout_mode = 3
anchors_preset = 0

[node name="Label" type="Label" parent="." groups=["exposed"]]
void SceneState::add_node_group(int p_node, int p_group) {
	ERR_FAIL_INDEX(p_node, nodes.size());
	ERR_FAIL_INDEX(p_group, names.size());
	nodes.write[p_node].groups.push_back(p_group);
}

this is very similar to editable_instances and exposed_nodes where the meta data is [editable... [exposed path="Parent/Exposed_0"] The only hard data saved within a node is exposed_in_owner = true:

[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"]
void SceneState::add_editable_instance(const NodePath &p_path) {
	editable_instances.push_back(p_path);
}
void SceneState::add_exposed_node(const NodePath &p_path) {
	exposed_nodes.push_back(p_path);
}

@allenwp
Copy link
Contributor

allenwp commented Nov 15, 2024

If this PR is merged, I expect a design pattern will emerge where users create a new single exposed node in their scenes that hosts all configuration parameters. This node will forward the parameters onto the properties of the scene's other child nodes. Currently, this sort of approach is done by hosting these configuration parameters in the script of the root node of the scene.

My gut tells me that a more complete solution including presenting properties of child nodes should be a part of this PR to prevent dissemination of the above pattern.

Because Editable Children already allows for all of the scene modifications that are desired, minimal changes to core should be made; I like the idea of the editor pulling from metadata.

The following comment is about subjects that are not in my wheel-house, so I might be wrong about this, but I think it's worth mentioning: I see this as not being any new functionality, but instead just new editor features to improve presentation of existing Editable Children scene functionality. And I don't like the idea of making any additions or changes the Node or PackedScene classes when there isn't any new functionality added. (A potential new bug in the editor is not great, but a potential new bug in a shipped game is an absolute no-go.)

@yahkr
Copy link
Contributor Author

yahkr commented Nov 15, 2024

Really appreciate the feedback!!

If this PR is merged, I expect a design pattern will emerge where users create a new single exposed node in their scenes that hosts all configuration parameters. This node will forward the parameters onto the properties of the scene's other child nodes. Currently, this sort of approach is done by hosting these configuration parameters in the script of the root node of the scene.

My gut tells me that a more complete solution including presenting properties of child nodes should be a part of this PR to prevent dissemination of the above pattern.

I can certainly see the logic of this pattern, however I'm not certain users would tend toward putting the config script on an exposed node vs the root of the scene. I agree this would just be bad practice. The issue regarding exposing node properties would definitely be nice and I may end up exploring while discussion of this PR continues. I do think it should be a separate PR as they would touch different parts of the engine.

Because Editable Children already allows for all of the scene modifications that are desired, minimal changes to core should be made; I like the idea of the editor pulling from metadata.

Perhaps I'm just mistaken as to what meta data is being referred to, but the only non-"meta data" change in this PR is the exposed_in_owner field within node.cpp, which is useful when determining node exposure after multiple levels of instantiation. Similar to unique_name_in_owner

The following comment is about subjects that are not in my wheel-house, so I might be wrong about this, but I think it's worth mentioning: I see this as not being any new functionality, but instead just new editor features to improve presentation of existing Editable Children scene functionality. And I don't like the idea of making any additions or changes the Node or PackedScene classes when there isn't any new functionality added. (A potential new bug in the editor is not great, but a potential new bug in a shipped game is an absolute no-go.)

I absolutely agree that with new features like this there is risk of new bugs. Certainly want to minimize user impact.

To provide a counter-argument the new functionality here presents in a couple major ways.

  1. Cleaner tree hierarchy/better UX
  2. Editable children is a top-down approach to node exposure, while this is a bottom-up. When instantiating a custom container tscn you wont need to enable editable children in order to see the exposed nodes. The scene controls exposure of its children, instead of scenes that instantiate the scene controlling it.
  3. Node exposure on import, ex. exposing a rigged character's head/hands/feet nodes for IK/equipment ( Could be a setting to enable editable children as well, but see point 1 & 2)

@timoschwarzer
Copy link
Contributor

If this PR is merged, I expect a design pattern will emerge where users create a new single exposed node in their scenes that hosts all configuration parameters.

Do you mind to elaborate on this? I did not think about this and do not see yet how such pattern would be beneficial over having the properties on the root script. Having a node set up adjacent nodes is a higher burden and it feels semantically incorrect to me, because suddenly nodes not only need to have correct children but also neighbors. In general, I don't see yet how a single exposed node with properties has any advantage over exported properties on the scene root node.

@allenwp
Copy link
Contributor

allenwp commented Nov 15, 2024

I can certainly see the logic of this pattern, however I'm not certain users would tend toward putting the config script on an exposed node vs the root of the scene. I agree this would just be bad practice. The issue regarding exposing node properties would definitely be nice and I may end up exploring while discussion of this PR continues. I do think it should be a separate PR as they would touch different parts of the engine.

This pattern would happen when users too aggressively favour composition instead of inheritance. I expect this is most common with users who are new to Godot, especially those used to systems that heavily favour composition.

Example: A Building packed scene and a BuildingWithBalconies packed scene. The second packed scene adds new properties regarding balconies.

"More correct" and current approach: BuildingWithBalconies is a new script that extends Building and provides access to properties of new child nodes relating to balconies. This new BuildingWithBalconies script is on root of the new packed scene.

"Anti-pattern" possible with this PR: BuildingWithBalconies is a packed scene with the same script on the root node as the Building packed scene, but has a new exposed node with a script that provides access to properties of new child nodes relating to balconies.

Second example: A plugin designer creates an a new exposed child node that hosts properties in order to organize them better in the Scene for the user to configure.

Users find all sorts of ways to "work with what they have to get what they want". So what I'm saying is that this PR, without anything that helps expose child properties, allows new ways to poorly structure a packed scene, especially for users who are new to Godot.

@allenwp
Copy link
Contributor

allenwp commented Nov 15, 2024

Because Editable Children already allows for all of the scene modifications that are desired, minimal changes to core should be made; I like the idea of the editor pulling from metadata.

Perhaps I'm just mistaken as to what meta data is being referred to, but the only non-"meta data" change in this PR is the exposed_in_owner field within node.cpp, which is useful when determining node exposure after multiple levels of instantiation. Similar to unique_name_in_owner

This PR adds three bool to every Node's data. It's possible this could be avoided by using Node::set_meta, as @huwpascoe mentioned. I like this idea.

The following comment is about subjects that are not in my wheel-house, so I might be wrong about this, but I think it's worth mentioning: I see this as not being any new functionality, but instead just new editor features to improve presentation of existing Editable Children scene functionality. And I don't like the idea of making any additions or changes the Node or PackedScene classes when there isn't any new functionality added. (A potential new bug in the editor is not great, but a potential new bug in a shipped game is an absolute no-go.)

I absolutely agree that with new features like this there is risk of new bugs. Certainly want to minimize user impact.

To provide a counter-argument the new functionality here presents in a couple major ways.

1. Cleaner tree hierarchy/better UX

2. Editable children is a top-down approach to node exposure, while this is a bottom-up. When instantiating a custom container tscn you wont need to enable editable children in order to see the exposed nodes.  The scene controls exposure of its children, instead of scenes that instantiate the scene controlling it.

3. Node exposure on import, ex. exposing a rigged character's head/hands/feet nodes for IK/equipment ( Could be a setting to enable editable children as well, but see point  1 &  2)

I don't know the details on how editable children are implemented... But this feature seems like it could use the same underlying data structures and functions that already exist in Node and PackedScene. My naive perspective believes that the editor could treat exposed nodes as "editable children enabled", and then use Node metadata (Node::set_meta) to determine which children are visible. This way, all changes can (maybe) exist in editor classes and never touch Node and PackedScene classes, which would keep the critical codebase that runs on shipped games as simple as possible, rather than adding a small bit of extra complexity. (A small bit of extra complexity now means more surface for bugs in the future.)

@yahkr
Copy link
Contributor Author

yahkr commented Nov 15, 2024

This PR adds three bool to every Node's data. It's possible this could be avoided by using Node::set_meta, as @huwpascoe mentioned. I like this idea.

I don't know the details on how editable children are implemented... But this feature seems like it could use the same underlying data structures and functions that already exist in Node and PackedScene. My naive perspective believes that the editor could treat exposed nodes as "editable children enabled", and then use Node metadata (Node::set_meta) to determine which children are visible. This way, all changes can (maybe) exist in editor classes and never touch Node and PackedScene classes, which would keep the critical codebase that runs on shipped games as simple as possible, rather than adding a small bit of extra complexity. (A small bit of extra complexity now means more surface for bugs in the future.)

Thanks for the clarifications. I will explore this, as I was mistaken as to the meaning of meta data. From a quick glance I could see moving at least 2/3 of the booleans to meta data.

We would still need to touch packed_scene in the same way editable_instances does

One thing to note about this PR is that it shouldn't affect anything regarding runtime in any way beyond what editable children does. It saves children of exposed nodes / modified properties in the same way.

@yahkr
Copy link
Contributor Author

yahkr commented Nov 15, 2024

Alright. I have boiled down the PR to use metadata which not only cleaned up modifications to node and packed_scene but it also cleans up the saved .tscn. now only requiring the [exposed path="..."]

Thank you @allenwp and @huwpascoe for the suggestion + feedback. It is greatly appreciated!

scene/main/node.h Outdated Show resolved Hide resolved
@allenwp
Copy link
Contributor

allenwp commented Nov 18, 2024

First off, I see that you're a new contributor, @yahkr, so I want to take a moment to thank you for looking into this and trying out some different approaches. It's clear you've put a lot of thought and time into this and it's very much appreciated. Trying out some different approaches is critical part of improving Godot.

I have realized I did not do a good job of writing this comment previously and would like to expand further on thoughts that I had relating to this:

The following comment is about subjects that are not in my wheel-house, so I might be wrong about this, but I think it's worth mentioning: I see this as not being any new functionality, but instead just new editor features to improve presentation of existing Editable Children scene functionality. And I don't like the idea of making any additions or changes the Node or PackedScene classes when there isn't any new functionality added. (A potential new bug in the editor is not great, but a potential new bug in a shipped game is an absolute no-go.)

This PR implements an alternative to Editable Children. If we started from scratch, I don't think we would implement both Editable Children and Exposed Nodes. In fact, if Editable Children did not already exist in the editor, I think there would be a lot of support to add this new "Exposed Nodes" feature instead of Editable Children.

...But Editable Children already exists in Godot, so adding this new Exposed Nodes feature now means adding something that comes across as a duplicate feature that has been implemented to solve the same core problem in a different way. This goes against some important best practices of tool design.

I have drafted a proposal for Hidden Editable Children that I believe would be a better fit for Godot, given Editable Children is already a well-established feature in the engine.

usage

hidden-editable-children

@KoBeWi
Copy link
Member

KoBeWi commented Nov 18, 2024

This approach would require zero changes to the core or any runtime engine code. Instead, it just changes the way that the editor displays the Scene tree based on some metadata added to certain nodes.

What happens when you modify a node and the hide it from the scene? Should the data be saved normally? You will end up with properties for nodes that aren't visible in scene tree.

What if you added a node under editable child and then it gets hidden? What will be the parent of such node? (with current implementation of hiding, the whole sub-tree will be invisible, including the node that isn't part of the instance)

EDIT:
I missed you opened a proposal. Feel free to answer my questions there.

@allenwp
Copy link
Contributor

allenwp commented Nov 18, 2024

This approach would require zero changes to the core or any runtime engine code. Instead, it just changes the way that the editor displays the Scene tree based on some metadata added to certain nodes.

What happens when you modify a node and the hide it from the scene? Should the data be saved normally? You will end up with properties for nodes that aren't visible in scene tree.

What if you added a node under editable child and then it gets hidden? What will be the parent of such node? (with current implementation of hiding, the whole sub-tree will be invisible, including the node that isn't part of the instance)

Probably better discussed in the proposal

@yahkr
Copy link
Contributor Author

yahkr commented Nov 18, 2024

This PR implements an alternative to Editable Children. If we started from scratch, I don't think we would implement both Editable Children and Exposed Nodes. In fact, if Editable Children did not already exist in the editor, I think there would be a lot of support to add this new "Exposed Nodes" feature instead of Editable Children.

You raise very valid points, and since this PR has lost its unique name aspect for more robust node placement/overrides I do feel it's posing as an alternative to editable children like you say.

Perhaps this is a feature that is intended to deprecate editable children. I'm having a hard time thinking of reasons to keep editable children if we had this feature. Perhaps others can chime in with any capabilities that can't be reproduced with exposed nodes vs editable children and vice versa?

Ones that jump out to me:
Benefits:

  • Cleaner UX #3248
    • Cleaner custom container like scenes #5475
    • Easier node exposure for imported scenes comment
  • Safer overriding of instantiated scenes due to limited access
  • Bottom-up approach instead of top-down.
  • Less repeated actions than editable children, once a node is exposed it's exposed in any instance.

Downsides:

  • Rapid prototyping isn't as easy since all child nodes aren't exposed
  • Needing child node access requires modifying the instance scene
  • Unexpected node paths visually "root/instance/exposed_node" may actually be "root/instance/nodeA/nodeB/nodeC/exposed_node" when saved (obviously not an issue if you use the right-click copy node path)

To me the benefits far outweigh the downsides.

@pineapplemachine
Copy link

pineapplemachine commented Nov 18, 2024

since this PR has lost its unique name aspect for more robust node placement/overrides I do feel it's posing as an alternative to editable children like you say.

Forgive me if this is a dumb question. I've been following this PR with interest because having a feature like editable children but without breaking things if an edited node gets moved around seemed very useful, but I managed to miss this. How/why was this aspect lost? Maybe this would be a more compelling feature if this advantage could be retained?

Perhaps nodes in a scene could have an Exposed Name that is independent of its unique name within that scene? I think it would be nice to decouple these two things.

...But Editable Children already exists in Godot, so adding this new Exposed Nodes feature now means adding something that comes across as a duplicate feature that has been implemented to solve the same core problem in a different way. This goes against some important best practices of tool design.

I'd also like to express some disagreement with this. Even only the feature of explicitly specifying which child nodes are intended to be commonly accessed in the editor in parent scenes is still useful on its own. It is a documenting expression of intent of how to interact with the instanced scene. This can be added without removing the option to just immediately gain access to the whole subtree via editable children when that's what's needed, and that is a good thing. Having more than one way to do something is not necessarily a bad thing. I think this is true especially as it applies to bringing more options and flexibility for dealing with Godot's node hierarchy, which I feel can be very ungainly at the moment because of that lack of flexibility, particularly when working with instanced scenes.

@timoschwarzer
Copy link
Contributor

@pineapplemachine It was dropped because it was causing some issues (I don't remember the specifics, everything should be outlined in the conversation above) and the consensus was to leave it as-is for now and wait for Node UIDs to become a thing eventually, which would solve this issue.

@pineapplemachine
Copy link

pineapplemachine commented Nov 19, 2024

It was dropped because it was causing some issues (I don't remember the specifics, everything should be outlined in the conversation above) and the consensus was to leave it as-is for now and wait for Node UIDs to become a thing eventually, which would solve this issue.

Trying to catch up, I think I've got a handle on why the unique names were dropped?

I have a suggestion:

The child scene should possess metadata indicating a subtree path to every exposed node. Then when the location of an exposed node within the parent is represented as e.g. path_to_scene_root="..." exposed_name="...", this could be resolved to a path as [path_to_scene_root]/[path_from_metadata(exposed_name)].

A node's exposed name would be defined separately from its unique name. These are two functions that are important to have decoupled - internal implementation vs. external interface. This decoupling would allow a scene re-exposing a node from a child scene to expose it with a different, more descriptive name than it has in the child scene. Also, importantly, it would allow having two instances of a child scene, and to re-expose the node(s) of one child with different names than those of the other child.

So, in the example in this previous comment #84018, scene_0 would have metadata associating a node path with the exposed name Exposed. scene_1 would have metadata associating a node path with the names Exposed and Exposed2 both. scene_2 would indicate the location of these nodes via e.g. parent="scene_1_instance" exposed_name="Exposed". When deserializing, to retrieve the node handle, there would be a lookup of a dictionary in scene_1 metadata, and then the produced path scene_0_instance/Exposed would be appended to the root path scene_1_instance.

Take for example a scene itself named MyCoolButtonPair. It contains two instances of a scene named MyCoolButton, each of which exposes a node named AddChildrenToThisNode to which children are meant to be added, to represent the label or icon or other content of the button. Maybe the game has a common need for paired buttons, like previous and next page, or increase and decrease a number. Our MyCoolButtonPair scene can then re-expose one of those nodes as AddLeftButtonChildrenToThisNode and the other AddRightButtonChildrenToThisNode. Now a parent scene can instance and re-use the MyCoolButtonPair scene, and it is clear in the editor which exposed node performs which role, and it is clear in the serialized scene to which MyCoolButton each re-exposed node belongs.

There'd also be the possibility to incorporate this into node path syntax, in case it's useful to represent the location specifically using a node path? Something like path_to_scene_root/%+exposed_name or some arbitrary thing like that. I'm unsure of the necessity, but this seems like it may be helpful particularly to simplify representing the location of a node re-exposed from a child scene in the metadata dictionary? That way everything can just be path strings, instead of requiring this parent path string plus exposed name string pair.

I'm aware that a node having one internal and one external name/identifier isn't something the editor is at all set up to accommodate right now, and so this could end up being clumsy from a UI standpoint. But it seems like this may preempt some significant obstacles to usability with instanced scenes if this could become a thing, and the UI can always be improved over time.

I'm not fully clear on exactly how UIDs are planned to work, so maybe that is an ideal solution to wait for that would already solve all problems? But it seems like the issue here runs a little deeper than just representing a reference to the node, right? Particularly because of the issue of duplicate names?

@yahkr
Copy link
Contributor Author

yahkr commented Nov 19, 2024

Trying to catch up, I think I've got a handle on why the unique names were dropped?

I have a suggestion:
...
I'm not fully clear on exactly how UIDs are planned to work, so maybe that is an ideal solution to wait for that would already solve all problems? But it seems like the issue here runs a little deeper than just representing a reference to the node, right? Particularly because of the issue of duplicate names?

I completely agree that the reliability on change is a must for godot. This is a logical approach and I considered something similar when hitting roadblocks (using internal/external paths/uids), and using meta data cleans it up a bit more.

Node UIDs fundamentally shift the way node references are managed, replacing path-based referencing with ID-based referencing, which directly solves the primary issue of why editable children and exposed nodes are unreliable on change. So I think it's best to await that feature instead of adding temporary bloat to this one to make it work like that. I know I'll be following that pr with great anticipation :)

@yahkr yahkr force-pushed the exposed_in_owner branch 3 times, most recently from 202da0d to b99c6a2 Compare November 19, 2024 13:42
@yahkr
Copy link
Contributor Author

yahkr commented Nov 19, 2024

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