• Godot HelpProgramming
  • Is a Node an object instance of the script it is attached to, or simply a separate object?

somebody suggested I throw this question over here so here it is.

i'm wondering if when you extend from super-classes, the node the script is attached to becomes an instance of the subclass the script represents. or maybe it's just some separate object that is paired with the script in some way?

No, node stays the same no matter what script you attach to it. You can change the script attached to a node during runtime.

This discussion was caught in the moderation queue since you have not confirmed your account yet.
Upon creating your account you should have received an account verification email. The confirmation email may have been incorrectly flagged as spam, so please also check your spam filter. Without confirming your account, future posts may also be caught in the moderation queue. You can resend a confirmation email when you log into your account if you cannot find the first verification email.
If you need any help, please let us know! You can find ways to contact forum staff on the Contact page. Thanks! :smile:
12 days later

@xyz said: No, node stays the same no matter what script you attach to it. You can change the script attached to a node during runtime.

there are some operations, like accessing a nonstatic method from outside the script that requires classes to be instantiated. since these methods can be accessed through nodes, wouldn't that mean the node instances the script? i imagine it'd be conceptually simple to switch the scripts attached to nodes in either case, so i don't see why such ability would eliminate the possibility in question. is there another reason for you to believe this to be the case?

Nodes and scripts are two separate things. You can see it in inheritance chains: Object -> Reference -> Resource -> Script Object -> Node So only by looking at this we can conclude that no node can ever be an instance of the Script class because Node class doesn't inherit from the Script class. Nor it can be the other way around. They are in separate branches of inheritance tree.

Script's new() behaves in a different way than in other classes. It will instantiate an object of a built-in class that Script's source code states it extends from. And it will set new object's script property to refer to it (the Script object that created it). Script.new() will not make a new instance of itself. If you're big on design patterns, Script in fact behaves like the factory pattern.

Let's for example take this source code stored in file script.gd

class_name CookieJar
extends Spatial
var cookie

Now we can do the following:

# create a Script object containing the above source code
var script_foo = load("res://script.gd") 
print(script_foo)

# create a Spatial node using Script.new()
# its script property will refer to script_foo object that created it
var spatial_foo = script_foo.new()

# now let's duplicate the Spatial node
# The duplicate will refer to the same script node as the original
var spatial_bar = spatial_foo.duplicate()

# It's exactly the same as creating a new Spatial without any script
# and then simply attaching a Script to it.
var spatial_baz = Spatial.new()
spatial_baz.script = script_foo

# let's print it out
print(spatial_foo, " ", spatial_foo.script)
print(spatial_bar, " ", spatial_bar.script)
print(spatial_baz, " ", spatial_baz.script)

# nodes have the script attached
# so this will work
spatial_foo.cookie
spatial_bar.cookie
spatial_baz.cookie

# now lets remove the script reference from nodes
spatial_foo.script = null
spatial_bar.script = null
spatial_baz.script = null

# and lets print it out
print(spatial_foo, " ", spatial_foo.script)
print(spatial_bar, " ", spatial_bar.script)
print(spatial_baz, " ", spatial_baz.script)

# nodes are fine but this won't work anymore
spatial_foo.cookie
spatial_bar.cookie
spatial_baz.cookie

Can you guess the output?

![GDScript:1953] ![Spatial:1954] [GDScript:1953] ![Spatial:1955] [GDScript:1953] ![Spatial:1956] [GDScript:1953] ![Spatial:1954] [Object:null] ![Spatial:1955] [Object:null] ![Spatial:1956] [Object:null] !Invalid get index 'cookie' (on base: 'Spatial')

!There's only one Script object [GDScript:1953] in existence the whole time

