Need Help With Firing Bullets In Twin-Stick / Top-Down Shooter

Godot Version

Godot 4.2
C#

Question

I’ve been trying to make a top-down shooter, and I’ve been running into an issue while trying to get the bullets from the player character to fire in the right direction. Essentially, I am having the player character look at the mouse, and attempt to fire a bullet towards the mouse from a Marker2D node, named BulletSpawn, that is always in front of the player.

The issue is that when I attempt to fire a bullet, it does not go towards the mouse, but rather in a perpendicular direction if I make the player face left or right, and away from the mouse if I make the player face down. If I make the player face directly up, then it does go towards the mouse, but any deviation from that results in the bullets no longer going directly towards the mouse. Needless to say, this has me pretty stumped, so please take a look at the code I have provided.

The method I’m currently using to fire bullets:

private void FireWeapon(){
	if(Input.IsActionPressed("shoot_weapon") && canFireTimer >= Guns[CurrentWeapon].FireRate){
		Bullet bullet = (Bullet)CurrentBullet.Instantiate();
		AddChild(bullet);
		bullet.Position = GetNode<Marker2D>("BulletSpawn").Position;
		bullet.direction = (GetGlobalMousePosition() - GetNode<Marker2D>("BulletSpawn").Position).Normalized();
		canFireTimer = 0;
	}
}

The code for Bullet:

using Godot;

public partial class Bullet : Area2D {
	[Export] public int Speed { get; set; } = 800;
	public Vector2 direction = Vector2.Zero;
	
	public override void _Process(double delta){
		Position += Speed * direction * (float)delta;
	}
}

In the _PhysicsProcess for the code for the player, I have these two lines that are almost certainly relevant as well:

LookAt(GetGlobalMousePosition());
RotationDegrees += 90;

In case you are wondering, for the RotationDegrees I found out after creating the player sprite, and all the animations for it, that it wasn’t aligned properly in the Godot editor (I should have made it facing right, not up). Regardless, with and without that line, the issue I described above still persists, just with the bullets going in different directions instead.

I appreciate any help.

I think your problem is a result of a hierarchy issue.

When you’re adding the bullet to the scene, you make it a child of the node that’s calling FireWeapon(). This a problem because you’re using the bullet’s local position (Position) to integrate it’s motion. Meanwhile, the direction you’re passing to your bullet is in global space. Mixing local vectors with global vectors nearly always result in problems, either immediately or eventually.

There’s a few ways to solve your problem.

Option 1: Top-level

If you intend on keeping the bullets as children of the player node, setting a bullet to be TopLevel is a solution. TopLevel eliminates any transformation inherited from its position in the node tree.

Option 2: Use a different parent

As mentioned, your issue is caused by the parent of your bullets. If you change the parent to a node that doesn’t move (such as the parent of your player), you should be good.

GetParent().AddChild(bullet);

Suggestion

I would maybe suggest that you remove the need for a direction-variable. 2D and 3D nodes already have the necessary data needed to translate and orient them. Why not rotate your bullet node based on the direction you want it to travel in?


NOTE: I’m not too sure of what your scene tree looks like or where this FireWeapon() method resides. I have, therefore, had to make some assumptions of your setup. If my assumptions are wrong, and this does not solve your problem, please describe your project in more detail. Context is important.

1 Like

Thank you very much. I used GetParent().AddChild(bullet) in combination with changing Position to GlobalPosition, and it worked.

New code:

private void FireWeapon(){
	if(Input.IsActionPressed("shoot_weapon") && canFireTimer >= Guns[CurrentWeapon].FireRate){
		Bullet bullet = (Bullet)CurrentBullet.Instantiate();
		GetParent().AddChild(bullet);
		bullet.GlobalPosition = GetNode<Marker2D>("BulletSpawn").GlobalPosition;
		bullet.direction = (GetGlobalMousePosition() - GetNode<Marker2D>("BulletSpawn").GlobalPosition).Normalized();
		canFireTimer = 0;
	}
}