Get_tree() returns null, but object is in the scene tree

Godot Version

4.3 stable Windows 11

Question

I have a system for pooling bullet objects, since I’m making a bullet hell game. In my bullet script I have this code:

func activate():
	$Sprite.frame = colour
	add_to_group("bullet")
	remove_from_group("bulletpool")
	if not lifespan == null:
		await(get_tree().create_timer(lifespan).timeout)
		deactivate()

func deactivate():
	remove_from_group("bullet")
	add_to_group("bulletpool")
	BulletManager.bullet_pool[type].append(self)
	BulletManager.active_bullets[type].erase(self)
	global_position = Vector2(29999, 29999)
	speed = 0
	accel = 0
	angle = 0
	angular_velocity = 0
	print("bullet deactivated")
	set_process_mode(PROCESS_MODE_DISABLED)

This code is meant to deactivate the bullet and send it back to the pool once its lifespan runs out. I also have a node with a script that manages several things in my scene tree, and in its _ready() function is this:

	for i in 500 :
		for type in BulletManager.bullet_pool :
			var objShot = load(BulletManager.BULLET_DIRECTORY + type + ".tscn").instantiate()
			BulletManager.bullet_pool[type].append(objShot)
			get_tree().get_root().add_child(objShot)

But whenever I shoot a bullet, I get the error Cannot call method 'create_timer' on a null value. I figure this means get_tree() is returning null in the bullet script, which should only happen if the bullet nodes are not in the scene tree, but by all accounts they should be. I added the bullets as children of the tree’s root at the beginning, before any bullet is fired.

What’s wrong here? Any help is appreciated.

1 Like

First step is set a breakpoint on the line await(get_tree().create_timer(lifespan).timeout) (or just before on a pass line).

When the debugger breaks check in the Scene tree panel and click on the Remote tab (left of the Local). Then expand the tree accordingly to see what’s going on with the live system.

I put a pass line before the problem line.

It seems there are no bullets in the scene tree at all… What could be the reason?

Hard to tell but I would say the if line above needs to be rehabed. Lifespan is an float but you are treating it as an object. Maybe use it this way?

if lifespan == 0.0:

I tried that, and it just makes the bullets not show up - but at least it doesn’t crash. If I add a not to that, I get the same error.

Hi! I still don’t know exactly why this happens, but basically, the node needs to be “ready” (inside the SceneTree) before you can create the timer inside the tree. I solved your problem by adding “await(self.ready)” on the start of my code, my script example became like this:

The node that will call the bullets

func _ready():
	for i in 5 :
		var objShot = load("res://slimepudding - Create Timer Problem/bala.tscn").instantiate()
		pool.append(objShot)
		call_deferred("add_child", objShot)
	
	pool[0].active()

The Bullet itself

extends Node2D

var lifespan := 2.0

func active():
	await(self.ready)
	await(get_tree().create_timer(lifespan).timeout)
	print("working!")

I suppose it will work on yours if it is like this

func activate():
...
	if not lifespan == null:
	    await(self.ready)
		await(get_tree().create_timer(lifespan).timeout)
	    deactivate()

Please share if it worked or not!

Edit: Notice that when you run the project and get the error, if you check the Remote Scene Tree you’ll see that no bullet was instantiated

I tried that, and now at least the bullets are added to the scene tree. Thank you! However, now the bullets just aren’t despawning with the lifespan timer. It’s as if the lifespan variable is being counted as null. This is my bullet spawning function:

func CreateShotA1(bullet_type : String, colour : int, pos : Vector2, speed : float, accel : float, target_speed : float, angle : float, angular_velocity : float, lifespan : float, dmg : float):
	
	var objShot = get_available_bullet(bullet_type)

	if not objShot == null :
		objShot.type = bullet_type
		objShot.colour = colour
		objShot.global_position = pos
		objShot.speed = speed
		objShot.accel = accel
		objShot.target_speed = target_speed
		objShot.angle = angle
		objShot.angular_velocity = angular_velocity
		objShot.lifespan = lifespan
		objShot.damage = dmg
	
		objShot.set_process_mode(PROCESS_MODE_INHERIT)
		objShot.activate()
		BulletManager.active_bullets[bullet_type].append(objShot)
		BulletManager.bullet_pool[bullet_type].erase(objShot)
		return objShot

