State Machine doesn't work when there is more than 1 NPC

Godot Version

4.2.1

Question

I have a very basic little game and want the NPC to flee from the player, when they get too close (that is working perfectly fine), go to a fish when it gets close, then wait for a few seconds after the fish is gone (the fish just destroys itself when touched).
All of this is working perfectly fine when I only have one NPC for testing, but as soon as I put a second one in the scene (still just testing, want them to spawn automatically later) some of this doesn’t work anymore.

The first one still behaves correctly, but the second one doesn’t wait after the fish is destroyed. In fact, the first one waits when the second one gets to the fish.

Can someone tell my why that is? What have I missed?

EDIT: Okay I get why only the first one goes into WAIT state now, I have manually connected the signal from the Fish collection node to this one NPC. How can I make it so the NPC knows when the specific fish in their reach is destroyed?
I might be just really dense rn but after staring at this for so long I don’t see the probably obvious solution :sweat_smile:

extends CharacterBody2D
class_name Penglin

var player: PackedScene = preload("res://scenes/Enteties/player.tscn")
var move_direction: Vector2
var wander_time: float
var current_state = WANDER
var speed = 8

enum {WANDER, FEED, FLEE, WAIT}

func _ready():
	randomize_wander()

func _physics_process(delta):
	match current_state:
		WANDER:#0
			$Alarm.visible = false
			if wander_time > 0:
				wander_time -= delta
			else:
				randomize_wander()
			move()
		FEED:#1
			$Alarm.visible = false
			move()
		FLEE:#2
			$Alarm.visible = true
			speed = 10
			move_direction = Globals.player_position.direction_to(global_position)
			move()
		WAIT:#3
			$Alarm.visible = false
			$Sprite2D.animation = "idle"
			await get_tree().create_timer(2).timeout
			current_state = WANDER
			

func move():
	velocity = move_direction * speed
	move_and_slide()
	# Sprite change based on direction
	if abs(move_direction.x) > abs(move_direction.y): #x-axis deviation is higher than y-axis
		#its more sideways then up or down
			if move_direction.x > 0: #right
				$Sprite2D.animation = "walking_right"
			else: #left
				$Sprite2D.animation = "walking_left"
	else:
   		 # its more up or down then sideways
			if move_direction.y > 0: #down
				$Sprite2D.animation = "walking_down"
			else: #up
				$Sprite2D.animation = "walking_up"

func _on_awareness_zone_body_entered(body):
	if body is Player:
		current_state = FLEE
	elif body is Fish:
		move_direction = (body.global_position - position).normalized()
		current_state = FEED
	else:
		current_state = WANDER

func _on_awareness_zone_body_exited(body):
	if body is Player:
		current_state = WANDER
	elif body is Fish:
		move_direction = (body.global_position - position).normalized()

		
func randomize_wander():
	move_direction = Vector2(randf_range(-1, 1), randf_range(-1, 1)).normalized()
	wander_time = randf_range(0, 2)

func _on_fishes_child_exiting_tree(_node):
	current_state = WAIT

This is my code so far, below is the node structure of the main scene I am testing in.

image

So as far as I can tell, right now your AI goes into WAIT mode when any fish in the scene exits the tree, because it’s connected to a signal from the fish. You probably don’t need the fish to alert all NPCs in the scene when it dies, right?

You say the fish destroys itself when touched - how does the fish know that it’s been touched? What if, instead, the NPC detects that it has touched a fish, and then both destroys the fish and puts itself in WAIT mode?

1 Like

Yes, that is how it “works” right now and it is clunky and as you pointed out, triggers at times it shouldn’t.
The fish has an Area2D that triggers queue_free() when an NPC enters.

I want to do it the way you said but I just don’t know how to code that. I am really new to this and no matter what I googled I didn’t know how to search for an implementation of this.

How do I destroy the fish from the NPC’s script?

Okay after thinking about it a bit more I realised i already had a similar thing in a different script and just had to apply the same logic.

Thank you for pointing out that the fish doesn’t have to destroy itself, that really helped get to think in the right direction.

This is how I solved it now:

func _on_food_zone_body_entered(body):
	body.queue_free()
	current_state = WAIT

So I basically just added another Area2D, that only detects fishes and then destroys them.
Maybe there is a more elegant way of doing this and if there is I am happy to hear from anyone who wants to show me, but for now this works fine.

This was what I had in mind, yeah! Glad you managed to solve it :smiley:

1 Like

Once the fish is deleted it will exit body, I think, and your npc will still look for it.

You can add another check to see if body is_queued_for_deletion i.e. it’s been eaten. Or have a state on the fish that is more readable body.state == Fish.state.DEAD

Godot will complain that you are deleting a node during a physics thread. Maybe queue free avoids this by deferring the free.

I would make a function on the fish to delegate the free to the fish scene. So it would be something like body.kill()

1 Like

Good idea, will try that as well

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.