0 votes

My enemy has five states (IDLE, CHASE, GRAB, EXCUTION, DEATH). When Player enter enemy's "Player Detection Area2D", enemy will move its state to CHASE. Once Player collided with enemy, enemy will move its state to GRAB. During the GRAB state, player can escape from enemy by mashing the button. If one is already in GRAB state, others wont be in GRAB state by emitting a signal and disabling "Player Detection Area2D". As Area Exited Function will be called and those who are not in GRAB state will return to IDLE state. The problem occurs when various enemies collided with Player at the exact same time and one is returned to IDLE state. The button mashing bar still shows up upon the enemy of IDLE state, even though it is not in GRAB state. I tried to solve this problem with a line of code if enemy_state != state.IDLE and enemy_state != state.CHASE: emit_signal("button_mashing") inside GRAB state. However, the button mashing bar still shows up. Do you have any idea to fix this problem?

enter image description here

ENEMY SCRIPT:

extends KinematicBody2D

signal button_mashing_start()
signal button_stop()
signal button()


enum state {IDLE, CHASE, GRAB, EXECUTION, DEATH}
var enemy_state = state.IDLE


func _ready():
    enemy_state = state.IDLE
    $Position2D/ZombieSkin/AnimationPlayer.connect("animation_finisehd", self, "on_animation_finished")
    Autoload.connect("has_triggered", self, "has_triggered_signal_recieved")
    Autoload.connect("has_player", self, "has_player_signal_recieved")
    current_hp = max_hp
    pass

func _physics_process(delta):

    match enemy_state:
        state.IDLE:
            label.text="IDLE"
            _animation_player.play("Zombie Idle")
            if velocity.x>0:
                $Position2D.scale.x=1
            elif velocity.x<0:
                $Position2D.scale.x=-1

            velocity.y += gravity * delta
            velocity.x = speed * direction

            if EnemyCollision.is_colliding():
                velocity += EnemyCollision.get_push_vector() * delta * 100
            velocity = move_and_slide(velocity, Vector2.UP)

            if is_on_wall() or not $Position2D/FloorRay.is_colliding() and is_on_floor():
                direction = direction * -1
                velocity.y += gravity * delta
                velocity.x = speed * direction

        state.CHASE:
            label.text="CHASE"
            _animation_player.play("Zombie Chase")

            if velocity.x>0:
                $Position2D.scale.x=1
            elif velocity.x<0:
                $Position2D.scale.x=-1

            velocity = Vector2(sign(player.global_position.x - global_position.x), 0).normalized()

            if not is_on_floor():
                velocity.y += gravity * delta

            if EnemyCollision.is_colliding():
                velocity += EnemyCollision.get_push_vector() * delta * 100
            velocity = move_and_slide(velocity * chase_speed)

        state.GRAB:
            label.text="GRAB01"
            _animation_player.play("Zombie Grab")

            velocity.y = 0
            velocity.x = 0 * direction 
            velocity = move_and_slide(velocity, Vector2.UP)
            if enemy_state != state.IDLE or enemy_state != state.CHASE:
                emit_signal("button_mashing_start")

        state.EXECUTION:
            label.text="Exution"
            _animation_player.play("Zombie Execution")

            velocity.y = 0
            velocity.x = 0 * direction
            velocity = move_and_slide(velocity, Vector2.UP)

        state.DEATH:
            label.text="DEATH"
            _animation_player.play("Zombie Death")

            velocity.x = 0
            velocity = move_and_slide(velocity, Vector2.UP)
            Autoload.emit_signal("screen_shake")
            $HitboxZombie/CollisionShape2D.set_deferred("disabled", true)
            $HitboxZombie/CollisionShape2D2.set_deferred("disabled", true)
            $HurtboxZombie/CollisionShape2D.set_deferred("disabled", true)
            $HurtboxZombie/CollisionShape2D2.set_deferred("disabled", true)



func _on_AnimationPlayer_animation_finished(anim_name):
    if anim_name == "Zombie Grab":
        print("Grab02 End!")
        enemy_state = state.CHASE
        Autoload.emit_signal("grab_finished", self)
        Autoload.emit_signal("has_player")
        Autoload.emit_signal("damage_player")
    if anim_name == "Zombie Death":
        queue_free()

func flip(): #Flip Sprite
    if direction>0:
        $Position2D.scale.x=1
    elif direction<0:
        $Position2D.scale.x=-1

