Unable to reference child node from an instantiated node

Godot Version

Godot 4.0

Question

I’m trying to change an object’s collision shape and sprite size via script and the object is an instantiated object. The object’s collision shape and sprite size is supposed to change when the player hits a power up but godot isn’t able to find the node that I’m referencing to. It hits me with this error: Invalid assignment of property or key ‘scale’ with value of type ‘Vector2’ on a base object of type ‘null instance’. This is the script for both power up and player:

player.gd:

extends CharacterBody2D
 
 class_name Player
 
 signal player_destroyed
 
 @onready var ray_cast_2d = $RayCast2D
 @onready var ray_pos = ray_cast_2d.position
 @export var move_speed = 200
 @onready var ShootingPoint = $PlayerSprite/ShootingPoint
 @onready var Power = preload("res://power_up.tscn")
 
 
 var bullet_speed = 2000
 var bullet_scene = preload("res://bullet.tscn")
 var coin = preload("res://coin.tscn")
 var bullet_amount = 1
 
 var dead = false
 var isShooting = false
 var targetPosition: Vector2
 var shootDirection: Vector2
 var BulletPosition: Vector2
 
 var BULLET = bullet_scene.instantiate()
 
 func get_input():
 	look_at(get_global_mouse_position())
 	
 	var dir = Vector2.ZERO
 	velocity = transform.x * dir * bullet_speed
 	if Input.is_action_just_pressed("shoot"):
 		shoot()
 
 func _process(delta):
 	global_rotation = global_position.direction_to(get_global_mouse_position()).angle() + PI/2.0
 	setBulletPosition(BULLET.global_position)
 
 func _physics_process(delta):
 	get_input()
 	var direction = Vector2.ZERO
 	var input = Input.get_axis("left", "right")
	
 	if dead:
 		return
 	
 	if Input.is_action_just_pressed("shoot"):
 		if bullet_amount == 1:
			shoot()
 	
 	if Input.is_action_just_pressed("throw"):
 		throw_coin()
 	
 	if input > 0:
		direction = Vector2.RIGHT
 	elif input < 0:
 		direction = Vector2.LEFT
 	else:
 		direction = Vector2.ZERO
 		
 	var delta_movement = move_speed * delta * direction.x
 	
	position.x += delta_movement
 	
 	move_and_slide()
 
 func kill():
 	move_speed = 0
 	if dead:
 		return
 	dead = true
 	$PlayerSprite.hide()
 	#$GameOverScreen/GameOver.show()
 	z_index = -1
 	queue_free()

 func restart():
 	get_tree().reload_current_scene()
 
 func setBulletPosition(pos: Vector2):
 	BulletPosition = pos
 
 func BulletSpread():
 	var spawnPosition = BulletPosition
 	
 	for n in 4:
 		var BULLET2 = bullet_scene.instantiate()
 		match n:
 			1:
 				targetPosition = Vector2.UP
 			2:
 				targetPosition = Vector2.DOWN
 			3:
 				targetPosition = Vector2.LEFT
 			4:
 				targetPosition = Vector2.RIGHT
 		shootDirection = (targetPosition - global_position).normalized()
 		BULLET.start(spawnPosition.global_position, rotation)
 		get_tree().root.add_child(BULLET)
 		BULLET.transform = spawnPosition.global_transform
 
 
 func shoot():
 	$Muzzle.show()
 	$Muzzle/Timer.start()
 	$ShootSound.play()
	
 if isShooting:
 		return
 	
 	isShooting = true
 	targetPosition = get_global_mouse_position()
 	shootDirection = (targetPosition - global_position).normalized()
 	
 	BULLET.start(ShootingPoint.global_position, rotation)
 	get_tree().root.add_child(BULLET)
 	BULLET.transform = ShootingPoint.global_transform
 	
 	await get_tree().create_timer(0.2).timeout
 	isShooting = false
 	bullet_amount -= 1
 
 func SetType(num):
 	match num:
 		1: 
 			BULLET.get_node("../BulletArea/CollisionShape2D/").scale = Vector2(100, 100) 
 			BULLET.get_node("Sprite2D").scale = Vector2(100, 100)
 		2:
 			BULLET.MOVE_SPEED += 5000
 
 func throw_coin():
 	targetPosition = get_global_mouse_position()
 	shootDirection = (targetPosition - global_position).normalized()
 	
 	var COIN = coin.instantiate()
	COIN.set_coin(global_position, targetPosition)
 	get_tree().get_root().call_deferred("add_child", COIN)
 	
 	await get_tree().create_timer(0.2).timeout
 
 
 func _on_player_area_area_entered(area: Area2D) -> void:
 	kill()
 	player_destroyed.emit()

power_up.gd:

 extends Area2D
 
 class_name PowerUp
 
 @onready var players = Player.new()
 
 func _on_body_entered(body: Node2D) -> void:
 	print("body entered")
 
 
 func _on_area_entered(area: Area2D) -> void:
 	if "PlayerArea" in area.name:
 		players.SetType(1)
 		print("got powerup")

This is the tree of the instantiated node:
image

Could you edit your post so that the code has indentations please.

