Godot: Ability to export Node types

Created on 16 Feb 2017  路  28Comments  路  Source: godotengine/godot

Could we have a shortcut to assign Nodes in the inspector instead of only relying on NodePath?

Explanation:
I had to link individual nodes several times in my project, especially in tool scripts,
but I quickly found that I was repeating myself doing so.

Everytime, my code looked like this:

export(NodePath) var node = null setget set_node_path

var _node = null


func set_node_path(path):
    node = path
    if is_inside_tree():
        _update_node_field()


func _update_node_field():
    if node != null:
        var n = get_node(node)
        if n extends Node2D:
            _node = n
        else:
            print("Error: the node must be Node2D!")
            node = null
    else:
        _node = null


func _ready():
    _update_node_field()

I have to:

  • Use a backing field to store the actual node
  • test if we are in the tree to get it because otherwise access is denied.
  • update it in _ready() because deserialisation happens before it.
  • check for nulls because you can leave the path empty.
  • check the type because I usually need a specific one.
  • Eventually test if the path is valid before getting the node (not in the example)
  • Eventually do some more tests when setting the property (not in the example)

That's a lot of boilerplate, just for ONE property.

It would be really cool if we could instead have this:

export(Node2D) node setget set_node

# Property example, executed on ready
func set_node(node):
    # Do custom logic
    # ...

This would still be saved as a NodePath, however at deserialization, if the exported type inherits Node,
all of the previous boilerplate would be done to assign the field automatically.
As a bonus, because the node type is known, we can prevent designers from assigning wrong node types in the editor by greying them out in the selection window and cancelling drag and drop.

It won't break compatibility either because using NodePath would still work if needed.

What do you think?

archived feature proposal editor gdscript

Most helpful comment

BTW, I haven't seen this syntax mentioned anywhere, so maybe people is not aware of it:

export(NodePath) onready var node = get_node(node)

Not sure if it may cause problems with serialization.

All 28 comments

This would be very cool to have indeed...

From #7846:

  • Would be cool to be able to rename/reparent exported nodes, without having to reassign them (like the AnimationPlayer)

Hmm but that would mean exporting a NodeRef which is not that handy (because type info is lost), unless that's how we would implement it behind-the scenes? Also, self-updating references when nodes are moved in the editor is more like something the editor should care about. It could be fixed by designing Nodes so they are References but it would go deeper in Godot's design.

@Zylann No it doesn't mean exporting a NodeRef, though we can do something like export(NodeRef, Sprite) var .... just fine if it does.

I'm not fan of export(NodeRef, Sprite), exporting the type directly looks cleaner.

Wouldn't it be a better idea to enable NodePath objects to handle re-acquiring the new path to any repositioned nodes it was previously assigned to by having it connect an acquisition method to the node's enter_tree signal? This avoids having to create and manage a whole new type. Exporting a node type would still internally store a NodePath, but the NodePath would simply start regulating what type of node it is pointing to and emit signals if someone attempts to assign it improperly. This could all be toggled with some sort of node_reference flag that can be set in the Inspector manually (for regular NodePaths) and which would automatically be set if someone exports a node type. The barebones functionality doesn't seem like it would be that difficult to do, but the Editor functionality surrounding it (drag and drop, etc.) might take a bit more work...?

@willnationsdev you can't do that, NodePath is not even an object, it's a value type like Vector3 with no signal abilities, so you would still need to create a new NodeRef resource type. On a side note, a NodeRef would also be the only way to reference nodes in a generic way, since they are not reference counted. However, like any generic thing, it would have downsides: you can't tell if a node exiting the tree is a node you can consider null, because heh, it's not null yet^^ it could simply be reparenting, or temporarily removed from tree (which I need to do in my game, hence why I didn't come up with a generic solution either xD)

I do agree that exporting with the name of the node type directly is preferable to supplying it as a second argument after NodeRef. All in all, I think it'd be best if the existence of the NodeRef type were more or less hidden from the user and they wouldn't even have to think about it.

What about making export work together with onready? This way we could export nodes like this: onready export(Node2D) var node. Exporting a node without onready would result in a compiler error.

@neikeq That would probably be a good idea for initialization via code.

Isn't there another problem here in that Variants are flexible enough to accept any sort of assignment and will just adapt the type rather than performing some sort of validation on the type? I mean, even if you do export(Node2D) var node, are we then going to say that any assignment to node has to be an object deriving from Node2D? If not, then this is mainly just an initialization from the Editor issue where we are simply supplying a NodePath and performing some simple path / type checks at design-time / game startup. Am I wrong?

@neikeq that would solve the use case of linking when game starts but it would not solve the fact I still need all this boilerplate in tool mode

Having skimmed the comments, while I understand export(Node2D) var node might be preferable, my concern is that this may cause issues later down the line when we want to add something new to the export keyword.

Also personally, export(NodePath, Node2D) var node seems a lot clearer to me. Read it as a sentence: "export a NodePath of a Node2D to a var named node". Compare this to what people seem to be preferring: "export a Node2D to a var named node" -- but aren't we just exporting a NodePath in reality?

@LikeLakers2 I agree with you, but it's not only about node path. I use NodePath because that's currently the only way, but in my use case I really don't care about the path (it's just an intermediate I wish could be transparent), all I want is a node.

