Chasing script wont properly chase

Godot Version

Godot_v4.3-stable_win64.exe

Question

tldr: enemy goes towards player start position and follows input on player afterward

game is a 2D sidescroller platformer

Art student-not coder but i’ve been having fun learning on my own.
my professor gave us a code for enemy chasing behavior but i couldn’t get mine to work properly… the chasing T/F value is immediately T as soon as the game is ran (i’m assuming because it’s detecting the floor as a node) and goes towards the player’s original position, eventually slowing down when it gets near (i dont want that i need constant speed). and when it reaches where the player was it follows my inputs wherever the player is, albeit in a sliding motion, slowing down at the release of an input eventually coming to a stop.

I’m assuming this script works for everyone else in my class, except for me…?(i dont have friends i can’t ask them at all) but i did jump ahead of the rest of the class so my script and theirs(the professor’s) is kinda different.

what the node tree looks like

  • CharacterBody2D3 :satellite::page_with_curl:
    • CollisionShape2D
    • Sprite2D
    • Area2D :satellite:
      • CollisionShape2D
extends CharacterBody2D

var chasing = false
var is_dead = false
@onready var playertarget = get_tree().get_first_node_in_group("Player")  #?I changed it to this from @export var playertarget : CharacterBody2D BECAUSE ERROR OF 'POSITION' TYPE NIL STUFF
  
var speed = 500                                          #                          |
#                                                                                   |
func _ready():                                                                 #    v
	pass
	#player_node = get_node(playertarget) as Node2D    ?maybe this has something to do but it was already '#'-d when he gave the script to us
func start_chasing(target):        #?i think these functions are useless.. but what are your opinions?
	chasing = true
func stop_chasing():
	chasing = false

func _physics_process(delta: float) -> void:
	# Apply gravity if the enemy is dead and falling
	if is_dead:
		velocity.y += 1000 * delta  # Adjust gravity strength if needed       
	# Normal chase behavior
	elif chasing:
		var direction = (playertarget.position - position).normalized()
		direction.y = 0  # Keep the enemy on the ground      ?what the frick is this? can't i just do get_gravity() like normal?
		velocity = direction * speed
	# Check if enemy is off the ground to apply additional gravity
	if not is_on_floor() and not is_dead:
		velocity += get_gravity() * delta * 3.0
	
	move_and_slide()

func damage():  #dont mind this my coding is pure hot working garbage rn and another enemy that shoots has its bullets interacting with this node
	pass

func _on_area_2d_body_entered(body: Node2D) -> void:
	if body.name == "CharacterBody2D":   #?i have a question.. when it refers to body.name does it mean like the name of the node type (like how you can rename nodes) or the type name type like CharacterBody2D,Area2D, etc
		print("chasing") 
		chasing = true

func _on_area_2d_body_exited(body: Node2D) -> void:
	if body.name == "CharacterBody2D":
		chasing = false
		print("stop chasing")
	

To me, this script is a mess.

You have playertarget as a direct reference to your player node, but then you use an Area2D to detect its proximity. Moreover, the body detected by the Area2D is validated by an inline string ID which is a fragile approach – and the naming of the ID is generic ("CharacterBody2D").

The start_chasing() and stop_chasing() appear to be unused. I can only assume that their existence is meant to serve as an interface for outside nodes. Then again, that makes no sense because if an interface was the goal, the interface (i.e. the methods just mentioned) should be used in _on_area_2d_body_entered() and _on_area_2d_body_exited(), and they aren’t.

Finally, the code is plagued with inline values which is bad practice:

  • velocity.y += 1000 * delta
  • velocity += get_gravity() * delta * 3.0
  • if body.name == "CharacterBody2D":

As previously stated, I believe start_chasing() and stop_chasing() exist to allow outside nodes to control the enemy’s behaviour. However, all they do is set chasing to true/false, and start_chasing() does not make use of the target parameter, so you might as well just set chasing directly instead.

This has nothing to do with gravity. The chase behaviour within the elif chasing-block starts by computing the direction to the player via trivial vector subtraction.

var direction = (playertarget.position - position).normalized()

This direction’s y-component is not always 0 (for obvious reasons) so the character would start walking away from the ground if this is not ensured. Therefore…

direction.y = 0 # Avoid walking upwards
velocity = direction * speed

I believe this is caused by the direction and how it is initially normalized. If you change it to the following, it should work they way you want.

var direction = playertarget.position - position # Don't normalize yet!
direction.y = 0 # Avoid walking upwards
direction = direction.normalize() # Set the vector's length to 1

velocity = direction * speed # Walk in the specified direction

I’m going to translate this comment as:

  • "name of the node type" = name of the node
  • "the type name type" = type

Node.name refers to the name of the node as you see it in Godot’s editor. That is why this kind of approach is fragile. Renaming the node this code is meant to affect breaks its functionality.

That is not to say you can’t use this approach. Just be aware of its fragility.

There are a couple of reasons that could cause this issue. One is that your Area2D’s shape is simply too large. The other reason is that the enemy is near another enemy which may also be named CharacterBody2D thus triggering the chase state.


Since you don’t see yourself as a programmer, and have been given this code, there could be other problems in the project that I’m not aware of which are causing your issue(s).

I hope this helps you get a better understanding of the code. Let me know if you have any other questions.

2 Likes

@Seatix
Started harsh, but true and your analysis is excellent. Just wanted to say great answer!

@Demonade

