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

Custom controls cannot be instantiated via GDScript #21410

Closed
andrea-calligaris opened this issue Aug 25, 2018 · 19 comments
Closed

Custom controls cannot be instantiated via GDScript #21410

andrea-calligaris opened this issue Aug 25, 2018 · 19 comments

Comments

@andrea-calligaris
Copy link
Contributor

andrea-calligaris commented Aug 25, 2018

Godot version:
3.0.5-stable_x11.64, Xubuntu

Issue description:
If you create a plugin to make a custom control, like a customized LineEdit, you can add it into your game choosing it from the classes.
However, you cannot add your custom control via GDScript, in fact the parser won't recognize its name. The workaround for this is to just instantiate a base control and add a script to it, however this has some disadvantages:

  • you have to add a script everytime (you can however create a .tscn which already have the script attached, but you will have an unnecessary .tscn scene.
  • the custom icon won't appear
  • it will be shown as an instanced scene, so it will have a clapperboard symbol in the dock
  • the class (get_class()) of the control will be the same of the base one

There should be a way to properly instantiate a custom control via GDScript. Or maybe there is and I can't find it.

Example project:
customized_control.zip

@vnen
Copy link
Member

vnen commented Aug 25, 2018

In master you can use script classes for this. I don't think it's worth it adding more features to add_custom_type() at this point.

@koodikulma-fi
Copy link

koodikulma-fi commented Sep 23, 2018

@akien-mga: Could you please elaborate on how to do that?
I was also looking for this. Or a method like instance_custom_type or get_custom_type which would return the base class/type (?) so you could call .new() on it to create a new instance. So, just like you can call Node.new(), you could do get_custom_type("MyCustomType").new().

@vnen
Copy link
Member

vnen commented Sep 23, 2018

The problem is that custom types are only an editor feature. Since the plugins aren't active when the game is running, there's no custom type registered at that point.

One solution would be to register those types in the project settings so they could be loaded in-game. But since named script classes already do this, it's redundant to add that now for plugins.

@koodikulma-fi
Copy link

koodikulma-fi commented Sep 23, 2018

Okay, thanks a lot for the clarification. Actually I was exploring into this just for "cosmetic" and editor-only reasons: making a dock plugin for quickly creating some basic as well as custom nodes into the scene. (So, in my case, the only set back is that the custom nodes won't show their dedicated icon in the scene tree.) Anyway, thanks, vnen! (And sorry for misreading who commented.)

@koodikulma-fi
Copy link

koodikulma-fi commented Sep 23, 2018

If anyone's interested found a decent enough work-around - at least for creating custom nodes in editor. I think this also works for the original issue:

  1. Make a dummy scene and insert your custom nodes in it (not as root, tho, see below), save it.
  2. To create a new instance of your custom node in editor, load this dummy scene, instance it and duplicate the child you want and insert it (and set owner). And voilà ;)
var custom_node = load(dummy_scene_path).instance().get_node("MyCustomNode").duplicate()
parent.add_child(custom_node)
custom_node.owner = get_tree().get_edited_scene_root()

If you know how to make an instance local in script (not context menu), then you can instead save your node as the scene root and just instance it without duplicating - otherwise it will be a PackedScene instead.

Edit: Can also save the node as root. In this case set custom_node.filename = "" to detach it.

var custom_node = load(dummy_scene_path).instance()
custom_node.filename = ""

@andrea-calligaris
Copy link
Contributor Author

@takaturre

I think this also works for the original issue:

Not really.
All the points in the bulleted list in my OP would still be valid.
Your solution is no different than just normally instancing the node and "manually" attaching the script, which is considered in point # 1. However it is useful if your custom node have children nodes. In that case you can keep the children "folded" (no children displayed in the tree), if your method does work (didn't tried it yet).

@koodikulma-fi
Copy link

koodikulma-fi commented Sep 24, 2018

Yes, exactly, that is the "solution", and it works perfectly for my case. It is not meant to be a perfect/correct solution, more like a hacky work around useful for some cases - for editor use, not so much in-game use.

Anyway, sorry, if it didn't work for you. However, all the points in the (original) list are met, except no. 4 (which is imo another topic in itself):

  1. You do not need to manually attach the script at any point, it is automatically prepared in tscn. (Of course this scene is otherwise unnecessary in game - however, if using as an editor-only tool, you don't need to load that scene into the actual game at any point.)
  2. The custom icon does appear,
  3. It will not be shown as an instanced scene if you don't insert the root node (or know how to make a instanced scene local by script), Edit 2: You can make a scene "local" (detach the reference), simply by doing: node.filename = "" (and so that clapperboard will disappear in the editor).
  4. get_class() command returns the same as if I add the node from the + button and search it from the list. It gives the base class from which the custom node extends.

... I thought a custom node is always one node. So the children thing is really unrelated, right? However, using a scene, this method of course supports that if you want to add children inside a custom node and fetch the whole family instead of just one node. (Edit: If you do this, rememeber to set the owner for all kids recursively - except the ones belonging to a nested instanced scene. Here is a pic for that func. )

Btw.. I noticed that you could also instance your custom node without first making a base class node. Thought it would't work but it actually did, at least for some cases - however, the icon didn't appear (which led me here originally). So:

	var child = load("res://addons/my_node/my_node.gd").new()
	add_child(child)
	child.owner = get_tree().get_edited_scene_root()

... To be honest, I'm a bit confused about the "core issue" here. If you really want this IN GAME, why do you need the icon and such..? Why do you need to use get_class()? There are likely other methods you can use. (For example, get_script().get_path() or preload a script and check is MyClass, etc.) What are the core practical reasons..?

@andrea-calligaris
Copy link
Contributor Author

@takaturre I tried and it works indeed. Thanks for sharing!

The hack keeps the icon indeed, and it's not considered an instanced scene.
No, I didn't care about in-game use, but you may want to use get_class() in the plugin code, but as you correctly found out, it gives the base class even when you manually select the node from the class list, so it's another Godot limitation; not a big deal though.

@koodikulma-fi
Copy link

Glad to hear it worked for you! Yeah, I agree get_class() is a bit confusing, because a custom node is essentially a custom class.

@Zylann
Copy link
Contributor

Zylann commented Dec 21, 2018

I've just found this in 3.06 too, so here is my workaround:

	# Instance it
	var layer = preload("hterrain_detail_layer.gd").new()
	# Give it the icon
	layer.set_meta("_editor_icon", preload("icons/detail_layer_node.svg"))
	# Give it a proper name (otherwise it just says "Spatial")
	layer.name = "HTerrainDetailLayer"
	_terrain.add_child(layer)
	# Make it editable
	layer.owner = get_tree().edited_scene_root

@koodikulma-fi
Copy link

Thanks a lot for sharing! Should make things much easier in some contexts. (I will check this later for myself.)

@willnationsdev
Copy link
Contributor

willnationsdev commented Mar 10, 2019

If I'm not mistaken, don't 3.1's script classes fix all of these issues?

extends Control
class_name MyControl, "res://my_control.svg"

# main.gd
extends Node
func _ready():
    add_child(MyControl.new()) # you have runtime access to the typename
  • The node will appear as an option in the CreateDialog with the custom icon
  • The node's custom icon will display in the scene dock.
  • It doesn't involve a scene, so there are no "scene" UI details

get_class() returning the base name rather than the class name is its own Issue now (#21789).

@koodikulma-fi
Copy link

koodikulma-fi commented Apr 8, 2019

Thanks for the info!

I tested your method, willnationsdev, but when adding a custom class by an editor script, it didn't load up the icon. However, as Zylann pointed out, it's very easy to set by: node.set_meta("_editor_icon", preload("icons/node.svg"))

.. If I remember correctly, it always worked that you can find your custom class (with the custom icon) from the CreateDialog - issue was about adding with GDScript. (If it didn't, certainly works now.)

@Calinou
Copy link
Member

Calinou commented Dec 1, 2020

Closing per @willnationsdev's comment.

@Calinou Calinou closed this as completed Dec 1, 2020
@Calinou Calinou added this to the 3.1 milestone Dec 1, 2020
@Zylann
Copy link
Contributor

Zylann commented Dec 1, 2020

So that means add_custom_type should have been deprecated since then?

@willnationsdev
Copy link
Contributor

@Zylann can't properly deprecate custom types until script classes are made available in all languages.

@koodikulma-fi
Copy link

Btw. This is a separate issue, but a word of caution: I stopped using the class_name MyControl, "res://my_control.svg" -style, as it made the whole editor really slow having created multiple classes (especially creating and removing nodes of these classes, as well as the "Add new" -popup - at least on Win10 in Godot 3.2). Took a while to figure out where this was coming from.
--> Anyone needing similar stuff (eg. to create and setup some custom nodes via a dock button or such), recommend to create a script to instance them and put the correct icon manually.

@Calinou
Copy link
Member

Calinou commented Dec 1, 2020

@takaturre This is being tracked in #27333.

@Zylann
Copy link
Contributor

Zylann commented Dec 1, 2020

@willnationsdev it's even still needed because class_name used under addons/ does not appear in the node dialog (I assume it's because plugins can be turned on/off?)

Also, seeing they were not appearing, I wrote a plugin using add_custom_type to do so. That made them appear 3 times in a row :|
There are other issues so can't really go all in for class_names yet in plugins tbh.

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

No branches or pull requests

7 participants