Projectiles not firing straight from an angle

Godot Version

4.6.1

Question

I’ve got a system in my game that involves a projectile that fires outward from a pivot point that can be rotated left and right. The projectile goes until it’s gone off screen or hit something then returns back to the spawn point. There are upgrades that can increase the number of projectiles fired at once. I’m having trouble getting the projectile to fire straight from the angle that it was at. It often times moves off and to the right or left depending on what code I’m trying. There’s a good bit of code here so I don’t want to oversaturate the post with code. I will say that my projectile’s position is (0,0)’d out and faces the right. There is a hitbox for it that only covers the tip. This is some of the projectile’s code:
func set_dir(dir:Vector2):
directionn = dir.normalized()

func _ready():
base_position = global_position
speed *= PlayerUpgrade.get_spear_speed_mult()func set_dir(dir:Vector2):
directionn = dir.normalized()

func _ready():
base_position = global_position
speed *= PlayerUpgrade.get_spear_speed_mult()

func _process(delta):
if state == “outward”:
global_position += directionn * speed * delta
elif state == “stuck”:
timer -= delta
if timer <= 0:
state = “returning”
elif state == “returning”:
var direction = (base_position - global_position).normalized()
position += direction * return_speed * delta
if global_position.distance_to(base_position) < 10:
if hooked_fish:
var fish_item = send_to_convert(hooked_fish)
send_to_inv(fish_item)
projectile_finished.emit()
queue_free()

Hopefully my formatting worked well.
here’s some of the shooter’s script:
func spawn_spread(count:int, spread:float):
active_projectiles = count
for i in range(count):
var projectile = projectile_scene.instantiate()
var t = 0.0
if count > 1:
t = float(i) / float(count - 1)
var angle_offset = lerp(-spread, spread, t)
var rot = spawn_point.global_rotation + angle_offset
projectile.global_position = spawn_point.global_position
projectile.set_dir(Vector2.RIGHT.rotated(rot))
projectile.projectile_finished.connect(_on_projectile_finished)
get_tree().current_scene.add_child(projectile)

func shoot():
var spear_count = PlayerUpgrade.get_multi_spear_count()
spear_sprite.visible = false
projectile_active = true
spawn_spread(spear_count, deg_to_rad(20))

I’ll be straightforward and say that I’m not extremely experienced. Some of this code I wrote, some I tried following other people’s code, and some I borrowed from an LLM. I’ve been on this code for too long I can’t see it right anymore and I think I’ve let it get muddled. Any help would be greatly appreciated. If you need more information from me, I am happy to supply.

It didn’t unfortunately. You should put your code between tripple backticks ``` on both sides.

```gdscript
<your code here>
```

Look at your positioning code to determine if you are mixing local and global coordinates.

Yeah I’m returning to this just to say I did end up getting it working a little while ago. I’d like to post what I did so maybe other people can learn from what I have done. The only code that needed changing was the ‘shooter’ script since it’s the one that handles the firing. Something that ended up helping was getting a better understanding of the difference between global and local positions, as soapspangledgames pointed out. I won’t claim that everything I did here was perfect, but it got the job done.

A few things, a direction was not helpful or necessary so I cut that out and just set the global rotation to the variable rot. I added a pivot_init_rot variable to keep track of where the returning firing position should be. I also separated firing one projectile from firing multiple. This may not be the most efficient option, but it kept it far simpler for my understanding and referral to later.

func spawn_spread(count:int, spread:float):
	active_projectiles = count
	if count == 1:
		var projectile = projectile_scene.instantiate()
		projectile.global_position = spawn_point.global_position
		projectile.global_rotation = spawn_point.global_rotation
		projectile.projectile_finished.connect(_on_projectile_finished)
		projectile.add_to_group("projectiles")
		get_tree().current_scene.add_child(projectile)
		return
	elif count > 1:
		for i in range(count):
			#for how many count you have, make a projectile
			var projectile = projectile_scene.instantiate()
			projectile.name = ("projectile " + str(i))
			var t = 0.0  
			if count > 1:
				t = (float(i) / float(count - 1))
			var angle_offset = lerp(-spread, spread, t)
			var rot = spawn_point.global_rotation + angle_offset
			projectile.global_rotation = rot
			#projectile.z_index -= 1 #this did not fix the ordering but its of low consequence right now
			projectile.global_position = spawn_point.global_position
			#print("pos: ", projectile.position, "rot: ", projectile.rotation)
			projectile.projectile_finished.connect(_on_projectile_finished)
			projectile.add_to_group("projectiles")
			get_tree().current_scene.add_child(projectile)
	else:
		push_error("How many projectiles do you have?",count)

func shoot():
	var spear_count = PlayerUpgrade.get_multi_spear_count()
	spear_sprite.visible = false 
	projectile_active = true
	pivot_init_rot = pivot.rotation
	catch_streak_timer.can_increment = true
	spawn_spread(spear_count, deg_to_rad(spread_deg))
 
func _on_projectile_finished():
	active_projectiles -= 1
	if active_projectiles <= 0:
		pivot.rotation = pivot_init_rot
		spear_sprite.visible = true
		projectile_active = false


PSA: WOW I just figured out the reason my code wouldn’t format. I kept ‘shift+enter’-ing for a new line cause I was afraid it would just post my text otherwise. I also want to add that while I did try to send this prompt to an LLM at one point, AI tools are just not well-equipped to handle game development. Relying on them robs new devs from learning and true understanding of concepts.