Making a 2D platformer and I'm trying to set up grounded attacks in such a way that the player has to basically commit to them and let the attack animation play through before they're able to do something else, such as run or jump. The only other action they should be able to do is another attack in a combo chain if there's one available. If they're walking or running, the attack should take priority and stop the movement until it's finished.

So far I've been able to prevent the player from crouching if they're doing a standing attack, but they can still move and jump. The attack animation will play through at least, but they shouldn't be able to receive any input until after the attack(s) is finished.

Here's my code:

extends KinematicBody2D

#VARIABLES
export var walkSpeed: int = 1200;
export var runSpeed: int = 2000;
export var jumpForce: int = 2750;
export var gravity: int = 4500;
export var maxFallSpeed: int = 2750;

export var health: int = 20;

#CHECKS
var isCrouching: bool = false;
var isAttacking: bool = false;
var landing: bool = false;

var stateMachine;
var velocity: = Vector2();

func _ready():
	stateMachine = $AnimationTree.get("parameters/playback");

func _process(_delta):
	pass;

func input():
	var current = stateMachine.get_current_node();
	
#MOVEMENT
	if Input.is_action_pressed("right") and !isCrouching and !isAttacking:
		$Sprite.scale.x = 1;
		if Input.is_action_pressed("run"):
			velocity.x = runSpeed;
			if is_on_floor():
				stateMachine.travel("Run");
		else:
			velocity.x = walkSpeed;
			if is_on_floor():
				stateMachine.travel("Walk");
	elif Input.is_action_pressed("left") and !isCrouching and !isAttacking:
		$Sprite.scale.x = -1;
		if Input.is_action_pressed("run"):
			velocity.x = -runSpeed;
			if is_on_floor():
				stateMachine.travel("Run");
		else:
			velocity.x = -walkSpeed;
			if is_on_floor():
				stateMachine.travel("Walk");
	else:
		velocity.x = 0;
		if !isCrouching and is_on_floor():
			stateMachine.travel("IdleStand");

#JUMP
	if is_on_floor() and Input.is_action_just_pressed("jump") and !isAttacking:
		velocity.y = -jumpForce;
		stateMachine.travel("Jump");
	if Input.is_action_just_released("jump") and velocity.y < 0:
		velocity.y *= 0.6;

#FALL
	if velocity.y >= 0 and !is_on_floor() and !isAttacking:
		stateMachine.travel("Fall");
	if is_on_floor():
		if landing:
			$LandingParticle.restart();
			$LandingParticle.emitting = true;
			landing = false;
	else:
		if !landing:
			landing = true;

#CROUCH
	if is_on_floor() and Input.is_action_pressed("crouch") and velocity.y == 0 and !isAttacking:
		isCrouching = true;
		$CrouchCollision.disabled = false;
		$BodyCollision.disabled = true;
		stateMachine.travel("Crouch");
	else:
		isCrouching = false;
		$CrouchCollision.disabled = true;
		$BodyCollision.disabled = false;

#ATTACKS
	if Input.is_action_just_pressed("light_attack") and is_on_floor() and !isCrouching and !isAttacking and velocity.y == 0:
		isAttacking = true;
		stateMachine.travel("AttackLight1");
	else:
		isAttacking = false;

func _physics_process(delta):
	input();
	velocity.y += gravity * delta;
	if velocity.y > maxFallSpeed:
		velocity.y = maxFallSpeed;
	velocity = move_and_slide(velocity, Vector2.UP);

I have an isAttacking bool that's supposed to be checked under the movement and jump code, but it doesn't seem to work there.

Also using an AnimationTree state machine that's setup properly as far as I can tell. Pretty sure it has to do with the code, but I could be wrong.

Check coroutines. You can have a loop wait until an animation is finished because the animation gives a signal when it's finished.

7 days later

Okay, so I've been told it was because of the last else statement in the input function that was the cause (although I still can't fathom how). Removing it fixes the above issue and even allows a combo attack to be performed, but now I'm running into a bigger problem where 'isAttacking' is permanently set to true and the player is unable to do anything.

Looking into it, it seems the best way to go about fixing that is to add an event or something that sets 'isAttacking' back to false, but I'm unsure how to do that with the AnimationTree.

Is your attack animation via animation player? Can you add a method track and call a function at the animation end that would set the variable to false? I recon where there is a will there is a way.

@Megalomaniak said: Is your attack animation via animation player? Can you add a method track and call a function at the animation end that would set the variable to false? I recon where there is a will there is a way.

Had to change a couple of things, but this works! Thanks!

For future reference, what I've done was delete the 'else' statement in the attack code, added a new function that changes the 'isAttacking' bool to 'false' if it's true and call that function at the end of every attack animation. Code is now:

#ATTACKS
func input():
	var currentAnimState = stateMachine.get_current_node();

	if Input.is_action_just_pressed("light_attack") and is_on_floor() and !isCrouching and !isAttacking:
		isAttacking = true;
		stateMachine.travel("AttackLight1");
	elif Input.is_action_just_pressed("light_attack") and currentAnimState == "AttackLight1" and is_on_floor() and !isCrouching and isAttacking:
		stateMachine.travel("AttackLight2");
	elif Input.is_action_just_pressed("light_attack") and currentAnimState == "AttackLight2" and is_on_floor() and !isCrouching and isAttacking:
		stateMachine.travel("AttackLight3");

func attackEnd():
	if isAttacking == true:
		isAttacking = false;

I am running into a small handful of issues though and hopefully I can get some insight into what's going on.

  1. There's a slight glitch where the player can perform standing attack and jump if they press the attack and jump buttons at the same time, sort of like the original problem where the attack will play through while they're jumping. Thankfully the player can't jump if they're already attacking and they can't perform combos, but it is possible.

  2. If the player rapidly presses the attacking button in a combo chain, they can basically skip the current attack animation and go on to the next attack in the chain (if there is one) before the previous attack even had a chance to execute. There should be a bit of a delay before the next attack is allowed to come out, similar to how it's done in fighting games or beat-em-ups, but I'm unsure how to go about that.

  3. Rapidly hitting some combination of attack and crouch can "soft lock" the player where they can't do anything. I think it's because somehow the 'isAttacking' bool is being permanently set to true. I'm want to say this could be solved once I get crouch attacks working, but for now, that's possible and I'm not sure how to fix it.

Are you sure the following elifs should feature isAttacking and not !isAttacking? If so then as @fire7side mentioned, perhaps yielding until the animation has finished playing and only continuing to the next state travel afterwards is the way to go.

a year later