Aiming code stopped working after moving it around into different functions

v4.2.2

Question

Why can’t I aim the “whip” attack? It’s supposed to detect aim while the whipTimer is running, and then attack in the last direction after the timer is up. It used to work, but after moving some code into new functions and whatnot, it just doesn’t work. I can’t aim. It just performs the attack facing forwards.

Code

extends CharacterBody2D

var direction: = Vector2.ZERO
var arm_aim = Vector2.ZERO
var whip_aim: float = 0
var redirect_aim = Vector2.ZERO
var last_direction: Vector2 = Vector2.RIGHT
var can_move = true
var can_flip = true
var flash_timer: Timer
var whip_timer: Timer
var nearest_bullet: Bullet = null
var bullet = preload("res://scenes/cowboyBullet.tscn")
var flash_scene = preload("res://scenes/cowboyFlash.tscn")
var roll_shot_scene = preload("res://scenes/cowboyRollShot.tscn")
var lasso = preload("res://scenes/cowboyLasso.tscn")
var lasso_projectile: Node2D = null
var rope: Line2D = null
@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 lasso_spawn = $"Shoulder Pivot/Arm/Lasso Spawn"
@onready var flash_spawn = $"Flash Spawn"
@onready var roll_shot_spawn = $"Roll Shot Spawn"

enum State { IDLE, RUNNING, ROLLING, ROLL_SHOOT, AIMING, SHOOTING, WHIPPING, LASSO_THROW }
var state: State = State.IDLE

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

func _process(delta):
	if state == State.WHIPPING and 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:
				arm_aim.x *= -1
				arm_aim.y *= -1
			whip_aim = arm_aim.angle()
			print(arm_aim, " | ", whip_aim)
			if whip_aim > PI / 2 or whip_aim < -PI / 2:
				pivot.scale.y = -1
			else:
				pivot.scale.y = 1
		else:
			pivot.scale.y = 1
	
	if rope and is_instance_valid(lasso_projectile):
		rope.clear_points()
		rope.add_point(lasso_spawn.global_position)
		rope.add_point(lasso_projectile.global_position)

func _physics_process(delta):
	if can_move == true:
		movement()
	rolling()
	attackA()
	attackB()
	velocity = direction * speed
	if state == State.SHOOTING:
		velocity = Vector2.ZERO
	move_and_slide()

func movement():

func rolling():

func flash():

func rollShoot():

func attackA():

func shoot():

func attackB():
	if Input.is_action_just_pressed("p1attackB") and state not in [State.AIMING, State.SHOOTING, State.WHIPPING, State.ROLL_SHOOT, State.LASSO_THROW]:
		if flash_timer and flash_timer.time_left > 0:
			lassoThrow()
		else:
			print("whip")
			state = State.WHIPPING
			can_flip = false
			anim_player.play("cowboyWhip")
			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:
				print("TIMER UP")
				pivot.rotation = arm_aim.angle()
				whip_timer.queue_free()
				whip_timer = null
				state = State.WHIPPING
			
			if anim_player.is_playing() == false and state == State.WHIPPING:
				state = State.IDLE
				speed = 75

func lassoThrow():
	nearest_bullet = null
	var nearest_distance = INF
	for node in get_parent().get_children():
		if node is Bullet:
			var distance = global_position.distance_to(node.global_position)
			if distance < nearest_distance:
				nearest_distance = distance
				nearest_bullet = node
	if nearest_bullet:
		state = State.LASSO_THROW
		can_flip = false
		speed = 0
		anim_player.play("cowboyLassoWindup")
	else:
		print("no bullets found")

func bulletRedirect(body):
	print("redirecting bullet")
	redirect_aim = Input.get_vector("p1aimLeft", "p1aimRight", "p1aimUp", "p1aimDown").normalized()
	print(redirect_aim, " | ", pivot.rotation)
	var bullet = bullet.instantiate()
	body.speed = 250
	body.chargedBullet()
	if redirect_aim.length() > 0:
		body.rotation = redirect_aim.angle()
		body.direction = redirect_aim.angle()
	if rope:
		rope.queue_free()
		rope = null

func _on_animation_finished(animation_name: String):
	if animation_name == "cowboyShoot":
		state = State.IDLE
		if state != State.SHOOTING:
			print("not shooting")
	elif animation_name == "cowboyWhip":
		state = State.IDLE
		speed = 75
		if state != State.WHIPPING:
			print("not whipping")
			can_flip = true
	elif animation_name == "cowboyLassoWindup":
		if nearest_bullet and is_instance_valid(nearest_bullet):
			print("nearest bullet at ", nearest_bullet.global_position)
			anim_player.play("cowboyLassoThrow")
			lasso_projectile = lasso.instantiate()
			rope = Line2D.new()
			rope.width = 1
			rope.default_color = Color("#8e4d1e")
			get_parent().add_child(rope)
			lasso_projectile.cowboy_ref = self
			lasso_projectile.target = nearest_bullet
			lasso_projectile.l_position = lasso_spawn.global_position
			
			if sprite.flip_h:
				lasso_projectile.l_rotation = pivot.rotation + PI
				lasso_projectile.direction = pivot.rotation + PI
			else:
				lasso_projectile.l_rotation = pivot.rotation
				lasso_projectile.direction = pivot.rotation
			get_parent().add_child(lasso_projectile)
		else:
			print("bullet despawned before lasso could finish")
			print("no bullets found")
	elif animation_name == "cowboyLassoThrow":
		can_flip = true
		speed = 75
		state = State.IDLE

is this the relevant code? What does the print give you?

What do you expect to happen versus what really happens?

1 Like

The relevant code is what you sent, as well as the attackB() function.

Here’s a video of what I mean:

You can see how, when my character aims his gun, the arm rotates around the body. Previously, when I used the whip attack (with the lasso), it would do the same. However, after moving some code around between functions, it has just stopped working. You can also see how when the character performs the whip attack, there is a windup period before the arm and lasso is fully extended. In the code, there is a timer (whipTimer) that is synced up with this windup so that during the windup, the player’s aim is detected. After the time is up, it aims the entire arm & lasso in the last detected direction. At least it should.

Well the only thing whip_aim influences is if pivot.scale is ±1. So I presume it can aim only straight left/right, is that correct?

attackB has some issues, not sure how relevant. You keep making new timers, but you really should create one in-editor and start it when needed, you are essentially leaking memory while the player exists.

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()

Then you only check if the timer is done immediately after starting it, which should never be true. Seems like you are assuming whip_timer.start() will pause this function until the timer ends, but it does not, .start merely starts the timer and the function continues on it’s way. The connected function does run once the timer finishes, but you have not posted it’s definition so that’s the most I can say.

if whip_timer and whip_timer.time_left == 0:
	print("TIMER UP")
	pivot.rotation = arm_aim.angle()
	whip_timer.queue_free()
	whip_timer = null
	state = State.WHIPPING

Also a nitpick you should use Godot 4.x signal connection syntax

whip_timer.timeout.connect(_on_whip_timer_timeout)

1 Like

Thanks, I was able to apply your suggestions and fix my issue.