! > @xyz said: ! ! > Nodes and scripts are two separate things. You can see it in inheritance chains: ! > Object -> Reference -> Resource -> Script ! > Object -> Node ! > So only by looking at this we can conclude that no node can ever be an instance of the Script class because Node class doesn't inherit from the Script class. Nor it can be the other way around. They are in separate branches of inheritance tree. ! > ! > Script's new() behaves in a different way than in other classes. It will instantiate an object of a built-in class that Script's source code states it extends from. And it will set new object's script property to refer to it (the Script object that created it). ! > Script.new() will not make a new instance of itself. If you're big on design patterns, Script in fact behaves like the factory pattern. ! > ! > Let's for example take this source code stored in file script.gd ! > ~ ! > class_name CookieJar ! > extends Spatial ! > var cookie ! > ~ ! > Now we can do the following: ! > ~ ! > # create a Script object containing the above source code ! > var script_foo = load("res://script.gd") ! > print(script_foo) ! > ! > # create a Spatial node using Script.new() ! > # its script property will refer to script_foo object that created it ! > var spatial_foo = script_foo.new() ! > ! > # now let's duplicate the Spatial node ! > # The duplicate will refer to the same script node as the original ! > var spatial_bar = spatial_foo.duplicate() ! > ! > # It's exactly the same as creating a new Spatial without any script ! > # and then simply attaching a Script to it. ! > var spatial_baz = Spatial.new() ! > spatial_baz.script = script_foo ! > ! > # let's print it out ! > print(spatial_foo, " ", spatial_foo.script) ! > print(spatial_bar, " ", spatial_bar.script) ! > print(spatial_baz, " ", spatial_baz.script) ! > ! > # nodes have the script attached ! > # so this will work ! > spatial_foo.cookie ! > spatial_bar.cookie ! > spatial_baz.cookie ! > ! > # now lets remove the script reference from nodes ! > spatial_foo.script = null ! > spatial_bar.script = null ! > spatial_baz.script = null ! > ! > # and lets print it out ! > print(spatial_foo, " ", spatial_foo.script) ! > print(spatial_bar, " ", spatial_bar.script) ! > print(spatial_baz, " ", spatial_baz.script) ! > ! > # nodes are fine but this won't work anymore ! > spatial_foo.cookie ! > spatial_bar.cookie ! > spatial_baz.cookie ! > ~ ! > ! > Can you guess the output? ! > ! > >![GDScript:1953] ! > >![Spatial:1954] [GDScript:1953] ! > >![Spatial:1955] [GDScript:1953] ! > >![Spatial:1956] [GDScript:1953] ! > >![Spatial:1954] [Object:null] ! > >![Spatial:1955] [Object:null] ! > >![Spatial:1956] [Object:null] ! > >!Invalid get index 'cookie' (on base: 'Spatial') ! > ! > ! > >!There's only one Script object [GDScript:1953] in existence the whole time ! > ! > ! > ! > ! > !

it's beginning to make a bit of sense. i'm asking because in my project, i'm using inheritance from a room class to create a bunch of rooms. each room is a node, but each room also has it's own, new script. at first i created a new room by creating a node, adding a script, and using

specific_room = Room.new()

however, i discovered that seemed to be redundant. i could simply use

specific_room_node.property

to refer to the room's properties and methods, and each property held it's own value as if it was instanced from the Room class. exactly like how an object behaved. if we take your example, and instead of attaching the same script to each new node, we attach a new actual copy of that script that may or may not have different definitions... is there any difference between true object instantiation? i mean probably, but like, i'm curious what they might be. or if what i'm doing is actually appropriate usage.

Your rooms all have the same script. The Script object only contains the source code that defines properties and methods. It doesn't actually store these properties. Instead they are added to the object each time script is assigned to it.

Let's suppose this is room.gd:

class_name Room
extends Node
var chair = true

When you do this:

specific_room_1 = Room.new()
specific_room_2 = Room.new()

It's exactly same as doing this:

room_script = load("room.gd")
specific_room_1 = Node.new()
spacific_room_2 = Node.new()
specific_room_1.script = room_script
specific_room_2.script = room_script

Which is again same as doing this:

specific_room_1 = Room.new()
specific_room_2 = specific_room_1.duplicate()

In each case you end up with two instances of Node object, each referring to the same Script object.

When you attach the script to a node, which happens in all of the above cases, the properties defined in script's source code will be added to the node itself. Script just tells it what properties to add. It could be seen as sort of pseudo-inheritance. However, when you remove the script from the node, script's properties will be removed as well.

