Child node is null, seems to be called before it is made

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

Thank you in advance.
I am trying to implement code into my game for a grappling hook. The code works on its own in the test game provided but I cant seem to get it to work in mine.

The problem is that I reference a variable of a child node in the parent’s script but it is null.
Full error is: "Invalid set index ‘global_position’ (on base: ‘null instance’) with value of type ‘Vector2’.

When it throws the error I notice that the game has NOT loaded the Player, GrappleHook, nor Tip assets into the game

I printed the child node ($Tip) and its null in mine but not in the test code.

I reference the child node like so onready var hookTip = $Tip
The code that throws the error is this func _physics_process(_delta: float) -> void: hookTip.global_position = tip
.
.
.
In case it matters, heres the node structure of the scene

  • Node2d: World
  • …KinematicBody2d: Player
  •   ......Node2d: GrappleHook
    
  •      .........KinematicBody2d: Tip
    

I have tried looking it up and finding why its happening, but no dice. Im already referencing it with onready so shouldnt the child nodes be loading before it tries to call it in the physics_process?

Another clue I found is the error in the command window that launches with Godot:
‘Error: (Node not found: “Tip” (relative to “/root/World/Player”).)’

I believe that the path should go down one further into “/root/World/Player/GrappleHook”. I thought that I can put scenes into scenes without pathing problems, that relative pathing would sort it out but did I do something wrong in this?

shipx7 | 2022-09-24 16:21

Could you show us the original code?

Ertain | 2022-09-24 16:38

This is the original code from the tutorial that works, the only changes that I made to it when I put it into my code is an onready reference to Tip

extends Node2D

onready var links = $Links		# A slightly easier reference to the links
var direction := Vector2(0,0)	# The direction in which the chain was shot
var tip := Vector2(0,0)			# The global position the tip should be in
								# We use an extra var for this, because the chain is 
								# connected to the player and thus all .position
								# properties would get messed with when the player
								# moves.

const SPEED = 50	# The speed with which the chain moves

var flying = false	# Whether the chain is moving through the air
var hooked = false	# Whether the chain has connected to a wall

# shoot() shoots the chain in a given direction
func shoot(dir: Vector2) -> void:
	direction = dir.normalized()	# Normalize the direction and save it
	flying = true					# Keep track of our current scan
	tip = self.global_position		# reset the tip position to the player's position

# release() the chain
func release() -> void:
	flying = false	# Not flying anymore	
	hooked = false	# Not attached anymore

# Every graphics frame we update the visuals
func _process(_delta: float) -> void:
	self.visible = flying or hooked	# Only visible if flying or attached to something
	if not self.visible:
		return	# Not visible -> nothing to draw
	var tip_loc = to_local(tip)	# Easier to work in local coordinates
	# We rotate the links (= chain) and the tip to fit on the line between self.position (= origin = player.position) and the tip
	links.rotation = self.position.angle_to_point(tip_loc) - deg2rad(90)
	$Tip.rotation = self.position.angle_to_point(tip_loc) - deg2rad(90)
	links.position = tip_loc						# The links are moved to start at the tip
	links.region_rect.size.y = tip_loc.length()		# and get extended for the distance between (0,0) and the tip

# Every physics frame we update the tip position
func _physics_process(_delta: float) -> void:
	print($Tip)
	$Tip.global_position = tip	# The player might have moved and thus updated the position of the tip -> reset it
	if flying:
		# `if move_and_collide()` always moves, but returns true if we did collide
		if $Tip.move_and_collide(direction * SPEED):
			hooked = true	# Got something!
			flying = false	# Not flying anymore
	tip = $Tip.global_position	# set `tip` as starting position for next frame

shipx7 | 2022-09-24 17:43

If I undestand your diagrams and text correctly, Tip is a grand grand grand child of the World2D.
You never specified which Node uses the code above ? Is it the World or is it GrappleHook?

Inces | 2022-09-24 19:10

Sorry, GrappleHook is where this script is located and where the error is occurring. I made the grapplehook scene and everything and put it into my Player which is in turn inside the World node. So World > Player > GrappleHook (where the error happens) > Tip (which is null).

shipx7 | 2022-09-24 20:09

:bust_in_silhouette: Reply From: Inces

It looks fine.
The problem must lie somewhere else. Are You sure You don’t have another grappling hook somewhere in the scene ? Like from old test purposes ? And it is orphaned or somewhere else in scene hierarchy ? You can print self if unsure

Is there any condition queuing free your grappling hook or Tip ?

Unfortunately no, theres no queue_free for grappling hook and its the only grappling hook in the scene. It just seems to be trying to get the GrappleHook before its made, since when I run the game I get the error before my player or grapplehook show; they’re null.
Its got me puzzled so much because its such simple code but I cant find the error.

shipx7 | 2022-09-24 20:43

AAAAAAAAAAAGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHH!!!
The player script for some reason was swapped over to the GrappleHook script.
I must have accidentally dragged the GrappleHook.gd over first instead of GrappleHook.tscn and it overwrote the Player script without realizing it. I wasted so much time over something so stupid.
Thank you for your help, I was losing my mind.

shipx7 | 2022-09-24 21:14