(Video Attached) Issues with an aimable top-down attack

v4.2.2

Question

Issues (listed in the same order they are presented in the video):

  • When whipping (attackB), if I aim while doing it, the whip (arm sprite) will face directly forward right before ending.
    • Related issue (basically the same thing, but I also show it in the video), if I buffer turning around you can see the whip go straight forward in the other direction even if I’m aiming it.
  • If I whip and then whip again immediately after the first one ends, I get stuck moving in last_direction, only being able to stop it by whipping again (attackB) or shooting (attackA → attackB).
  • This one isn’t able to be shown in the video, but if I’m facing right (unflipped), aiming the whip is normal (sort of). Up is up, right is right, etc. However, if I face left (flipped), when aiming up becomes down, right becomes left, etc.

Video

Code

(See attackA() ← involves the arm_aim variable that is used in the whip attack, shoot() ← involves arm_aim (I believe), and attackB() ← the actual whip attack

extends CharacterBody2D

var direction: = Vector2.ZERO
var arm_aim = Vector2.ZERO
var whip_aim: float = 0
var last_direction: Vector2 = Vector2.RIGHT
var is_rolling = false
var is_aiming = false
var is_shooting = false
var can_flip = true
var is_whipping = false
var flash_timer: Timer
var whip_timer: Timer 
var bullet = preload("res://scenes/cowboyBullet.tscn")
var flash_scene = preload("res://scenes/cowboyFlash.tscn")
@export var speed: int = 75
@onready var anim_player = $AnimationPlayer
@onready var sprite = $Sprite2D
@onready var pivot = $"Shoulder Pivot"
@onready var arm = $"Shoulder Pivot/Arm"
@onready var bullet_spawn = $"Shoulder Pivot/Arm/Bullet Spawn"
@onready var flash_spawn = $"Flash Spawn"

func _ready():
	anim_player.animation_finished.connect(_on_animation_finished)

func _process(delta):
	movement()
	rolling()
	attackA()
	attackB()

func _physics_process(delta):
	velocity = direction * speed
	if is_shooting == true:
		velocity = Vector2.ZERO
	move_and_slide()

func movement():
	if not is_rolling and not is_shooting and not is_whipping:
		direction = Input.get_vector("p1left", "p1right", "p1up", "p1down").normalized()
		if direction.length() > 0:
			anim_player.play("cowboyRun")
		else:
			anim_player.play("cowboyIdle")
		
		if direction.length() > 0:
			last_direction = direction.normalized()
	else:
		direction = Input.get_vector("p1left", "p1right", "p1up", "p1down").normalized()
		if direction.length() == 0:
			direction = last_direction
	
	if can_flip == true:
		if direction.x < 0:
			sprite.flip_h = true
			pivot.scale.x = -1
			pivot.position = Vector2(-2, -0.5)
		elif direction.x > 0:
			sprite.flip_h = false
			pivot.scale.x = 1
			pivot.position = Vector2(2, -0.5)

func rolling():
	if Input.is_action_just_pressed("p1roll") and not (is_rolling or is_aiming or is_shooting or is_whipping):
		anim_player.play("cowboyRoll")
		is_rolling = true
		speed = 125
	
	if is_rolling and direction.length() > 0:
			last_direction = direction.normalized()
	
	if anim_player.is_playing() == false and is_rolling:
		is_rolling = false
		speed = 75
		flash()

func flash():
	if sprite.flip_h:
		flash_spawn.position = Vector2(6, -6)
		var flash_instance = flash_scene.instantiate()
		flash_instance.global_position = flash_spawn.global_position
		get_parent().add_child(flash_instance)
	else:
		flash_spawn.position = Vector2(-7, -6)
		var flash_instance = flash_scene.instantiate()
		flash_instance.global_position = flash_spawn.global_position
		get_parent().add_child(flash_instance)
	flash_timer = Timer.new()
	flash_timer.wait_time = 0.5
	flash_timer.one_shot = true
	flash_timer.connect("timeout", Callable(self, "_on_flash_timer_timeout"))
	add_child(flash_timer)
	flash_timer.start()

func attackA():
	if not is_shooting and not is_whipping and Input.is_action_pressed("p1attackA"):
		anim_player.play("cowboyAim")
		is_aiming = true
		speed = 0
		can_flip = false
		pivot.scale.y = 1
		arm_aim = Input.get_vector("p1aimLeft", "p1aimRight", "p1aimUp", "p1aimDown").normalized()
		if arm_aim.length() > 0:
			if sprite.flip_h:
				var angle = atan2(-arm_aim.y, -arm_aim. x)
				pivot.rotation = angle
			else:
				var angle = atan2(arm_aim.y, arm_aim.x)
				pivot.rotation = angle
			if pivot.rotation > PI / 2 or pivot.rotation < -PI / 2:
				pivot.scale.y = -1
			else:
				pivot.scale.y = 1
	elif not is_shooting and not is_whipping:
		pivot.rotation = 0
	
	if Input.is_action_pressed("p1attackA") and Input.is_action_just_pressed("p1attackB") and is_shooting == false:
			print("shoot")
			is_shooting = true
			anim_player.play("cowboyShoot")
			shoot()
	
	if Input.is_action_just_released("p1attackA"):
		speed = 75
		is_aiming = false
		can_flip = true

func shoot():
	var bullet = bullet.instantiate()
	bullet.b_position = bullet_spawn.global_position
	if flash_timer and flash_timer.time_left > 0:
		bullet.speed = 250
	if sprite.flip_h:
		bullet.b_rotation = pivot.rotation + PI
		bullet.direction = pivot.rotation + PI
	else:
		bullet.b_rotation = pivot.rotation
		bullet.direction = pivot.rotation
	get_parent().add_child(bullet)

func attackB():
	if Input.is_action_just_pressed("p1attackB") and not (is_aiming or is_shooting):
		print("whip")
		is_whipping = true
		can_flip = false
		anim_player.play("cowboyWhipWindup")
		speed = 0
		whip_timer = Timer.new()
		whip_timer.wait_time = 0.05
		whip_timer.one_shot = true
		whip_timer.connect("timeout", Callable(self, "_on_whip_timer_timeout"))
		add_child(whip_timer)
		whip_timer.start()
	
	if whip_timer and whip_timer.time_left > 0:
		arm_aim = Input.get_vector("p1aimLeft", "p1aimRight", "p1aimUp", "p1aimDown").normalized()
		if arm_aim.length() > 0:
			if sprite.flip_h:
				whip_aim = atan2(-arm_aim.y, -arm_aim. x)
			else: 
				whip_aim = atan2(arm_aim.y, arm_aim.x)
			whip_aim = arm_aim.angle()
			print(arm_aim, "|||", whip_aim)
	
	if whip_timer and whip_timer.time_left == 0:
		print("TIMER UP")
		pivot.rotation = arm_aim.angle()
		whip_timer.queue_free()
		whip_timer = null
		is_whipping = true
	
	if anim_player.is_playing() == false and is_whipping:
		is_whipping = false
		speed = 75

func _on_animation_finished(animation_name: String):
	if animation_name == "cowboyShoot":
		is_shooting = false
		if is_shooting == false:
			print("not shooting")
	elif animation_name == "cowboyWhipWindup":
		is_whipping = false
		speed = 75
		if is_whipping == false:
			print("not whipping")
			can_flip = true