How can I create Melee attack based on mouse direction?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By cookieoil

First of all, Thanks for reading this and pardon me for my bad English

I’m a high school student who is new as a godot game developer, and my attempt is to create a top down 2D platform game like the game Bright Lancer (I will put the link if you wanna see Bright Lancer by Slo Nod)

My problem begins in the attack, im trying to make a melee attack (the attack is not done yet) in the direction of the mouse. Unfortunately, my code cause debugs every time the player attack (which dont seem to be affect the functions) until you click the mouse button fast enough in order to stop the animation.

My apologize, maybe my code can give u a closer view:

var AttackPoints = 3;
var current_animation = "Idle"

var s = 0
var i = 0
var a = 0
var b = 0
var c = 0

func _process(delta:float):
   if isplaying == false:
	var mouse = get_local_mouse_position()
	s = stepify(mouse.angle(), PI/4) / (PI/4)
	s = wrapi(int(s), 0, 8)
	
	if Input.is_action_just_pressed("skill"):
		i = s
	
	if Input.is_action_just_pressed("attack"):
		a = s
		b = s
		c = s

func move_state(delta:float):
   if Input.is_action_just_pressed("attack") and AttackPoints == 3:
	$AttackResetTimer.start();
	state = ATTACK;
	AttackPoints = AttackPoints - 1;
	
  elif Input.is_action_just_pressed("attack") and AttackPoints == 2:
	$AttackResetTimer.start();
	state = ATTACK2;
	AttackPoints = AttackPoints - 1;
	
  elif Input.is_action_just_pressed("attack") and AttackPoints == 1:
	$Attack3ResetTimer.start();
	state = ATTACK3;
	AttackPoints = AttackPoints - 1;
	
func attack_state() -> void:
  isplaying = false;
  animationTree.active = false;
  animationPlayer.playback_active = true;
  current_animation = "Attack";
  animationPlayer.play(current_animation + str(a));
  animationPlayer.playback_speed = 1.75;
  isplaying = true;

 if isplaying == true:
	a = null

func attack2_state() -> void:
 $Sprite.visible = false
 $Sprite2.visible = true
 isplaying = false
 animationTree.active = false
 animationPlayer.playback_active = true
 current_animation = "AttackTwo"
 animationPlayer.play(current_animation + str(b))
 animationPlayer.playback_speed = 1.75
 isplaying = true

 if isplaying == true:
	b = null

func attack3_state() -> void:
 $Sprite.visible = false
 $Sprite2.visible = true
 isplaying = false
 animationTree.active = false
 animationPlayer.playback_active = true
 current_animation = "AttackThree"
 animationPlayer.play(current_animation + str(c))
 animationPlayer.playback_speed = 1.75
 isplaying = true

 if isplaying == true:
	c = null

func attack_animation_finished()->void:
 if state == ATTACK || state == ATTACK2 || state == ATTACK3:
	$Sprite.visible = true
	$Sprite2.visible = false
	isplaying = false
	current_animation = "Idle"
	animationPlayer.play(current_animation + str(s))
	isstop = false
	state = MOVE

	if isstop == false:
		s = null

To my way of thinking, the possible way this functions work, for instance, on the first attack of three, the function will play the animation (Attack) + “a” (a number based on the direction of the mouse), after play the animation, “a” will be null in order to lock the animation. After that, the idle animation will be played and “a” wont be null anymore.

Like I said, it will cause debugs “Attacknull not found” every time “a” == null, and if you click the mouse button fast enough, isplaying might still == true and “a” will be null for eternities, which will freeze the animation forever.

Hope my summary do help you, once again, thank you for reading my terrible question, I want to fix this as quick as possible and introduce the game for my friends so ask me if you guys need more information or other crazy things that you dont understand, I will definitely appreciate every small efforts you can made, and if you are too shy for that, here are some videos link that I used to write my code, hope it can help you guys something:

8 directions based on mouse direction:

Melee attack:

(it’s 3am now so I might have some spelling mistake due to the sleepiness, just want you to know lol)

:bust_in_silhouette: Reply From: ipdramon

