Beginner with Godot/Gamedev: Ways to structure projectile firing code

Question

I am new to gamedev, and am starting with a simple game in Godot. I do not have a problem with my code yet, but I am asking a general question about how you would structure this code, so that I can get used to the programming paradigms in Godot and common practices in gamedev in general.

My game:

I am creating a simple game where I am shooting objects from a character toward (eventually) another character. Right now I am working on the code dealing with the logic of spawning this projectile.

Right now, my player has a state machine, which alternates between idle, walking, and shooting. The player state machine is a node in the player scene with multiple children nodes.

Player

  • State Machine
    • IdleState
    • WalkState
    • ShootState

The “Shoot” state is entered from either the walk or idle state whenever I click down on the left mouse. While holding down the mouse, this “charges up” the shot, and whenever the left mouse is released, the shoot state is exited. This creates a new projectile instance and then invokes its “fire” method as below:

shoot_state.gd

@onready var shot_projectile: PackedScene = preload("res://shot_projectile.tscn")

var charge_level: int

func exit() → void:
    self.player.shot_arrow.visible = false
    var shot_type = null # Different types will be implemented later
    var rotation: float = self.player.shoot_arrow.rotation
    var new_shot = shot_projectile.instantiate()
    root_scene.add_child(new_shot)
    new_shot.fire(shot_type, self.player.global_position, charge_level)


In the projectile scene, this is how the shot fire is implemented

shot_projectile.gd

func fire(type, position: Vector2, charge_level: int) -> void:
	var dest: Vector2 = get_global_mouse_position()
	distance_to_target = position.distance_to(dest)
	creation_time = Time.get_ticks_msec()
	
	var direction: Vector2 = position.direction_to(dest)

	self.visible = true
	self.position = position
	self.scale *= Vector2(charge_level, charge_level)
	velocity = direction * speed
	
	process_mode = Node.PROCESS_MODE_ALWAYS
	

func _process(delta: float) -> void:
	var distance_moved: Vector2 = velocity * delta
	self.position += distance_moved
	distance_to_target -= distance_moved.length()
	if distance_to_target <=0 or Time.get_ticks_msec() - creation_time >= LIVE_TIME:
		destroy()

I want to see if there are any obvious ways of doing this better, and I would like to stimulate some conversation about changes I should make in order to make this more tolerant to future feature additions, bug fixing etc. Basically I am just wanting to know how you would make this functionality and why.