Action Just Pressed isn't working in conjunction with Area Entered

Godot Version

Godot 4.6.2 (Browser Version)

Question

I’m trying to program an attack detection for my enemy and I need the enemy to know specifically what input was pressed since I plan to use the same collision area for different actions.

The code below is what I have now, and the sprite never hides. The value of insideArea changes correctly (I checked using print statements), but it never hides the sprite.

I would assume that the problem is that the Input.is_action_just_pressed is only true for a single frame, which doesn’t last long enough for the insideArea to be changed to true. My problem is, I don’t want to artificially create input delay by using a wait function. I want to know if there is any way to do what I’m trying to do without the need for any delay.

I’ve already tried nesting the if statements, changing the order in the &&, etc. Any changes in the formatting of this code just assume I have done.

Again it may be important that I am using the web browser version of Godot, so this might only be a problem on that version since I haven’t tested it using the desktop version.

extends CharacterBody2D
class_name Enemy
var insideArea= false

func _on_enemy_box_area_entered(area) -> void:
	if area is InteractBox:
		insideArea = true
		
func _on_enemy_box_area_exited(area) -> void:
	if area is InteractBox:
		insideArea = false


func _physics_process(delta):
	if insideArea&&Input.is_action_just_pressed("attack"):
		hide()

I’m really new to game design and GDscript in general, so if it just isn’t possible to do it the way I’m trying to I apologize for the redundant question! x-(

The way you’re trying to do it seems to be correct.
I’d suggest adding print statements to your _physics_process() and observe what happens to both conditions you check when you press the button and enter the area.

1 Like

Hey there. Just did this, and it doesn’t seem to have given me any new information.

func _physics_process(delta):
	if insideArea:
		if Input.is_action_just_pressed("attack"):
			print("attack")
		print("in")

Doing it in the above order has it print “in” continuously until the attack is done, but it never prints attack.

func _physics_process(delta):
	if Input.is_action_just_pressed("attack"):
		if insideArea:
			print("in")
		print("attack")

Doing it in this way causes it to print “attack” on the frame of the input, and then it does not print “in”

In the bottom example, if I quickly press the attack input a second time, then “in” will be printed.

I already had assumed the problem was related to the insideArea boolean not being detected on the exact frame that the area is entered, while the Input.is_action_just_pressed is frame perfect.

I am stuck where I started x-b, at least you think it seems correct, so I know I’m not just going crazy lol.

Is this some kind of melee attack and you’re checking if the sword hits the area? You can just use enums or a variable to keep track of what the player character is doing.

Lets say its a sword swing. When I press attack, I change the player’s state (in the player script) to ‘is_attacking = true’, then once the animation ends, i return it to false.

Then instead of asking about input, you ask the area ‘if area.is_attacking’ (you need guard clauses later on) and you additionally set the new variable ‘is_attacked’ as true.

Now you only check if “is_attacked” is true

1 Like

Hi there. I already have a script like this in the player scene.

extends CharacterBody2D
class_name Player
var attacking : bool = false
func _physics_process(delta):
	velocity.x = 0
	velocity.y = 0
	if Input.is_action_just_pressed("attack"):
		attacking = true
		$AnimationPlayer.play("new_animation")
		await $AnimationPlayer.animation_finished
		attacking = false

But I have my enemy code in a different scene and I’m unable to reference the attacking variable (if there is a way to circumvent this, or to create something equivalent to a public method in java, please let me know.)

I’m a bit confused about what you mean by:

I’m not sure if this is what you meant, but I changed the enemy’s code to the following:

extends CharacterBody2D
class_name Enemy
var attacked= false

func _on_enemy_box_area_entered(area) -> void:
	if area.attacking:
		attacked = true
		print("attacked")
		
func _on_enemy_box_area_exited(area) -> void:
		attacked = false
		

func _physics_process(delta):
	if attacked:
		hide()

This code never prints attacked, nor does it hide.

I’m assuming that this isn’t what you meant, or I made an error somewhere. Whenever you get the chance I ask that you shoot me another reply.

Thanks so much for the help! Even if I don’t think I understood properly, this is definitely giving me some new ideas! : - )

func _on_enemy_box_area_entered(area) -> void:
	await get_tree().process_frame
	if area.attacking:
        attacked = true
		print("attacked")

“await get_tree().process_frame” is a common dirty fix for a lot of things. If that doesn’t work, try coding out the ‘attacking = false’, we just want to rule out the await somehow not working.

1 Like

Completely eliminate the usage of await from your code.

Yeah, generally you should avoid using await unless really necessary.

Hey!! Thanks for getting back to me : - ) (Apologies for a REALLY long reply, I wanted to be very thorough in my documentation.)

I used this code as you said:


func _on_enemy_box_area_entered(area) -> void:
	await get_tree().process_frame
	if area.attacking:
		attacked = true
		print("attacked")
		
func _on_enemy_box_area_exited(area) -> void:
	pass
		

func _physics_process(delta):
	if attacked:
		hide()

…and it does not print “attacked”

The error is coming from the “area.attacking.”

Probably this is important and I should’ve mentioned it earlier, but the InteractBox is only a child of the Player node, and it has its own code (there is nothing in the InteractBox code except for a class name declaration.) The variable “attacking” is only inside of the Player script and not the InteractBox script.

