godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Implement support for Node Stashing #10212

Open reduz opened 1 month ago

reduz commented 1 month ago

Describe the project you are working on

Godot

Describe the problem or limitation you are having in your project

Multiple times, when editing scenes in the editor, users would like to (from the editor) set a branch or a node to a disabled state, so when the scene is loaded it does not do absolutely anything (acting as if it was not there). This can be done with the intent of either leaving it disabled (just for debugging), or eventually enable it back under some gameplay condition, as fast as possible, so it starts working normally again.

Currently, to achieve something like this, Godot allows:

While this works to some degree, there are shortcomings with these approaches:

This means, that it is not possible to reach a complete disabled state. Users expect the node to exist (in order to enable it), but to act as if it was not there for the rest of the nodes.

Related proposal: https://github.com/godotengine/godot-proposals/issues/7715

Describe the feature / enhancement and how it helps to overcome the problem or limitation

This proposal is based of Kobewi's idea to allow users to mark nodes as stashed, either in the editor or by code. When a node is marked as stashed, it will load together with the scene but it will not appear in the tree (nor will its sub-children). Instead, it will be stored in its parent node stash.

If the user chooses, they can unstash the node (it gets added back to the tree) and/or stash it again, which happens immediately. Ultimately, stashed or not, the parent node keeps track of the child node and it will erase it properly together with the rest of the children nodes when freed.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Nodes can be stashed or unstashed back by calling the following functions

# On Parent
Node.stash_child(chid : Node)
Node.unstash_child(chid : Node, at_index : int = -1)
Node.unstash_by_name(chid_name : StringName, at_index : int = -1)
Array<Node> Node.get_stashed_children() const
Node Node.has_stashed_child(chid_name : StringName) const
Node Node.get_stashed_child(chid_name : StringName) const
# On Child
Node.stash_in_parent()
Node.unstash_from_parent()
bool Node.is_stashed() const
Node Node.get_stash_parent() const # get_parent() will not work because node is out of tree.

To make nodes stashed by default, an option will be added to the Scene Tree editor:

image

This will make the node stashed, and the stash icon will appear:

image

Clicking back on the icon will un-stash, similar to other states like unique node.

Then, when instantiating the scene, the node will begin stashed (out of tree). If nothing is done, everything will go on as if the node never was there. Otherwise, it can be brought back by doing

unstash_child( get_stashed_child("DirectionalLight3D") )
# or
unstash_child_by_name("DirectionalLight3D")

If you are using this API from code to disable nodes and back, you can just use the pointers instead:


onready var mynode = $MyNode
...

stash_child(mynode)
unstash_child(mynode)

The lifecycle of these nodes when stashed/unstashed would be the same as when added/removed from the scene, so:

Then on exit:

Live edit integration

Stashing nodes on the editor should use the debugger protocol to send stash/unstash commands to nodes in the running game.

FAQ

Q: Why is it called "stash" and not "disable"?\ A: Because disabling in the context and language of Godot is more related to other functions such as visibility or processing state. The only true way of fully disabling in Godot is removing from the tree, hence this functionality works with that and is called "stashing".

Q: Why not an "active" property in the node?\ A: This is something often requested by users coming from Unity. As mentioned above, Godot has many ways to deactivate parts of a node, by either changing the processing mode or the visibility. Changing those does not completely deactivate a node. In Godot, by design, the only way to make a node 100% and completely inactive is removing it from the tree (and this will not change, for both complex technical an compatibility reasons), hence this proposal is built around pre-existing design constraints to allow solving the same problem in a cleaner and more "Godot" way, where everything will work in a completely predictable way to the user.

Q: I don't care about the editor part that much, is stashing/unstashing the same as completely setting a node to active/disabled?\ A: Yes. Again keep in mind stashing is a bit conceptually different to how it works in Unity because it gets removed from the tree (though still book-kept by the parent, of course). But in practice it can be used the same.

Q: What's the difference with manually adding and removing the tree from the scene?\ A: This is a more elegant way to handle it that also allows to edit the nodes default stashed state visually in the editor, while having them load out of tree. This is not possible (or at least easily) with code currently.

Q: I like this function, but I would prefer the actual stashed are not actually not even loaded if I don't need them. Is this how it works or possible?\ A: The whole concept of stashing is that it remains around until you need it, so these nodes remain loaded and fully configured in memory. Additionally, due to how Godot works, what is in the scene will always be loaded, because resources are loaded before the scene. In some cases, though, you may want the opposite, but for this Godot already has Placeholder function. To do a workflow as you want, you do as follows:

Q: So this is like a dynamic placeholder?\ A: In part yes, but in part this function is meant to be used directly from code too, to make bookkeeping of nodes outside the scene simpler and safer.

Q: Why is not possible to stash by node path, such as $MyNode.stash() and $MyNode.unstash()?\ A: Because when a node is stashed, it is effectively removed from the tree. If it stayed on the tree, external factors such as other nodes, animations, game code, etc. will still interact with it and this would easily result in weird bugs that are hard to figure out, as the node thinks its out of tree. Still, you can cache a pointer and use it to avoid using strings. The proposed API follows the same convention of the rest of the bookkeeping APIs in Node.

Q: Would it not be better to have a stash icon similar to the eye icon for every node instead of an option in the menu?\ A: Despite the demand, It is not clear how much this option will be used. It can begin as an option in the node menu and, only if enough demand, can be moved more prominently (or moved with an option). For the time being, if you need to use this fast, keep in mind the node menu options have shortcuts assigned and you can use that.

If this enhancement will not be used often, can it be worked around with a few lines of script?

N/A

Is there a reason why this should be core and not an add-on in the asset library?

N/A