How to fix the enemy bug

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

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?

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 
:bust_in_silhouette: Reply From: Inces

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.

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.

amaou310 | 2022-09-22 07:11

I don’t have discord :slight_smile:

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.

Inces | 2022-09-22 14:22

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

amaou310 | 2022-09-24 07:58

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.

Inces | 2022-09-24 18:53