room_script = load("room.gd")

specific_room_1 = Node.new() # no script
print(specific_room_1.get_property_list()) # no chair property

specific_room_1.script = room_script # script attached
print(specific_room_1.get_property_list()) # contains chair property

specific_room_1.script = null # script removed
print(specific_room_1.get_property_list()) # chair property removed

So, again, Script object doesn't contain any data. It merely defines it. Actual data is created in the scripted object whenever the script is attached to it. You can attach the same script to millions of object and each of these object will make its own storage according to definitions found in the script source.

I think i failed to communicate what i did. I didn't simply create each node in code, i created them through the interface. in my case, the scripts are actually different. here, this is the file system and node tree:

scripts: node tree:

to verify, if i go to each room node's script and in the ready function execute this code:

var bunker_script 	= load("res://Locations/GreyAbbeyLibrary/Rooms/Bunker.gd")
var bunker_node 	= get_parent().get_node("Bunker") 
print("bunker node: ", bunker_node, "  bunker script:", bunker_script)

and for the attic:

var attic_script 	= load("res://Locations/GreyAbbeyLibrary/Rooms/Attic.gd")
var attic_node 	= get_parent().get_node("Attic") 
print("attic node: ", attic_node, "  attic script:", attic_script)

now when the nodes enter the tree and become ready, the output is this:

as you can see, each script on each room has a different script object, because i manually attached a new script via the editor. yet each room extends the same class via:

extends FSRoom

in this case, it appears as if each node is an object instance of FSRoom, because each room stores it's own instance of properties from the FSRoom class, like light level and room size, independently from other rooms. and all of this happens without the room scripts having to have a single line of code, yet if they did, they could have properties specific to that room that don't apply to other rooms.

so like, it makes me wonder, is there any difference between this implementation and creating actual instances of FSRoom objects. is this implementation.. uncommon?

In your printout you can see exactly which class is each object an instance of. Bunker and Attic are both instances of native Node class. They have their functionality extended via different custom script classes. The inheritance chains of these scripts doesn't affect the actual type of scripted object. Godot makes it appear to be a regular class instantiation system, but it actually isn't. If you look at it as such, you might miss to fully understand what's happening under the hood

class_name Room
extends Spatial
class_name Bunker
extends Room
class_name Attic
extends Room

All three above script classes will instantiate an object of the native Spatial class, but each of these Spatial instances will have its functionality extended by a different script class.

There are two actual inheritance systems at play in Godot. Built in classes (Node, Spatial etc), and custom script classes. Each object is seen as an instance of a built in class as well as a sort of an instance of a script class. During the runtime you can change object's script class but can't change its native class. So it's conceptually problematic to call that object an instance of a script class. Consider this:

var room = Bunker.new()
print(room is Bunker) # True
print(room is Spatial) # True
room.script = null
print(room is Bunker) # False
print(room is Spatial) # True
room.script = Attic
print(room is Attic) # True
print(room is Bunker) # False
print(room is Spatial) # True

So which class is the room instance of? The only sure and invariant thing is that it's an instance of Spatial class

oh i see! it behaves so similarly. i suppose that's intended design. It does make me think that creating an actual room object instance may be redundant here since i don't plan on switching the scripts attached to nodes.

to recap: while the node is an object instance of it's actual built in class, it can still store properties it gets from it's attached custom script because of how the built in class is coded. the custom script can be changed, which is a byproduct ability of the custom script extending the functionality of the base built in class.

this means.. while it may act like an instance of the script it's attached to, it isn't actually that simple. if it was, it wouldn't be able to do the things the built in node classes can do, and the system wouldn't be as modular and flexible.

the keyword extends makes a little more sense to me now.

@Wolfram said: to recap: while the node is an object instance of it's actual built in class, it can still store properties it gets from it's attached custom script because of how the built in class is coded. the custom script can be changed, which is a byproduct ability of the custom script extending the functionality of the base built in class.

Precisely!

the keyword extends makes a little more sense to me now.

"Extends" is quite a good choice of keyword in this case.

a year later