Edit: @athousandships above is correct of course. I just wanted to add some additional comments.
I presume you have simplified your question for the forum post (which is always a good idea) but here it looks like your shoot function is also doing your movement. This is not a good idea.
func shoot():
# here you just need to instantiate your bullet and let it run
Your bullet (or ball or whatever) should have it’s own process function in which you do the movement. Then in that process function you can check to see if it is now off screen, and if it is, remove it. Something like this:
if not $VisibleOnScreenNotifier2D.is_on_screen():
call_deferred("queue_free")
When using ‘while’ statements, you have to be very careful that it will not end up in an infinite loop, otherwise, as you have seen, the process will never end and the program will crash. This is a very common problem and why I would mostly avoid them (of course they have their uses but they can be dangerous if not implemented carefully).
At the very least you must always make sure the loop condition is updated within the loop body so it eventually becomes false to stop the loop.
Well, basically the code is called once and continues until it leaves the scene, so I prefer not to create a new function, and as long as each bullet is fired once, it makes sense and is fine.
But yes, if it is called a few times, it must be transmitted