This may not be an “answer” to your question but more on the side of a suggestion.
I’m also currently working on something similar and I use an AnimationTree with BlendSpace2D to differentiate between the different animations as you do but I never deactivate the animationTree as you do during your attack_state functions.
I used this video as a starting point https://www.youtube.com/watch?v=KAZX4qfD06E. He also uses the mouse (even though he clicks) for his animations. I suppose having an attack state or multiple attack states as shown here https://www.youtube.com/watch?v=rGd-B6heyKs with the correct travels used from your code might point you in the direction the AnimationTree node is intended to be used.

On some general note I would like to give you some advice from my programming experience: variables with names like “a”, “b” and so on will get very hard to read and remember after a short amount of time. If you want to be able to read your code a month after not having interacted with it I would suggest you look for better names like you did in the lower part of your code with the state variable. There is no optimal length to a variables name, but you should use a meaningful name every time.

Thanks for answering my question, of course, there is no problems when you just make some simple animations like run or idle, cause there is only one animation need to be trigger, but sadly, when you try to combine it will Melee attack, things just don’t work like what you expected due to cause the long animation and more over, if the player click the mouse button fast enough in different directions, they can spam the attack like multiple times, which obviously not a good thing that we want (you can visual that the character hitting like crazy that give the enemy no chance in order to approach him)

But, finally, after all the suffering that i have to paid, I think that by deleting two of the line “a” = null and “b” = null, the game is now looking fine, this may not solve any of the problematics but it really minimize the numbers of the debugs, which might not be a good solution at this time around, but the game now can be playable in an amount of time. But still, I really like the ideas of using the animationTree instead, which can lead to other output that can changes this situation around (and also change the names like “a” and “b” might be helpful in the future, too).

Like I said, once again, thanks for giving me your suggestion and sorry for my bad English (I will fix it later lol)

cookieoil | 2021-10-30 11:28

In that case I just added 2 frames or so to the animations that I want the player to stay in this state and be vulnerable. The magic is then done in combination with the AtEnd transition that will wait with the transition until the animation is finished. If your animation takes too long and you want to speed it up, you can add a BlendTree in which you can speed up the animation by a factor. The positive of this is that you should be able to set this statically, even though I experienced that differently as can be seen in this question:
Why is my BlendSpace TimeScale only working in Editor and not in Debug? - Archive - Godot Forum

ipdramon | 2021-10-30 12:10

Ay, sorry for the the late, I have tested your idea about using the animationTree and turn out it work perfectly, the only problem remain is that after using the Melee Attack (the first Attack of three) , whenever I control my player with the “s” button (run down), the AttackCollision just automatically active and damaged the enemy, but I don’t know if that error happen before or after the I apply the animationTree, so I will pretend not to see it (I will tell you again if I found out the any clues behind that mysterious or just fix it myself :v).

But after all, despite that annoying thing, the game works great, no debugs show up and being playable. My life of suffering is end (maybe) and I can finally move on for the other things, once again, thanks for helping me lol.

In case you can help me, I will leave the code here (don’t need to answer if you don’t like):

var isplaying = false
var AttackPoints = 3;

if Input.is_action_just_pressed("attack") and AttackPoints == 3:
	$AttackResetTimer.start();
	isplaying = true
	animationTree.set("parameters/Attack1/Attack/blend_position",position.direction_to(get_global_mouse_position()).normalized())
	animationTree.set("parameters/Attack1/TimeScale/scale", 1.3)
	animationTree.set("parameters/Idle2/blend_position",position.direction_to(get_global_mouse_position()).normalized())
	state = ATTACK;
	AttackPoints = AttackPoints - 1;
	
elif Input.is_action_just_pressed("attack") and AttackPoints == 2:
	$AttackResetTimer.start();
	isplaying = true
	animationTree.set("parameters/Attack2/Attack/blend_position",position.direction_to(get_global_mouse_position()).normalized())
	animationTree.set("parameters/Attack2/TimeScale/scale", 1.3)
	animationTree.set("parameters/Idle2/blend_position",position.direction_to(get_global_mouse_position()).normalized())
	state = ATTACK2;
	AttackPoints = AttackPoints - 1;
	
elif Input.is_action_just_pressed("attack") and AttackPoints == 1:
	$Attack3ResetTimer.start();
	isplaying = true
	animationTree.set("parameters/Attack3/Attack/blend_position",position.direction_to(get_global_mouse_position()).normalized())
	animationTree.set("parameters/Attack3/TimeScale/scale", 1.3)
	animationTree.set("parameters/Idle2/blend_position",position.direction_to(get_global_mouse_position()).normalized())
	state = ATTACK3;
	AttackPoints = AttackPoints - 1;

