+1 vote

As the title suggests, in my project, I'm using two booleans, one when the weapon is inside the enemy, and another for if the attack animation is active. If both are active, then I can damage the enemy. The issue I'm encountering is that the weapon only damages the enemy when the hilt (the origin of the area 2d) is touching it. When the blade, which is a part of the same collision shape, touches the enemy, nothing happens. In the image attached, I've set up strings for testing. I attacked away from the enemy twice, twice when I attacked the enemy with the blade, and another two for when I attacked the enemy up close with the hilt. It clearly shows that when the blade touches the enemy, both conditions are met, but it doesn't trigger for some reason. Any help would be appreciated! Attached is the code I'm using and the results below it.
Link to the image if the embed doesnt work https://ibb.co/k9NywfQ
The code

Godot version 3.2.3
in Engine by (103 points)
edited by

The code isn't showing...

Added a link to the image, sorry.

Images don't load properly from that website here, but we can follow the image link anyway so I have seen your code.
I don't think there's anything wrong with it.

But could you show an image of the collision shapes in-game?
Select (top menu): Debug > Visible Collision Shapes, which will show the outlines of the shapes while running your project.

Does the sword collision shape intersect the enemy?

What might be happening is that while you have the sword animated(?) the collision shape isn't and thus stays near the player.

Okay, so I can see the hilt seems to a separate collision shape and that's the one actually getting checked which is where your problem is (since the attacks only work when it intersects).

But that seems strange if they are meant to be the same collision shape... could you show your node tree?

It's as you say.

Hm... I feel like what's happening here is that the sword isn't checking for collisions while it's being animated (so only when it reaches the idle position does it actually 'collide'). Unfortunately I don't know the answer here without being able to play with the whole project, but you can test this — offset the collision shape so it is facing forward by default.

You don't have to change the animation, just rotate the sword collision to the front and facing slightly down ↘ (to test if the animation affects anything)

This isn't the solution, but does that make it work?

No, unfortunately not. The collisions are still detected through the animation, but no damage is received.

Okay, that is a head scratcher. I've wrote many similar statements and never had any issues like that.

If you remove is_attacking == true and only check if is_in_body == true:, then...?