Don’t be worried about asking your professor. Surely this would be your first port of call. If this was from a ‘Professor’ the code seems a bit shoddy for ‘example code’ for students to use. You can tell them I said that :slight_smile:

It is the name of the node that you gave it by either renaming a node or by setting the name after instantiation and before adding as a child. It is a property of the base class Node, so applies to all Nodes.

PS The documentation is excellent, use it! (Although the search function may not be Google powerful, it still works well enough)

1 Like

The revision you made to its movement speed worked and it’s now moving at constant speed! I still have the other problem of it following my inputs on the character when i make the player go left/right

Also, the fix revealed a new problem in which the enemy constantly goes left until it hits the player’s original position again and then stopped by a wall where then if I (the player character) enter its detection Area2D, it will chase after me but stop to jitter in place at a certain distance from the player. When the player(that is faster) moves slightly, the distance shrinks/widens as the enemy follows the my inputs on the player to go left/right and the enemy stays at that new distance until the player moves too far and out of the Area2D, in which the enemy continues to continuously going, mostly, left but sometimes right if done a specific way.

i know that’s a bit much to wrap around, i tried uploading a clip but new users can’t upload, and i’m not sure if i’m allowed to bypass with outside links. Anyways guess this’ll be my next bug fixing experience.

thanks for the solution to one of my problems! I’ll make some progress first before i ask again, once again thank you!

It’s possible that using the local position (position instead of global_position) causes some issues due to hierarchy differences between the player and the enemy (I don’t know if this is the case). If either entity has a parent whose position is not at the origin, your chase script will not function as expected.

Unless you need the local position of a node, use global_position over position.

var direction = playertarget.global_position - global_position

Let me know if that does anything for your described problem.

Also, if you don’t already know, Godot has documentation with a Getting Started section for new users. Here you will also find tutorials with example code. I imagine it’s a better reference point than the code from your “professor”. Take a look here.

1 Like

The enemy is properly chasing now thanks! Here is the working script in case anyone else had experienced the same issue as me, and at the end there are a few things i’d like to share learning from this issue!

extends CharacterBody2D

var chasing = false
var is_dead = false
@onready var playertarget = get_tree().get_first_node_in_group("Player")
var speed = 500                                   

func _ready():                                                                 
	pass

func _physics_process(delta: float) -> void:
	var direction = playertarget.global_position - global_position # Don't normalize yet!
	direction.y = 0 # Avoid walking upwards
	direction = direction.normalized() 
	
	#if is_dead:
		#velocity.y += 1000 * delta  # Adjust gravity strength if needed       ?this is just obsolete part of the professor's script i'm not using
	# Normal chase behavior
	if chasing:
		speed = 500
		velocity = direction * speed
		
	else :
		velocity = direction * 0

	if not is_on_floor():
		velocity += get_gravity() * delta * 25.0
	
	move_and_slide()

func _on_area_2d_body_entered(body: CharacterBody2D) -> void:
	if body.name == "Player":  
		print("chasing") 
		chasing = true

func _on_area_2d_body_exited(body: CharacterBody2D) -> void:
	if body.name == "Player":
		chasing =  false
		print("stop chasing")

things i learned:

  • after getting the direction from this line
	var direction = playertarget.global_position - global_position # Don't normalize yet!

you’d get direction value that consists of x and y movement. And the function of this line

direction.y = 0 # Avoid walking upwards

is to negate the value of y, so that you’d only be left with x’s value when you calculate the velocity in

velocity = direction * speed
  • “.name” does find the renamable name of a node! BUT if you have placed the node.tscn file in a main control scene/stage before changing its name, changing the name on the node’s file’s tab itself wont change it in the main scene, so you have to click to your main scene/stage tab to change the node’s name there so it would interact properly with scripts from other nodes when they try finding a node with that specific name!
  • .global_position works in place of .position when the interaction is between 2< different nodes that you’ve placed into the stage scene where everything is gathered into. Whereas .position focuses on a local level within the same node file

I’m not fluent in the language of coding and i had a lot of trouble processing a lot of your explanations but in the end it did help! Thanks a lot! I’ve been reading a lot of other forums and next i’m gonna try applying what i’ve been reading as states and classes and etc. This post helped me and i hope it can help others as well!

feel free to correct “things i learned” for anyone who’s more fluent!

Your interpretation of the explained difference between position (local space) and global_position (global space) is not quite right.

It’s not that global_position must be used when working with multiple nodes, it’s just that using global_position ensures that the position for each of the nodes you’re working with is in the same space – the global space. In the case of subtracting two points to get a vector…

var direction = playertarget.global_position - global_position

…you want to work with your data in the global space where it’s relative to the world origin. If you work in local space (e.g. using position), the point is relative to the node’s immediate parent. If the parent for node A and node B is not the same, you end up working with two different local spaces: the local space of node A and the local space of node B. Even if these local spaces overlap (which likely produces correct behaviour), you’re communicating that the local space for each node, in the context of the script, matters. In the case of getting a vector pointing from node A to node B, this does not matter.

Space is just another thing to consider when creating solutions to a problem.


It’s a little tough for me to explain though. For me, understanding space, and space transformations, meant acquiring an intuition for how space is presented rather than understanding its definition at a theoretical level. If you’re interested in material that promotes intuition for space while still explaining the theory behind it, I can recommend 3Blue1Brown’s video on Linear transformations and matrices.

1 Like

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