Typing an export of NodePath be useful, just less performant due to having to get the node by path (could be every frame), and different meaning since path changes or renaming would break the link. But at least, it's a secure option.

I'm aware my ideal need cannot be implemented currently without corner cases though, mostly due to the impossibility to reset references to a node when it gets deleted in editor AFAIK (my current boilerplate has this issue anyways), though Unity is fine with it.

Btw I'm not settled on the syntax, I mostly want the feature so I dont have to write all this code^^

@Zylann Ah. Well my concern about issues down the line with export(Node2D) var node still stands. Though I'm not sure how we would fix that sort of concern, besides with the export(NodeRef, Node2D) idea, which nobody here really seems to like. :/

BTW, I haven't seen this syntax mentioned anywhere, so maybe people is not aware of it:

export(NodePath) onready var node = get_node(node)

Not sure if it may cause problems with serialization.

@neikeq Oh nice, didn't think of that. If you could just supply a second parameter to the export for the type of node then, as @LikeLakers2 suggested, then that would pretty much deal with the initialization issue.

@Zylann As for handling subsequent assignment type checking, I don't think that would be something you can support out of the box until Godot starts implementing optional static typing in GDScript. You'd have to actually create an Object that you assign values to and which performs the type checking for you (like the NodeRef you referenced earlier). It's basically just a layer of indirection so that you can have more fine-tuned control over the assignments to a particular Variant, but that may be more generally useful overall, like being able to assign a user-defined "validate_assignment" function to an individual Variant instance. Oh wait...that's what setget does. XD

Wouldn't you then have to make modifications to Variant, telling it, "Hey, you, this instance, you're gonna only be null or Object instances deriving \

@willnationsdev as I said, the feature I expect is doable in current Godot, just can't cover all corner cases, and syntax is a formality. I guess I'll stay with all my boilerplate for now... :/

@blurymind since you are wondering about it, this could be illustrated as...

export(Sprite) var sprite = null

You might then see a "Sprite" property in the Inspector and could assign only a Sprite or Sprite-derived node to that value. Now, whether it shows up in the Inspector as a NodePath or something else, I don't know. Would probably need to be a NodePath since nodes don't have a design-time ID of any sort. But then, if the value displayed in the Inspector were a NodePath from the current node to the node represented by the stored NodeRef, you could update the NodePath's value anytime the targeted node was reparented, via a signal.

@willnationsdev node paths would be used only for serialization purpose, in any other cases the property would behave like a normal pointer to the node, in terms of usage (so no node path in the inspector). Which means there is no update to do when the node is moved in the tree, it just works (tm). But I think other details were discussed earlier in this thread.

@LikeLakers2 One nice way to express that in C# would be NodePath<Node2D>, though it probably won't ever be done that way, since we would first need an implementation in GDScript some other way, and then that could likely be mirrored for C#.

this would be so nice for error checking.
allowing the user to pass in nodes that the script relies on.

I'm not familiar with GD script as I have just been using the C# interface, so I may be missing something in this conversation. However I think it is extremely important that Godot have a good way of assigning a node reference to a variable in a script. Currently the only way I am aware of is using an exported NodePath. This works fine except that if node A has a script with a NodePath that points to node B, the NodePath will not be updated if you move either A or B in the same scene. This is NOT good behavior under any interpretation! If the nodes are in the same scene, the editor should update all the affected NodePath values in order to preserve references within that scene.

This is certainly how other engines like Unity work and is critical for efficient workflow. Why on earth would you NOT want to update the NodePaths? Otherwise a NodePath is little better than a hard-coded path and is nearly as rigid. I dread any time I have to rearrange nodes in a scene at this point because it breaks all references and I have to re-assign all the NodePaths in the editor.

I am not asking for this to happen at run-time, only in the Editor, and only within a single scene. Just update the NodePath values. It is possible to over-complicate anything, but we just need a simple way to easily assign nodes to variables in the editor.

@BrianCook This is being tracked in #3163.

This is certainly how other engines like Unity work and is critical for efficient workflow

In fact I believe Unity does not even need to do that, because it stores game object "fileIDs" (i.e the ID of an object within the scene file), not their path. Which works fine as references by design.

if node A has a script with a NodePath that points to node B, the NodePath will not be updated if you move either A or B in the same scene.

I thought Godot was already updating NodePaths 馃

@Zylann Afaik, Godot does update exported NodePaths automatically if the referenced node's relative path from the current node is changed.

What is not yet supported is automatically converting the value to a Node and enforcing type constraints on the exported NodePath target, e.g. for GDScript's optional static typing.

It's a shame that feature wasn't implemented yet. That ruins all the workflow. Serialization is not an excuse because you can easily extract NodePath from NodeRef and vice versa. So far we just need a wrapper that holds node sessional id and handles like a real node inside the code. (Not to mention type restriction in editor.)

Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.

The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.

If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rgrams picture rgrams  路  3Comments

SleepProgger picture SleepProgger  路  3Comments

mefihl picture mefihl  路  3Comments

ducdetronquito picture ducdetronquito  路  3Comments

n-pigeon picture n-pigeon  路  3Comments