I tried doing this

func _on_enemy_box_area_entered(area) -> void:
	await get_tree().process_frame
	if (Player is attacking && area is InteractBox):
		attacked = true
		print("attacked")

and received the error: “Could not find type “attacking” in the current scope.”

From my knowledge of Java, I tried making a method in player that would return the value of “attacking",” then calling that method in InteractBox, which would then give it a value accessable by the Enemy code. Here’s what I ended up doing:

# this is in the InteractBox, which is a child of my player
func isAttacking():
	return get_parent().attacking
# this is in my enemy's code
func _on_enemy_box_area_entered(area) -> void:
	await get_tree().process_frame
	if area is InteractBox && area.isAttacking:
		print("attacked")

Using this code, “attacked” IS printed when the attack area enters the enemy hitbox.

# this is the whole enemy code
extends CharacterBody2D
class_name Enemy
var attacked= false

func _on_enemy_box_area_entered(area) -> void:
	await get_tree().process_frame
	if area is InteractBox && area.isAttacking:
		attacked = true
		print("in")
		
func _on_enemy_box_area_exited(area) -> void:
	if area is InteractBox:
		attacked = false
		print("out")
		

func _physics_process(delta):
	if attacked && Input.is_action_just_pressed("attack"):
		print("attacked")
		hide()
	pass

With the above code, “in” is printed frame perfect, “out” is printed frame perfect, and “attacked” is never printed. If I remove the “&& Input.is_action_just_pressed(“attack”)” then “attacked” is printed and the enemy sprite hides on the first frame of the attack.

My problem with this is that attacked is continuously printed for the entire duration of the attack, which means that the enemy would hide at ANY point during the attack instead of only on the first frame.

So THEN I tried to do this:

func _on_enemy_box_area_entered(area) -> void:
	if area is InteractBox && area.isAttacking:
		attacked = true
		print("in")
		
func _on_enemy_box_area_exited(area) -> void:
	if area is InteractBox:
		attacked = false
		print("out")
		

func _physics_process(delta):
	if Input.is_action_just_pressed("attack"):
		await get_tree().process_frame # this await is intended to have the        function pause for one frame such that "attacked" needn't be true frame perfect.
		if attacked:
			print("attacked")
			hide()
	pass

This one prints out “in” and “out” but never “attacked”… it doesn’t make sense to me! When “attack” is pressed, that will be true, then it will wait one frame, and check if “attacked” is true, which it WILL be because “attacked” is now also frame perfect… I’m completely stumped.

It works, but it still isn’t frame perfect. I’m convinced it may be impossible to ensure that “attacked” is only printed on the earliest frame after “attack” is pressed that “attacked” is true…

I’m going to mark this as solved, because my original problem HAS been solved… but I’d still appreciate any further feedback in this thread or in my DMs.

Ok sorry for replying the day after I posted this as solved but I thought this might help somebody else: the code I tried last works if THREE frames are await-ed.

func _physics_process(delta):
	if Input.is_action_just_pressed("attack"):
		await get_tree().process_frame
		await get_tree().process_frame
		await get_tree().process_frame
		if attacked:
			print("attacked")
			hide()
	pass

I experimented more with what I had originally, and using ONE await works if you press attack again while the first attack is still ongoing, so, not ideal. I tried with two, and it works every time except the first time you attack, for reasons I do not know and honestly don’t care to find out. Three frames seems to be the sweet spot, the enemy hides on the (nearly) exact frame it is hit.

If an apparent solution to your problem is 3 consecutive awaits in _physics_process(), it’s a strong sign that there’ll be more unpleasant surprises waiting for you down the line.

3 Likes

Using 3 awaitstatements like that it probably going to come back to haunt you. Especially if you use them liberally throughout the rest of your code.

I’d recommend adding a variable to InteractBox that contains the attack type and is set when the player presses the attack button. Then in _on_enemy_box_area_enteredyou can check set that variable to a flag that is checked/cleared in the _physics_processinstead of checking if the input was just pressed.

1 Like

Do not do that, seriously.

Can u switch on the collision shapes for debugging so u can see them when u run the game and try it?

Also can u check if the collision layer for the area2d is set correctly and and the mask of what it can see is correct.

Can u also check that the function is attached to the correct area2d and use a print on the area2d with maybe special name assigned to each one to check them?

Let’s say I’ve made all these mistakes in the past 2 months programming with godot.

Hey there, thank you for the explanation.

Not sure if this is what you meant, but I put this var into InteractBox

var atk = Input.is_action_just_pressed("attack")

func isAttacking():
	return atk

and then made this adjustment to my Enemy code:

# attacked starts set to false, omitted for space
func _on_enemy_box_area_entered(area) -> void:
	if area is InteractBox && area.isAttacking:
		attacked = true
		print("in")

func _physics_process(delta):
	if attacked:
		print("attacked")
		hide()
	pass

This works! Thank you so much! : - D

I’ll probably mess with it a little more, (i.e. setting “attacked” to false as soon as the sprite hides, instead of when exited) but this is the solution I was looking for!

Yep, that’s exactly what I meant. Glad it’s working!