func attack_state() -> void:
  isplaying = true
  animationState.travel("Attack1")
  yield(get_tree().create_timer(0.4), "timeout")
  isplaying = false

func attack2_state() -> void:
  $Sprite.visible = false
  $Sprite2.visible = true
  isplaying = true
  animationState.travel("Attack2")
  yield(get_tree().create_timer(0.4), "timeout")
  isplaying = false

func attack3_state() -> void:
  $Sprite.visible = false
  $Sprite2.visible = true
  isplaying = true
  animationState.travel("Attack3")
  yield(get_tree().create_timer(0.4), "timeout")
  isplaying = false

func attack_animation_finished()->void:
  if state == ATTACK || state == ATTACK2 || state == ATTACK3:
	$Sprite.visible = true
	$Sprite2.visible = false
	isplaying = false
	isstop = true
	state = MOVE

func move_state(delta:float):
  input_vector.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
  input_vector.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
  input_vector = input_vector.normalized()

  if input_vector != Vector2.ZERO:
	$Sprite.visible = true
	$Sprite2.visible = false
	isstop = false
	animationTree.active = true
	animationPlayer.playback_active = false
	roll_vector = input_vector
	swordHitbox.knockback_vector = (input_vector * 0.375)
	crossbowHitbox.knockback_vector = input_vector
	animationTree.set("parameters/Idle/blend_position", input_vector)
	animationTree.set("parameters/Run/blend_position", input_vector)
	animationTree.set("parameters/Stop/Stop/blend_position", input_vector)
	animationTree.set("parameters/Stop/TimeScale/scale", 1.3)
	animationTree.set("parameters/Roll/blend_position", input_vector)
	animationState.travel("Run")
	velocity = velocity.move_toward(input_vector * MAX_SPEED, ACCELERATION * delta)
	time_start += delta
	
else:
	animationState.travel("Idle")
	velocity = velocity.move_toward(Vector2.ZERO, FRICTION * delta)
	if time_start > 2.5:
		state = STOP
	time_start = 0
	if isstop == true:
		animationState.travel("Idle2")

The way those codes working the same like the previous one, but in this case, I also add “Idle 2” in order to make the player idle animations look at mouse position after the character finish the attack, “Idle 2” is the only one using the BlendSpace2D without BlendTree cause it doesn’t need to change the speed, of course, then “Idle 2” will change into “Idle” after the player move, to my way of thinking, I believe that is how it work. (have no idea if they are relative to the problem or not)

Edit: I just found out that after you hit the enemy second times, the AttackCollision error seem to be disappear, then show up again after the next hit.

cookieoil | 2021-11-02 07:11

I went thorugh your code (good naming by the way) and can’t find a problem that directly relates to the “running down” animation since I don’t see one here. Does this maybe also happen with other directions and you have just seen it with running down?
From what I see from your Edit it seems like the activity of the hitbox may still be active after the attack and is not set back to inactive at the end of the animation automatically. That way it would still work while running down (like your character just rushes with an invisible sword ) and on the first hit your hit code will run and set the hitbox back to inactive until the next attack. Since I can’t see how you implemented the hitboxes I can only guess, but I would create a box in the editor and just check the active flag in my animation when I want to activate it and back to false after the animation is finished. I suppose you did something like this as well and if not I would still look at that point of your project since it really sounds just like you only forgot to deactivate the collision if nothing was hit.

ipdramon | 2021-11-03 19:02

Yes, I don’t think the problem came from the code, further more, I don’t even know why the problems exist, I checked active flag in the animation, they are all at the correct position when will they active and back to false, I deleted some of the codes, they did affect the exist numbers of the error, but still I can’t get any good conclusions with it. First, the collision active whenever I press the “Run down” button after the first attack. Then, I checked it again the second times and the collision problem started at all directions after the attack 1. Finally, I checked it the third times in order to understand when will the invisible sword show up, but seem like the problematic just happen randomly without any ways to explain.

The way to I solve this problem is to make the attack collision back to false whenever the player pressed the run button, which works perfectly at this time around. That all I want to say, the problem solved and the game can be playable now, thank you for all of your efforts, they did help me a lot in order to make the game better. I don’t know what to say now except once again, pardon me for my poor English :v.

cookieoil | 2021-11-06 07:45