Why not add your code into the question using the code block or tripple backtick ```

Code

Same thing happens again. I've come to the realisation that it works properly when the sword doesn't leave the enemy's body at the end of the animation; this was achieved by have the blade horizontally jab forwards when attacking the enemy. This issue is resolved by moving the code from under the attack command into the _physics process directly, but this causes the damage to be dealt every frame, which I don't want. I can't tell if the issue is with the overlap, or with the attack command. Also, the code block doesn't wanna work. Here's the full script for the sword:

extends Area2D

var isinbody = false
var isattacking = false
var can
attack = true
onready var swingtimer = $Timer
onready var attacktimer = $Timer2

func onSwordbodyentered(body):
if body.name == "PlayerTwo":
isinbody = true
print(str(isinbody, "inbody"))

func physicsprocess(delta):
if Input.isactionjustpressed("attack") and canattack == true:
$AnimationPlayer.play("swing")
canattack = false
attacktimer.start()
swingtimer.start()
if $AnimationPlayer.current
animation == "swing":
isattacking = true
print(str(is
attacking, "attacking"))
if isattacking == true and isinbody == true:
print("attacksuccessful")
get
node("/root/TestLevel/CanvasLayer/FightUI/Player2Health").value -= 20

func onTimertimeout():
is
attacking = false
$AnimationPlayer.stop()

func onSwordbodyexited(body):
if body.name == "PlayerTwo":
isinbody = false

func onTimer2timeout():
can
attack = true

There's only really one thing it can be according to the code:

func onSwordbodyexited(body):
if body.name == "PlayerTwo":
    isinbody = false

Right here, this function is being called before the next _physics_process frame and is setting the isinbody bool to false before the if statement can check it. The animation and this function are running at a higher framerate than _physics_process, which is why the issue is present when the animation is running normally but then goes away when the sword doesn't leave the enemy's body. Probably.

Damn, thanks for spotting that. How can I delay this enough to allow the damage to take place, while still setting the bool to false?

Since nothing here really needs to be bound by delta, try change _physics_process to _process and see if that fixes it

Didn't fix it.

...And commenting out that function?
It would be nice to at least verify if that's the problem or not.

(also to help yourself with diagnostics put another print function for print(is_in_body) right before the if)

Here are the results from your suggestion (you seem to have been right):

Attacking away from the enemy:
Trueattacking
Falseinbodybefore

Attacking the enemy, but not right up against them:
Trueattacking
Falseinbodybefore
Trueinbody

Landing a successful hit:
Trueinbody
Trueattacking
Trueinbodybefore
attacksuccessful

Here's the updated function:
func process(delta):
if Input.is
actionjustpressed("attack") and canattack == true:
$AnimationPlayer.play("swing")
can
attack = false
attacktimer.start()
swingtimer.start()
if $AnimationPlayer.currentanimation == "swing":
is
attacking = true
print(str(isattacking, "attacking"))
print(str(is
inbody, "inbodybefore"))
if is
attacking == true and isinbody == true:
print("attacksuccessful")
get_node("/root/TestLevel/CanvasLayer/FightUI/Player2Health").value -= 20

I mean, I think I would have just given up if that didn't fix it. Haha.
Okay, so... couldn't you just put the is_in_body = false in the on_Timer_timeout(): function?

It was a good idea, but it didn't work. I'm not sure what to do anymore :/ Thanks for the help, and for your time, I really appreciate it

But like, the more it doesn't work, the more I'm intrigued how it's possible that it doesn't work and then more interested I get.

But I think I might have overlooked something pretty major as well:
All of this is all contained within is_action_just_pressed and the code won't (re)evaluate unless the action is just pressed.

Thus: try mashing the attack button really fast: does it...?

And one more thing if you decide to come back to this: put is_in_body = false inside of the if is_attacking == true and is_in_body == true: and delete that other function.

Good luck!

You're an absolute genius; putting the isinbody = false functioning inside the if statement worked! I can't believe I didn't think of that. Thank you so much for your help and advice, it was really helpful. Can't thank you enough.

Haha... I don't know about genius... it took me this much just to debug < 40 lines of code with you. But I'm really really glad you got it working! I'll simplify this thread into an answer so the question can be seen as solved.

Great, thanks.

1 Answer

+2 votes
Best answer

Overview:

As per the full code when the Area2D enters the enemy body it signals:

func on_Sword_body_entered(body):
    if body.name == "PlayerTwo":
       is_in_body = true
       print(str(isinbody, "inbody"))

Which sets the is_in_body bool to true and prints this information (giving a false positive diagnostic), but another function is signaled when the Area2D "exits" — and for whatever reason this signal is emitted immediately:

func on_Sword_body_exited(body):
    if body.name == "PlayerTwo":
        is_in_body = false

Thus by the time _physics_process starts processing its code the is_in_body bool has already been set to false.

func _physics_process(delta):
    if Input.is_action_just_pressed("attack") and can_attack == true:
        $AnimationPlayer.play("swing")
        can_attack = false
        attacktimer.start()
        swingtimer.start()
        if $AnimationPlayer.currentanimation == "swing":
            is_attacking = true
            print(str(isattacking, "attacking"))
            if is_attacking == true and is_in_body == true:
                print("attacksuccessful")
                get_node("/root/TestLevel/CanvasLayer/FightUI/Player2Health").value -= 20

Solution:

The end solution as we finally found was to put the is_in_body = false into the final attacking branch: if is_attacking == true, and delete the _body_exited signal.

This made it so the player couldn't spam multiple attacks if they hit whilst also fixing the issue at hand. Final revision:

if is_attacking == true and is_in_body == true:
    is_in_body = false
    print("attacksuccessful")
    get_node("/root/TestLevel/CanvasLayer/FightUI/Player2Health").value -= 20

Actual Issue:

Unknown — but it's possible that the incorrect triggering of the exit signal function is an engine bug, or perhaps the circular CollisionShapes of the Sword and the Enemy combined is causing the geometry to somehow register as both entering and exiting simultaneously.

If you need to use the _body_exited collider signal for something else, try using rectangular or polygon shapes instead of circles.

Anyway, good luck and have fun with the rest of your project!

by (1,002 points)
edited by
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.