Shooting 2 bullets instead of 1. HELP!

I am making a topdownshooter and whenever i click twice my gun shoots twice im trying to fix it so it only shoots one when the revolver is clicked after its animation and i am not trying to fix how the bullet shoots not where the barrel is ill fix that myself. HELP!

Github: GitHub - kaihan-man/TopDownShooter: This is a topdownshooter

1 Like

I’ll give you a few scripts just because I’m bored. You don’t have to use them by any means but at least try the new gun script because it likely solves your issue.

This is your updated gun script, I hope it works:

extends Node2D

#Players animated sprite refrence
@onready var animated_sprite := $AnimatedSprite2D
#Cooldown timer/tracker
@onready var cooldown_tracker:SceneTreeTimer
#Cooldown in seconds
@export var cooldown:float = 1
#Preloaded revolver bullet from files
const REVOLVER_BULLET = preload("res://Scenes/revolver_bullet.tscn")


func _ready() -> void:
	#Make fire_cooldown not equal to null
	cooldown_tracker = get_tree().create_timer(0)


func _process(delta: float) -> void:
	#Rotate The Gun To Face The Same Direction As The Player
	look_at(get_global_mouse_position())
	#Limit Rotation
	rotation_degrees = wrap(rotation_degrees, 0, 360)
	if rotation_degrees > 90 and rotation_degrees < 270:
		scale.y = -2
	else:
		scale.y = 2
	
	#Fire When LMB
	if Input.is_action_just_pressed("LMB"):
		#When The Coodown Is Done Fire
		if not cooldown_tracker.time_left > 0:
			shoot()
			
func shoot():
		#Play the shoot animation
		plr_animated_sprite.play("Shoot")
		#Spawn bullet and mirror players rotation and position
		cooldown_tracker = get_tree().create_timer(cooldown)
		var bullet_instance = REVOLVER_BULLET.instantiate()
		get_tree().root.add_child(bullet_instance)
		bullet_instance.global_position = global_position
		bullet_instance.rotation = rotation

This is the new player script, it should be more performant and smoother, but you might be extremely slow are superfast so make sure you adjust speed, accel, and deccel!

extends CharacterBody2D

#Export variable are editable through the inspector
@export var speed:float = 350
#Acceleration is how fast the character speeds up
@export var accel:float = 5000
#Decceleration is how fast the character slows down
@export var deccel:float = 6000
#Keeps track of the direciton you player wants to move
@onready var dir:Vector2

func _physics_process(delta:float) -> void:
	var moving = true if dir.dot(get_real_velocity()) > 0 else false
	dir = Input.get_vector("Left", "Right", "Up", "Down") 
	velocity = velocity.move_toward(dir * speed, (accel if moving else deccel) * delta)

Lastly the bullet script, I basically didn’t change anything besides removing obsolete ready function:

extends Node2D

#Export varaibles are editable in the inspector
@export var bullet_speed:float = 500

#Pushes the bullet on the postive X axis AKA forwards. This is then multiplyed by speed to choose how fast, and delta to make it the same speed for all pc's
func _process(delta:float) -> void:
	position += transform.x * bullet_speed * delta

Can you explain what each part does because im really early in game dev and this game is really only meant for learning purposes. If u cant or somthing, its fine. Thank you.

1 Like

Keep in mind some of the things I’m saying are grossly over simplified…

extends Node2D

This means the script extends, or is based on Node2D. Meaning, all of the features added in Node2D apply to your script.

@onready var animated_sprite := $AnimatedSprite2D

This makes an @onready variable of guns animated sprite. You can now use this variable to play animations and other stuff. Normally when the script runs, everything WONT already be loaded. So, if you make a variable referencing to something that isn’t loaded, and use said variable in some code, it will somtimes error, or even crash. @onready just makes it so the variable can’t be used until it’s loaded.
Edit: The := symbol combination means the script will guess which type of variable to use, which is ok in this scenario, it’s 10:19 rn so I don’t feel like explaining the why tho…

@onready var cooldown_tracker:SceneTreeTimer

This is an empty variable that we use to create timers, in this case a cooldown for shooting your gun. Adding a colon “:” after a variable name allows you to lock it to a specifc type of variable like int, which is a number. Or string, which is a word. You don’t have to add the :SceneTreeTimer but it will help prevent errors and improve fps/performance.

@export var cooldown:float = 1

This is an @export variable that keeps track of how long you want the cooldown to be. The colon “:” makes it so it can only be a specific type of variable, in this a case a float. Floats are basically just numbers with decimals. The @export part lets you edit the variable it without starting the game in the inspector. The inspector should be on the left side of your screen.

const REVOLVER_BULLET = preload("res://Scenes/revolver_bullet.tscn")

This is a value referencing a preload of your bullet scene. Costs cannot be change in the code but run faster in turn. Since you’re not going to be changing this, it’s good to have it as a const. Even though this is referencing something, we don’t need it to be a @onready variable because it’s accessing your files which are more reliable than the scene tree, aka the nodes. Consts also cannot be @onready vars either way.

func _ready() -> void:
	#Make fire_cooldown not equal to null
	cooldown_tracker = get_tree().create_timer(0)

The function _ready() runs 1 time. If the cooldown is’nt created before you shoot, it won’t be able to check if the cooldown is over or not, so it creates a temporary replace meant which equals nothing.
The get_tree().create_timer(0) basically makes it a timer, specifically a SceneTreeTimer, that lasts 0 seconds.

func _process(delta: float) -> void:
	#Rotate The Gun To Face The Same Direction As The Player
	look_at(get_global_mouse_position())
	#Limit Rotation
	rotation_degrees = wrap(rotation_degrees, 0, 360)
	if rotation_degrees > 90 and rotation_degrees < 270:
		scale.y = -2
	else:
		scale.y = 2

I’m going to skip this part because I didn’t do any changes.

	if Input.is_action_just_pressed("LMB"):
		#When The Coodown Is Done Fire
		if not cooldown_tracker.time_left > 0:
			shoot()

In here, whenever you press LMB the script checks if the timer has any time left, then inverses that. So, if there is time, it won’t run shoot(), but if there isn’t it will. This is exactly how a cooldown works in every other game.

func shoot():
		#Play the shoot animation
		animated_sprite.play("Shoot")
		#Spawn bullet and mirror players rotation and position
		cooldown_tracker = get_tree().create_timer(cooldown)
		var bullet_instance = REVOLVER_BULLET.instantiate()
		get_tree().root.add_child(bullet_instance)
		bullet_instance.global_position = global_position
		bullet_instance.rotation = rotation

This is similar to your method, but I put it a separate function for easier to read code. The only thing I did was to restart the cooldown whenever you shoot.

If you have any question, please ask away. Ill update you about the other scripts sometime tomorrow. (probably)

2 Likes