How to change NPC movement behavior?

Godot Version

4.3

Question

I’m quite new to Godot and am learning a lot making a new game. I’ve got a function that is meant to change an NPC’s behavior, and I’m not able to override the base function. I thought I’d ask for community help on this one.

Here’s my logic:
Player presses and holds a button mapped as “confuse.” It’s meant to override the NPC evade function and get them to stop running, move slowly towards the player and play a confused animation as long as the player holds the button down. I thought it would be best to use a global script to communicate between the two. For the player script, I used Input.is_action_pressed and Input.is_action_just_released to turn global.player_current_confuse on and off. Here’s my script for the NPC:

extends CharacterBody2D
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D

const SPEED = 200.0
const JUMP_VELOCITY = -400.0

var evade = false
var player = null
var confused = false

@export var acceleration: float = 5.0
var current_speed: float = 0.0

func _physics_process(delta: float) → void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta

if evade:
	var direction = (position - player.position).normalized()
	position += direction * SPEED * delta

	current_speed = lerp(current_speed, 0.0, acceleration * delta)

if confused:
	var confused_direction = (position + player.position).normalized()
	position += confused_direction * SPEED * delta
	


	
move_and_slide()

func _on_detection_area_body_entered(body: Node2D) → void:
player = body
if global.player_current_confuse == false:
evade = true
if global.player_current_confuse:
confused = true

func _on_detection_area_body_exited(body: Node2D) → void:
player = null
evade = false
confused = false

Currently, I can’t get any change out of the NPC. I managed to get it to change animation once, but then it crashed, and I deleted and rewrote the script.

Any advice would be greatly appreciated.

Hi, glad your trying to learn godot.
first of all please mark your code as such like this:

if test == true:
   return "sucess

```gdscript
if test == true:
return "sucess
```
Now regarding your actuall problem. I would suggest you do some debugging.
Modify your functions as such

func _on_detection_area_body_entered(body: Node2D) → void:
print("Detected Player") #DEBUG
print("Players currentConfuse is: ", global.player_current_confuse) #DEBUG
player = body
if global.player_current_confuse == false:
evade = true
if global.player_current_confuse:
confused = true

func _on_detection_area_body_exited(body: Node2D) → void:
print("Not detecting player anymore") #DEBUG
player = null
evade = false
confused = false

The print messages will show up in the console so you can see what exactly the problem is. Delete them if you fixed it.
If you run your game like this you will see:

  • Is your player correctly being detected
  • is your confuse value correctly set

Something I just noticed:
Do you really want the Enemy to be able to be in both evade and confused mode at the same time? because as of current this is possible. This will cause really weird movement behaviour becaue both the confusedDirection and the direction will be applied at the same time.
If you do not want this you have to do some minor or major changes to your code.
Do you want to have more that just the current 2 movement modes later?
if yes you can make an elif hirachy like this:

if confused:
	var confused_direction = (position + player.position).normalized()
	position += confused_direction * SPEED * delta
elif evade:
	var direction = (position - player.position).normalized()
	position += direction * SPEED * delta

	current_speed = lerp(current_speed, 0.0, acceleration * delta)

Now the Enemy will only evade if they are not confused

if you only have 2 movement modes: Dont use 2 booleans use 1.
For example remove confused and only make it evade then do:

if !evade:
	var confused_direction = (position + player.position).normalized()
	position += confused_direction * SPEED * delta
else:
	var direction = (position - player.position).normalized()
	position += direction * SPEED * delta

	current_speed = lerp(current_speed, 0.0, acceleration * delta)

And replace all statements where you said confused = true with evade = false
This will spare you with additional confusion.

Something else to consider. Using a global for this usecase is rather unortodox. It means that if your Player presses the confuse button all enemys with your NPC script will be confused. It also means that all scripts and methods you ever use will be able to acess the player_current_confuse variable.
If you dont mind that, your approach should be fine.
I personally would probably prefer using godots signal system for things such as this. I dont know your game idea so I cant exactly tell you what to do there but you could for example define a

signal changedConfuseState(confused)

in your Player or in your confuse area.
If the enemys have a reverence to that area or to the player (depending on where you defined the signal) you can subscribe to it as such:

var _amIconfused: bool
@onready confusedDetector : Area2D = $DRAG_IN_YOUR_NODE_FROM_SCENE_VIEW

func _ready() -> void:
   confusedDetector.changedConfuseState.connect(onConfuseStateChanged())


func onConfuseStateChanged(confused: bool)
   _amIconfused = confused

This would have several advantages:

  1. you can easily make enemys start or stop to ignore or not ignore confusion by subscribing or unsubscribing form the signal with connect() and disconnect()
  2. You dont add more and more clutter to 1 central global class
  3. You can easily give the Confusion multiple parameters for exaple like a confusion time after which the enemys will automatically unconfuse themselfes.
  4. Probably some others that I am forgetting

But it is a little more complicated so if you are just looking for the simpelest possible solution you might be good with the global class. Although going for the simplest solution can mean that it will get more complex later on depending on your type of game. There is also a chance that the global class is simply the best alternative you have I just wanted to tell you that the signal option exists.
I wish you good look with your game and hope you find the error :grin:

Wow, thanks so much for your thorough reply! These are some fantastic suggestions and very educational for me.

I’m trying to make sure that evade and confuse don’t happen together – that’s the main crux of my problem. Super excited to try out your suggestions. I’ve tried an elif before on it, but I think I didn’t write it correctly.

I had assumed that using global would be the simplest idea, but yes that would create a number of other issues for me.

Thanks so much for your time and multi-level thinking on this. I’ll work towards a solution and let you know what happens.

Glad if I could help.
I would also suggest you to rename this topic. Right now it looks as if you where working with inheritance, which as far as I am seeing you are not. So something along the lines of: “How to transfer information between scripts” or “How to detect collisions” would make more sense depending on what exactly you problem is. Right now it would probably be somthing like “How to dynamically change movement behaviour”.