func flash(): #Flash Sprite when hit bullet
    sprite.material.set_shader_param("flash_modifier", 1)
    flashTimer.start()

func _on_flashTimer_timeout(): #Stop Flash
    sprite.material.set_shader_param("flash_modifier", 0)

func on_damage(area):
    var base_damage = area.damage
    self.current_hp -= base_damage
    if self.current_hp <= 0:    
        print("IM DEAD!")
        enemy_state = state.DEATH
        $DeathSound.play()

func _on_HurtboxZombie_area_entered(area):
    if area.name == "Bullet":
        print("I got hit!")
        flash()
        $HitSound.play()
        on_damage(area)


func _on_HitboxZombie_area_entered(area):
        if area.name == "HurtboxPlayer":
            #if enemy_state != state.IDLE:
            enemy_state = state.GRAB
            print("Grab!")
            Autoload.emit_signal("has_triggered")
            pass

func _on_PlayerDetector_area_entered(area: Area2D): #Player Search Area. Chase when Player entered
    if area.name == "PlayerDetector":
        if enemy_state != state.DEATH:
            print("found you !")
            enemy_state = state.CHASE

func _on_PlayerDetector_area_exited(area: Area2D): #Player Search Area. Stop Chase when Player exited
    if area.name == "PlayerDetector":
        if enemy_state != state.DEATH:
            print("lost you !")
            enemy_state = state.IDLE

func has_triggered_signal_recieved(): #Recieve signal from Player when collided
    if enemy_state != state.GRAB:
        $PlayerDetector/CollisionShape2D.set_deferred("disabled", true)
        print("Collision off")
            pass

func has_player_signal_recieved():
    $PlayerDetector/CollisionShape2D.set_deferred("disabled", false)
    print("button Cleared! Return Idle")
    pass

func _on_ProgressBar_button_mashing_cleared(): ##If player Cleared Button Mashing, state.IDLE
    enemy_state = state.CHASE
    Autoload.emit_signal("grab_finished", self)
    Autoload.emit_signal("has_player")
    print("Return IDLE!")
    pass 

func _on_ProgressBar_button_mashing_failed(): #If player failed Button Mashing, state.EXECUTION
    enemy_state = state.EXECUTION
    print("Execution!")
    pass 
Godot version ver3.3.3
in Engine by (65 points)

1 Answer

0 votes

Problem lies in code not shown here - I can see there is some timer counting after being grabbed. When it is done, it simultanously enables collision for zombies. They will detect player and resolve their code in the same time, entering GRAB status, even if just for a one frame.

I would redesign this signal a bit. If only one zombie is allowed to be in state GRAB, why won't players collision control it instead ? Let player collide with only one zombie, emit signal on collision ( with info about zombie who collided first ), and disable emission right after, until timer counts down to 0.

by (7,447 points)

Thank you for a comment Inces. I disable player collision once collided with enemy, yet this would not fix the problem. The button mashing bar still show up , if player collided with two enemies at the exact same time. Do you mind if I contact you through discord or something. I would like to talk with you personally about this.

I don't have discord :)

The thing is You cannot just disable collision when enemies are alredy there, collision will still be called many times. You need to design a condition, which makes player emit a signal at the time of collision, and turns false when one signal is emited. To ensure only the first collision is taken into acount, You can use something like this :

Player

func on_collision_with_zombie(body):
       if grabbed == false:
              var firstzombie = hurtbox.get_overlapping_bodies[0]
              emit_signal("grabbed",firstzombie)
              grabbed = true

and let zombies react to only signals that contain themselves as argument. Take note, that this may eventually send more than one signal to the zombie, so make sure to only react once.

Thank you Inces.
What does number zero mean inside getoverlappingbodies[] and when and where do I set grabbed = false?

get_overlapping_bodies ( or areas ) return array of all collisions currently inside a shape. So just like arrays, its entries are indexed, and can be accessed by array[index]. 0 is smallest possible index, and it translates for first collision detected by collision shape.

Your timer countdown will set grabbed to false. Look at your gif video footage. When your countdown was finished, collision shaped were enabled, Zombies were alredy in range, so they both emitted a signal and both entered grabbing state.

With my approach this situation is different : countdown finished sets grabbed to false, zombies are already colliding with player, so they will force player to emit signal twice, before grabbed is back to true. Both zombies receive thiese 2 signals, but only first zombie is allowed to enter grabbing state.

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.