How to get class name of native class

See here and here for documentation of the typed array constructor.

1 Like

I read those yea, this was as close as I could get

func _ready():
	var a:Array[Sprite2D]=ta("Sprite2D")
	print(name,": ME ", 
			type_string(typeof(a)), " ", 
			a.get_typed_class_name(), " ", 
			a.size()
		)

func ta(type_name:StringName, script:Script = null) -> Variant:
	var typed_array : = Array([], TYPE_OBJECT, type_name, script)
	typed_array.append(ClassDB.instantiate(type_name))
	return typed_array

image

The only problem is that when I use a custom class it complains that the types don’t match but the error says they do…

This also doesn’t work for native types.

Instance the class first works for scripts loaded from disk. It should work also in your case.
var class_instance = (script as GDScript).new()
print(class_instance.get_class())

1 Like

Not very efficient though, creating a new node everytime the function is called.
Probably negligible, since it usually will only be called once in a few places, but still feels dirty.

Can you not just create an enum in your instanced object with the type that it is? That enum could be based off a global enum list which is just a subset of the ojbects you are interested in.

I assume you dont need to know all of the different types of objects in Godot and only need a subset.

A bit hacky and technically not necessary if it was exposed , but gets you around the problem for now.

I know this does not exactly answer the question - but I wondered if it might help.

Here is my solution to find_nodes_by_type:

I implemented a ‘matches_class’ comparator, that way I could tweak the behavior. Maybe I just want to compare by type reference, or string, maybe by an instance’s type, etc. Here, I have it check by type reference, or by the class name as a string. Note that I check both the type of the passed object, or the attached script:

static func matches_class(obj, klass) -> bool:
	# A more permissive version of is class that checks:
	# * If obj is of the class (exact or subclass)
	# * If obj matches the class_name (e.g., a string was passed in)
	# * If obj is a node that has a script with the class
	# * If obj is a node that has a script with the class_name
	if klass is String: return matches_class_name(obj, klass)
	else: return matches_class_type(obj, klass)

static func matches_class_name(obj, klass: String) -> bool:
	# Check if the object itself matches the class or any subclass
	if obj.is_class(klass): return true
	# Traverse through attached script and its base scripts
	var script = obj.get_script()
	while script:
		if klass.to_snake_case() in script.get_path(): return true
		script = script.get_base_script()
	return false
	
static func matches_class_type(obj, klass):
	# Check if the object is an instance of the class or inherits from it
	return is_instance_of(obj, klass)

(Again, you can tweak this behavior however you like.)

Then you can write whatever functions you need to, say, recursively find a matching node in the children. I’ve got these:

# Functions for finding by script or class name (see 'matches_class')
func get_matching_node(script, allow_null=false):
	return Utils.static_get_matching_node(get_tree().root, script, allow_null)
func get_matching_nodes(script, allow_empty=false) -> Array:
	return Utils.static_get_matching_nodes(get_tree().root, script, allow_empty)
static func static_get_matching_node(node: Node, script, allow_null=false):
	var res = Utils._static_get_matching_node(node, script)
	assert(res != null or allow_null, "Could not find a %s (%s) = %s"%[script, node, res])
	return res
static func static_get_matching_nodes(node: Node, script, allow_empty=false) -> Array:
	var res = Utils._static_get_matching_nodes(node, script)
	assert(res != [] or allow_empty, "Could not find a %s (%s)"%[script, node])
	return res
static func _static_get_matching_node(node: Node, script):
	if Utils.matches_class(node, script): return node
	for child in node.get_children():
		var res = Utils._static_get_matching_node(child, script)
		if res != null: return res
	return null
static func _static_get_matching_nodes(node: Node, script) -> Array:
	if Utils.matches_class(node, script): return [node]
	var res = []
	for child in node.get_children():
		res += Utils._static_get_matching_nodes(child, script)
	return res

Which allow me to call it either as a static utility function, or to just search the whole game tree. Then I can use these utilities to implement easy “find_closest_to_player” and whatnot.

(Sorry if the comments and whatnot are sloppy, just wanted to share incase someone can build something useful from it).

Here’s some excerpts from my unit tests to give you an idea of how it can be used:

func _ready():
	var outhouse_prefab = preload("res://scenes/buildings/outhouse.tscn")
	var outhouse = outhouse_prefab.instantiate()
	var tent_prefab = preload("res://scenes/buildings/tent.tscn")
	var tent = tent_prefab.instantiate()
	add_child(tent)
	add_child(outhouse)
	
	assert(Utils.matches_class(outhouse, Outhouse))
	assert(Utils.matches_class(outhouse, "Outhouse"))
	assert(Utils.matches_class(outhouse, "Bathroom"))
	assert(Utils.matches_class(outhouse, Building))
	
	assert(not Utils.matches_class(outhouse, "Tent"))
	assert(not Utils.matches_class(outhouse, Residence))
	
	assert(Utils.matches_class(tent, "Residence"))
	assert(Utils.matches_class(tent, Building))

    assert($"/root/UtilsNode".get_matching_node(Outhouse) == outhouse)
	assert($"/root/UtilsNode".get_matching_nodes("Bathroom") == [outhouse])
	assert($"/root/UtilsNode".get_matching_nodes(Residence) == [tent])])
	
	assert($"/root/UtilsNode".get_matching_nodes("Building") == [tent, outhouse])
	assert($"/root/UtilsNode".get_matching_nodes(Building) == [tent, outhouse])

Frankly, I’d like to see some kind of search like this implemented as a standard Godot func. I know it is not performant, but it feels like the type of QOL thing most projects will use to start off, and then you can always replace it with autoloaders and specific references later to optimize.
But, I’m no expert - just a lay-user’s opinion.

Did you even read the post? This has nothing to do with the question.

Hey,

This may not be ideal, but doesn’t Godot allow cross language scripting?
That sounds like a problem that could be solved by a c++ template<>

(I suppose C# can do the job properly, but I can only speak of what I know).

I hope this isn’t total nonsense and that it helped.

I was suggesting one could rewrite their recursive finding script to take a string, rather than the type reference. Then, one could do something like:

func find_nodes_by_type(node, type_name: StringName, script=null) -> Variant:
	var typed_array : = Array([], TYPE_OBJECT, type_name, script)
	typed_array.append_array(static_get_matching_nodes(node, type_name))
	return typed_array

Which would allow you to use typed arrays:

func _ready():
	var nodes: Array[CollisionShape3D] = find_nodes_by_type(get_tree().root, "CollisionShape3D")

Perhaps it isn’t useful to you, as you are using find_nodes_by_type(node: Node, type: Variant), and it would be more cumbersome for you to pass in a string. I don’t know how you are using find_nodes_by_type. But for me, this solution allowed me to use a find_nodes_by_type by passing a variant OR by passing a string_name, thus I can pass a variant when the code is cleaner that way, or a string if I want GD to do runtime array type checking as you do.

(all the code is just WIP - anyone can clean it up or tweak to use as they like - its just to give you an idea.)

But yeah - it doesn’t answer your question exactly, but would be useful to past-me who found this thread on google as I was trying to implement my own ‘find_nodes_by_type’

The function to get the native type is not exposed to the GDScript API, hence it is also not exposed to the GDExtension API.

1 Like

The answer to this question is, there is no solution but ugly workarounds.

See also Expose class name in GDScriptNativeClass · Issue #9160 · godotengine/godot-proposals · GitHub.

Sometimes people make mistakes. We all do our best :slight_smile: Godot’s support for c# isn’t perfect but always improving.

If I needed to get the type of a c# class (assuming it was a class I created) then I would stick the string type in meta data.

my c# class:

[GlobalClass]
public partial class CsharpClass:Node2D {
	public CsharpClass() {
		SetMeta("TypeOfNode", Variant.From(GetType().ToString()));
		SetMeta("SecretMessage", Variant.From("This is a secret message."));
	}
}

my GDscript:

extends Node2D

@onready var m_node : Node2D = $/root/TestProject/SomeCsharpNode

func _ready() -> void:
	if(true == m_node.has_meta("TypeOfNode")):
		print ('m_node.get_meta("TypeOfNode") --> ', m_node.get_meta("TypeOfNode"))
		
		var v_new_node : CsharpClass = load(m_node.get_script().resource_path).new()
		print (v_new_node.get_meta("SecretMessage"))

Or from the root in the scene file, I would override _Notification, watch for CsharpClass , and do whatever from there. None of this is perfect, but it is still much nicer than being mindful of malloc/free statements.

	void MyNodeAdded(Node a_node) {
		if(a_node is CsharpClass _a_csharp_class) {
			GD.Print("a CsharpClass was added to the scene tree! Here is its secret message: ", _a_csharp_class.GetMeta("SecretMessage"));
		}
	}
	public override void _Notification(int a_notification) {
		if(NotificationEnterTree == a_notification) {
			GetTree().NodeAdded += MyNodeAdded;
		}
	}