So NOT using the quote button that looks like this:

func get_input():
look_at(get_global_mouse_position())
var dir = Vector2.ZERO

But using the code button </> so it looks like this:

func get_input():
      look_at(get_global_mouse_position())
      var dir = Vector2.ZERO


PS You cannot instantiate a scene outside of a function. I am amazed any of this works at all.

Oh whoops yeah okay i’ll edit it using the code button. I dont use the godot forum that much so i didnt know.

Also yeah it does just works

1 Like

You can instantiate a scene outside of a function.

1 Like

BULLET.get_node("../BulletArea/CollisionShape2D/").scale = Vector2(100, 100)

The “…” gets you to /root. This is above /Bullet.
Try
BULLET.get_node("BulletArea/CollisionShape2D/").scale = Vector2(100, 100)
(Or some version thereof. You can experiment string paths using print statements)
Personally, I can’t stand using string paths and would much rather code named nodes.
Give Bullet a script with an entry like:
@onready var bullet_area_cshape:CollisionShape2D = $BulletArea/CollisionShape2D
And then you can simply say:
BULLET.bullet_area_cshape.scale = ...

PS: You are going against convention to uppercase a variable. Usually people uppercase constants. I am not saying its wrong, just unconventional.
Here is the GDScript style guide.

@sancho2
I did a test of this and yes, you can. My apologies, I was wrong.

However I think this is mad that you can. At first I thought perhaps with @onready that makes sense (sort of), but it even works without it.

Here was my quick test:

extends Node2D

var EnemyScene = preload("res://components/actors/zhrak/zhrak.tscn")

var BULLET = EnemyScene.instantiate()

func _ready():
	BULLET.global_position = Vector2(100, 100)
	add_child(BULLET)

I still can’t quite believe it though!

@jaudanafzal61
Sorry for the wrong information.

PS I suppose scripts are classes. And classes can run code outside of functions, so I suppose this makes sense. It must surely be bad practice to do this though and to me at least seems to serve virtually no purpose. I am honestly still amazed at this though and thank you again @sancho2 for the correction. Much appreciated.

PS Deleted my post with the error.

1 Like

Try use groups instead:

# In your bullet script:
func _ready() -> void:
	add_to_group("bullet")
# In your player script:
func function_you_need_to_get_the_bullet -> void:
   var bullets_array = get_tree().get_nodes_in_group("bullet")

   for bullet in bullets_array:
   	# Do your stuff with bullet

Btw some things i saw in your code:

  1. In game you can only have a unique bullet at time inside screen? Otherwise your approarch to manage the bullets storing their referencies in a unique variable will fail.

  2. In your BulletSpread function you instantiate new bullets but never do nothing with them.

  3. NEVER scale objects that rely on physics, that can break their physics detection and lead to undefinied behavior, always change their size/extents.

I tried doing this, while it didnt generate any errors it did not change the size of the bullet’s sprite and collision area at all

to address to child node properties regardless of what an when initialized, you can use parent’s node signal callback
self.child_entered_tree.connect(func(child): if &'scale' in child: child.scale=Vector(2, 2)

I don’t understand how using groups can make it so that i can access the bullet’s collision shape and sprite from the player script.

The bulletspread function isnt used and i forgot to delete it lmao but i have deleted it now.

Soooo like im not supposed to instance a bullet if i just want one unique bullet in the game the whole time?

I tried this

func SetType(num):
	match num:
		1: 
			#BULLET.get_node("BulletArea/CollisionShape2D/").scale = Vector2(100, 100) 
			#BULLET.get_node("Sprite2D").scale = Vector2(100, 100)
			self.child_entered_tree.connect(func(child): if &'scale' in child: child.scale=Vector2(100, 100))
		2:
			BULLET.MOVE_SPEED += 5000

It didnt generate any errors but it also didnt change the size of the bullet’s collision shape and sprite

try to replace ‘self’ to ‘BULLET’

BULLET.child_entered_tree.connect(func(child):
	if child.name=='BulletArea':
		child.get_node(^'CollisionShape2D').scale=Vector(100,100)
	elif child.name=='Sprite2D':
		child.scale=Vector2(100, 100) ,4)

it should work for sprite

child_entered_tree is supposed to be one time callable, that why flag “,4” is present

From the error you put when you wrote this (the “Invalid assignment of property or key ‘scale’ with value of type ‘Vector2’ on a base object of type ‘null instance”) that means you’re getting a null value when you try to access the nodes, using groups would make sure u’ll get the node or do nothing if no valid node is there, also, you can put a script in the bullet to make the changes and use groups to call the function needed.

I didn’t said that, you only store one bullet reference, if you have two bullet at the same time, you’ll be able to manipulate only the last bullet instantiated, the other one you’ll lost the reference for her when instantiate the new bullet because you’ll replace the reference for the first bullet for the second one, as i said, if you only have one bullet in the scene that will work, but if you can put more than that at the same time u’ll be unable to manipate the other bullets (unless you store them on an array or use groups).

I did exactly this and it doesnt seem to trigger at all for me, no errors tho

I used groups like you said now it works

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.