How to access node tree from inherited inner class

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By path9263

I am trying to make a class which inherits from another class. The below example works except that I can not access anything in the node tree from the inherited class. Please note that subClass is an inner class but also does not seem to work with a normal class (gd script file) be it inherited or not. I can access functions and variables in the class which I inherited from but if I try to call something like “print (get_path())” it just prints nothing and throws an error:

0:00:02:0489 - Condition ' !is_inside_tree() ' is true. returned: NodePath()
----------
Type:Error
Description: 
Time: 0:00:02:0489
C Error: Condition ' !is_inside_tree() ' is true. returned: NodePath()
C Source: scene\main\node.cpp:1780
C Function: Node::get_path

Here is the below code and project file in a downloadable zip.
mainClassScript.gd attached to the root node, a Spatial Node called main:

extends Spatial

var testVar = "this is a test var"

var containsObject

func _ready():
	printPath() # prints: "/root/main"
	containsObject = $sub.subClass.new()

func testFunc():
	testVar = "test var changed!"

func printPath():
	print (get_path())
	
func _unhandled_input(event):
	containsObject.handleInput(event)

subClassScript.gd attached to a Spatial Node called sub which is under “main” in the node tree:

extends Node

class subClass:
	extends "mainClassScript.gd"
	
	func _init():  
		pass
		
	func handleInput(event):
		
		if event.is_action_pressed('ui_accept'): # press SPACEBAR 
			print(testVar)  # properly prints out: "this is a test var"
			testFunc() # modifies testVar to "test var changed!"
			print(testVar) # properly prints out: "test var changed!"
			printPath() # prints nothing and produces error: Condition ' !is_inside_tree() ' is true. returned: NodePath()

Any help you can offer would be greatly appreciated! Thank you!

:bust_in_silhouette: Reply From: Footurist

Docs for get_path():

Returns the absolute path of the current node. This only works if the current node is inside the scene tree (see is_inside_tree()).

You can avoid this by doing this:

extends Node2D

func _ready():
    _print_path()

func _print_path():
    if is_inside_tree():
        print(get_path)

The reason the first approach won’t work is the order in which things are getting called, I suspect. When you try to call get_path() on your subnode, _ready() was called for itself, but not yet for all of the hirarchy above it.

Footurist, thank you for your reply! Please correct me if I am wrong but I do not think that this is really the issue. Please also note that the “subClass” is an inner class.

Here is why I think this: I don’t actually create the new object withcontainsObject = $sub.subClass.new() until the _ready() function of the main class, after the main class has properly printed a get_path(). I’ve also tried not creating the new object until much later (with a button press), this also did not work. I think for some reason the objects being created, though an extension of “mainClassScript.gd” just never have a node assigned to them and never will return true for is_inside_tree()

I should mention though that if I put a func _ready(): in my inner class (subClass) like you suggested (I think) it never gets called.

I think the problem is more to do with creating a sub class from a main class and then trying to call the sub class from the main class. Something just isn’t working right with that.

Thanks!

path9263 | 2018-02-28 16:27

:bust_in_silhouette: Reply From: hilfazer

mainClassScript.gd

extends Spatial

var testVar = "this is a test var"

var containsObject

func _ready():
	readyImpl()
	
func readyImpl():
	printPath() # prints: "/root/main"
	containsObject = $sub.subClass.new()
	call_deferred("add_child", containsObject)

func testFunc():
	testVar = "test var changed!"

func printPath():
	print (get_path())
	
func _unhandled_input(event):
	handleInput( event )

func handleInput(event):
	pass

subClassScript.gd

extends Node


class subClass:
	extends "mainClassScript.gd"
	
	func _init():  
		pass
		
	func readyImpl(): # override, readyImpl() from mainScript.gd won't be called
		pass # do nothing
		
	func handleInput(event): 
		if event.is_action_pressed('ui_accept'):
			print(testVar)
			testFunc()
			print(testVar)
			printPath()

You created subClass object but didn’t add it to SceneTree.
I’ve added readyImpl() function that is overriden by derived class. Otherwise subClass object would try to access “sub” child it does not have.

hilfazer thank you! We are getting close now!

Your suggestion works quite well except you have changed containsObject.handleInput(event) to just handleInput(event). I see why you have done this but it makes every instance of the class and subClasses get called every time. I would really like to be able to call a function that just exists in the subClass, is this possible? Say I want to have two subClasses that both have different versions of the same function, how would I call one or the other? Currently both will be called!

Something like:

class subClass:
	extends "mainClassScript.gd"
	
	func _init():  
		pass
	
	func readyImpl(): # override, readyImpl() from mainScript.gd won't be called
		pass # do nothing
	
	func handleInput(event):
		if event.is_action_pressed('ui_accept'): # press SPACEBAR 
			print("THIS IS CLASS: A") 

class subClassB:
	extends "mainClassScript.gd"
	
	func _init():  
		pass
	
	func readyImpl(): # override, readyImpl() from mainScript.gd won't be called
		pass # do nothing
	
	func handleInput(event):
		if event.is_action_pressed('ui_accept'): # press SPACEBAR 
			print("THIS IS CLASS: B")  

Thanks again!

path9263 | 2018-02-28 21:49

To remove handleInput() from base class script, You can implement _unhandled_input() like this:

func _unhandled_input(event):
	if containsObject != null:
		containsObject.handleInput( event )

I don’t quite understand wha you mean by 2 subClasses. Do you mean adding 2 children to mainClassScript object, one of type subClass, other of type subClassB?

Edit:
You can try disabling input processing for subClass objects by adding this line to their readyImpl()

set_process_unhandled_input(false)

Then _unhandled_input() will be called only in object of base class (“main” node).

hilfazer | 2018-02-28 22:21

I don’t quite understand wha you mean by 2 subClasses. Do you mean adding 2 children to mainClassScript object, one of type subClass, other of type subClassB?

Yes this is what I was trying to do. In the end I have given up on trying to extend mainClassScript.gd and just wrote a new class that takes the mainClass as an argument and then calls functions on it that way. IE:

class subClass:
	extends Node
	var mainClass
	
	func _init(edt):  # this is the constructor
		mainClass = edt
	
	func doSomething():
		mainClass.testFunc()
		mainClass.testVar = "changed again!" 

path9263 | 2018-03-04 19:28