Godot Version
4.3.beta1.mono
Question
I have a (non-scene) script of a class called “Order”
class_name Order
var state:State
func _init(_state=WaitState):
state = _state
And a subclass that directly inherits from this script called “BlockOrder”
class_name BlockOrder extends Order
var blockers= []
func _init(_blockers):
super(BlockState)
blockers = _blockers
Now in this scenario I have two, seperate, adjacent nodes that want to interact together.
In one of them I have an array of Orders that I want to manipulate and I want to do it with a statically typed function
var order_stack:Array[Order] = []
func order_stack_has_order_of_type(order_type:Order) -> bool:
for order in _order_stack:
if is_instance_of(order, order_type):
return true
return false
And then I want to call this function from the other, adjacent node as a sort of component
@export var order_component
order_component.order_stack_has_order_of_type(BlockOrder)
This throws the error
Invalid argument for “order_stack_has_order_of_type()” function: argument 1 should be “Order” but is “BlockOrder”.
Does static typing not allow for subclasses in the place of superclasses? Is there another way to do this?
You should be able to this. I do this in a current project (though in my case they are scenes. The base class is a script (with no associated scene) which extends Node3D
, and the implementations are scenes which extend the base class in a script.
I think your problem here is that you are passing BlockOrder
which is the name of a class, when you need to be passing an instance of the class, created like BlockOrder.new()
for example.
I’m not sure what you’re doing in the bigger picture here, but if you are trying to create a group of interchangeable sort functions, it might make sense to implement these simply as functions rather than classes and pass them around as Callable
. I’m not sure how to implement this with rigorous static type safety though.
2 Likes
I think your problem here is that you are passing BlockOrder which is the name of a class, when you need to be passing an instance of the class, created like BlockOrder.new() for example.
Thanks for the answer, I noticed that doing it like that worked, but it felt a bit strange that I had to instantiate an object of a class before I could check against the class. So I wanted to see if there was a better way I was missing to compare to class names.
The base class is a script (with no associated scene) which extends Node3D, and the implementations are scenes which extend the base class in a script.
Which I’m guessing this is it? Godot’s documentation says that scenes can be thought of as classes so I was considering making Node3D (or just Node) scenes from the Order scripts as well, but that also felt a bit strange since I would effectively have two files for one class, a script and a scene. Maybe that’s just me not used to Godot yet.
I’m not sure what you’re doing in the bigger picture here…
Very quick rundown is that an Order is just a formal tuple to store a state (for a state machine) and some piece of data together (a coordinate, a list of blocking units, a duration, etc.). I can’t really piece together how to make that work as a function.
Quick update now that I’m able to sit down and test it.
I think your problem here is that you are passing BlockOrder which is the name of a class, when you need to be passing an instance of the class, created like BlockOrder.new() for example.
This did not work. Same code as above except we’re sending in a BlockOrder.new()
gives us the error
Error calling GDScript utility function “is_instance_of()”: Invalid type argument for is_instance_of(), should be a TYPE_* constant, a class or a script.
Which makes sense, since now we’re sending in an object and not a class.
For now I’ve just removed the static typing from these functions since it’s a lot easier to work with when just sending in BlockOrder
, but I would really like to know the proper way to do it.
Huh… yeah. I’m not an expert in GDScript, but it does kinda seem like classes do not, themselves, have a first-class “Class”… class.
You could make the type Variant
to match the definition of is_instance_of
… but that’s basically the same as leaving it untyped. Obviously, the fact that you can pass BlockOrder
to a function means the language has some concept of this as an object it can work with. But the fact that Object.get_class()
returns a String, Object.is_class
takes a String, and ClassDB
operates entirely in Strings makes me think that there actually isn’t a type for types. Or at least not one that I can find.
I wonder, if you put a breakpoint in your order_stack_has_order_of_type
method, with the typing removed so it compiles and runs, I wonder if you can inspect at runtime what the BlockOrder
value is, to see if you can find a type for it?
Tested it out real quick.
The debugger says that order_type has a value of Object ID: 12345
Running the following prints
print(order_type)
print(typeof(order_type))
print(order_type.get_class())
Returns
<GDScript#-12345> (same ID value as the debugger)
24
GDScript
Where type 24 is a variable of type Object.
So it seems to be taking the script of BlockOrder
as GDScript
and instantiating that as an object. Which does explain why it complains that it isn’t a sort of Order
.
Does that mean you can change the type of your method to GDScript
and have it type-check and work?
Yes that works.
func order_stack_has_order_type(order_type:GDScript)
Not that I will keep it that way because that’s a very confusing thing to read.