And this is my attempt to call that function:

for i in 6 :
			CreateShotA1("ball_M", 2, global_position, 60, 0, 60, (2*PI*i)/6, 0, 3, 5)

Lifespan should be 3 when this code is run. Printing lifespan even results in 3 when I put it in the bullet’s activate function. Furthermore, whenever I try running deactivate(), I get the error Invalid access to property or key of type 'String' on a base object of type 'Dictionary'. This is what my bullet manager’s dictionaries look like:

var bullet_pool : Dictionary = {
	"ball_M": [],
	"knife": []
}

var active_bullets : Dictionary = {
	"ball_M": [],
	"knife": []
}
1 Like

hmmm, so, the problem is probably in calling the “activate()” function while the node has not been fully loaded (because get_tree() will always return the tree in which the node is inserted, and will return an error if the node is not inside the tree), so much so that if you call the same function in _process(): it will work normally.

I did this changes in my test project and its still working properly

func _ready():
	for i in 5:
		var objShot = load("res://....tscn").instantiate()
		pool.append(objShot)
		get_tree().get_root().call_deferred("add_child", objShot)
	    await(pool[i].ready) 
        # or: await pool[i].is_inside_tree()
		pool[i].active()
func active():
	await(get_tree().create_timer(lifespan).timeout)
	print("working?")
	deactivate()

func deactivate():
	print("help_test")
	queue_free()
	if self.is_queued_for_deletion() == true:
		print("im queued")

My assumption is that you should put the “await bullet.ready” right after you create the nodes (has i did in my for loop), or add a condition in CreateShotA1. I suppose this should, work:

func CreateShotA1(...):
	var objShot = get_available_bullet(bullet_type)

	if not objShot == null && objShot.is_inside_tree() == true:
...

Here’s some stuff that i found about it, maybe it can help you: https://www.reddit.com/r/godot/comments/18ttaau/i_keep_getting_an_error_that_data_tree_is_null/
Get_tree().reload_current_scene() crashes game
Using SceneTree — Godot Engine (stable) documentation in English
Using SceneTree — Godot Engine (stable) documentation in English

(It’s a common problem in godot 4 apparently), if nothing ended up working, use a instantiated Timer instead, atleast until you figure out what is the solving key.

About the dictonary problem, i’ll see it now!

By the way, I’m not running the activate() function when the bullets are instantiated at the beginning; I’m running it when they’re fired through the function CreateShotA1. My intention is to deactivate the bullets and put them in the pool when they’re first instantiated, so they can be activated when fired, and then put them back in the pool after their lifespan timer is don.

About the Dictionary, first i tested this to get the bullet_pool in a variable inside the bullet script

@onready var test= get_tree().get_first_node_in_group(“test_group”)

and then tried puting a class_name on the bullet maneger node to use it remotely

bulletManeger.new().bullet_pool[“ball_M”].erase(self)

and then i used it as an autoload/singleton node

BulletManeger.bullet_pool[“ball_M”].erase(self)

all of them worked properly, so i could not understand what is the problem.

Here’s how i did:

extends Node2D

var pool = []

var bullet_pool : Dictionary = {
	"ball_M": [1],
	"knife": [],
}
func _ready():
	for i in 5:
		var objShot = load("res://....tscn").instantiate()
		pool.append(objShot)
		get_tree().get_root().call_deferred("add_child", objShot)
		bullet_pool["ball_M"].append(pool[i])
		pool[i].active()
func active():
	await self.ready
	await(get_tree().create_timer(lifespan).timeout)
	deactivate()

func deactivate():
	BulletManager.bullet_pool["ball_M"].erase(self)
	print(BulletManager.bullet_pool["ball_M"])
	queue_free()

sorry for that :confused: , i found that other person that got the same issue, if it helps