Why do the get_class() function returns a different outcome depending on how you call it?

I was struggling to understand why the override of get_class() wasn’t working in another part of my code, since it was working just fine in my main script. And then, I figured out that the get_class() was only working 'cause I was calling it from a variable that contained the node I wanted to call get_class(). Attaching the node to a variable and then calling get_class() fixed my problem, but I still wanna understand why it doesn’t work if I call it directly from a node of my scene tree. That doesn’t makes sense to me. If someone can explain for me what’s happening, I’d appreciate. Here is my code:

var object = load("res://scenes/white's farm/weapons/pistol.tscn").instantiate()
func _ready():
	add_child(object)
	var child = get_child(-1)
	print(get_child(-1).get_class()) #Gives wrong outcome
	print(child.get_class()) #Gives right outcome
	print(object.get_class()) #Gives right outcome

And here is the outcome:

Sprite2D #Wrong outcome
Weapon #Right outcome
Weapon #Right outcome

It seems like get_class should ignore class_name declarations.

When you say override do you mean you wrote a function?

Also the is keyword is better for testing type.

2 Likes

I did override the get_class() method rewriting the function in my Weapon class, as I show here:

class_name Weapon extends ItemBlueprint

@warning_ignore("native_method_override")
func get_class(): return "Weapon"

You’re right about get_class ignoring class_name declarations, that’s why I did override get_class() to return me my desired outcome, as it seems there’s no other way to do that. Also, thanks for the advice on using the “is” keyword, but in this case, I need to verify the String, as I’m using the outcome in a match statement. The “is” keyword only work with if/else, not with match statements unfortunately, and that’s why I’m using get_class() for comparing instead.

Btw, why did you highlight the “get_child(-1)”? Is it a bad way of getting a node or a bad practice? Thanks for helping me.

Switch you match to a few if/elifs and use is that will be the best way to accurately get the class with class_name extensions. You could make your own enum for types of blueprints if you really want to keep match.

1 Like

I thought about doing an enum, but the problem is that I made this all over my scripts, so I don’t have plans to change it now, as it works fine. As I said before, I fixed my problem. I just made this post mainly to understand why it works this way:

var child = get_child(-1)
print(child.get_class())

But not this way:

print(get_child(-1).get_class())

I’m trying to figure out why this happens and maybe find a aswer that might help somebody else here in the forum. In short, I made this post 'cause I was curious to understand why the code behaves like this. Btw, thx for your response, I appreciate that!

This is a hack but you can do it this way

var types :Array[Node]= [MyClass.new(), Node.new()]

func _ready():
	for t in types:
		var s = t.get_script()
		if s == null:
			s = t.get_class()
		match s:
			MyClass:
				print("Myclass")
			"Node":
				print("node")
			_:
				print("idk ", s)

#output
Myclass
node

Also i;m kind of confused how your are overriding the function when you technically shouldn’t

Line 5:The method "get_class()" overrides a method from native class "Object". This won't be called by the engine and may not work as expected. (Warning treated as error.)

ah, But i guess i see you ignore that warning

The reason behind this is because of performance reasons. Native class functions do not have a virtual declaration and technically cant be overridden.

But when I do it it works

#main.gd
extends Node3D

class MyClass extends Node:
	var p = ""
	@warning_ignore("native_method_override")
	func get_class():
		return "MyClass"
	pass 

class TestChild extends MyClass:
	@warning_ignore("native_method_override")
	func get_class():
		return "TestChild"

var types :Array[Node]= [MyClass.new(), Node.new(), TestNode.create(), TestChild.new()]

func _ready():
	for t in types:
		add_child(t)
		var c = get_child(-1)
		var s = c.get_class()
		#if s == null:
			#s = c.get_class()
		match s:
			"MyClass":
				print("Myclass")
			"Node":
				print("node")
			"TestNode":
				print("TestNode")
			_:
				print("idk ", t)

#testnode.gd
extends Node
class_name TestNode

static func create():
	return load("res://testnode.tscn").instantiate()

@warning_ignore("native_method_override")
func get_class():
	return "TestNode"



# output
Myclass
node
TestNode
TestChild

My guess is that the gdscript function is shadowing the native class function, and is not actually overriding.

1 Like

That’s interesting. So, if I get my node directly and call get_class(), like this:

print(get_child(-1).get_class()

the get_class() function will be shadowed, not overridden, and if I put it in a variable first, like this:

var a = get_child(-1)
print(a.get_class())

the get_class() will be “overridden”? That’s some good information to write down.
Also can you explain me what is a “virtual” declaration and why does that impact performance? Thx a lot for your response, it’s was very useful!

virtul declarations are callback functions the godot engine executes when an event or signal occurs.

So func _ready() is a virtual function because it gets called automatically. Same with _init()/ Another one is _notification.

You then write your code to react to that event accordingly, based on parameters or anything else you are monitoring.

The convention is for virtual functions start with an underscore.

1 Like

So virtual declarations are callbacks that run automatically when override them, and you just decides what it’s gonna happen inside that? If so, that’s a concept of polymorphism, wich is something I gotta study again, 'cause I don’t remember a thing about that :sweat_smile:. Anyway, thank you for your response, I apprecciate that!

Thank you all guys for your answers, you’re all have been very helpful! I have no more questions!

There is hidden control flow when it comes to get/set property. When you define a property you automatically get functions to set and get.

var foo

...

self.set_foo("a sting")
print(self.get_foo())
print(self.foo)

# output 
a string
a string

There is some strange things happening under the hood that supports this property functionality. I wonder if get_class() has some aspects of this hidden control happening that in this case can allow it to work when making a pure gdscript override.

But I would definitely heed the warning in general. Because some internal c++ functions for a class may call the native version only because it will not know that the override exists in gdscript side. (Because of the virtual function mechanics of of c++ and the binding to Gdscript)

In this case I don’t think native c++ code utilizes the get_class() function internally at all and uses different c++ mechanisms to do something similar.

I’m not sure how get_class didn’t work in your example, I tried to replicate it but did not succeed.

2 Likes

Yeah, this was some strange behaviour that happened. I just put my object in a variable and bum, it worked like magic. And about the warning, knowing that it works differently depending on how I call it, I think I shouldn’t have any problems, because I can call it in the way that fits better in my code. As you mention, the get_class() is probably working “under the hood” like the setters/getters, but if you said you couldn’t replicate my problem, probably it’s my code. I’m gonna be working on my project. If I found out why this happens only to my code, I’ll let u know. Anyway, thx for the help and the explanation!

1 Like