How do you make enemies chase the player in Godot 3D?

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

I cannot for the life of me figure out how to get enemies to chase the player. I’ve looked at multiple tutorials but nothing is working.
Here is the script for the enemy (its really messy because I’ve been messing around trying to figure this out for a while now):

extends KinematicBody
 
export var speed = 3
 
onready var raycast = $RayCast
onready var anim_player = $AnimationPlayer
onready var collisionshape = $Area2/CollisionShape
onready var area = $Area/CollisionPolygon
 
var player = null
var dead = false
var target
var space_state

func _ready():
	anim_player.play("walk")
	add_to_group("Zombies")
	space_state = get_world().direct_space_state
	
func _physics_process(delta):
	if dead:
		return
	if player == null:
		return

	if target:
		var result = space_state.intersect_ray(global_transform.origin, target.global_transform.origin)
		if result.collider.is_in_group("Player"):
			look_at(target.global_transform.origin, Vector3.UP)
			move_to_target(delta)
   
	var vec_to_player = player.translation - translation
	vec_to_player = vec_to_player.normalized()
	raycast.cast_to = vec_to_player * 1.5
   
	move_and_collide(vec_to_player * speed * delta)
   
	if raycast.is_colliding():
		var coll = raycast.get_collider()
		if coll != null and coll.name == "Player":
			coll.kill()
   
 
func kill():
	dead = true
	$CollisionShape.disabled = true
	collisionshape.disabled = true
	anim_player.play("die")
	area.disabled = true
 
func set_player(p):
	player = p


func _on_Area_body_entered(body):
	if body.is_in_group("Player"):
		target = body
		print(body.name + " entered")


func _on_Area_body_exited(body):
	if body.is_in_group("Player"):
		target = null
		print(body.name + " exited")
	
func move_to_target(delta):
	var direction = (target.transform.origin - transform.origin).normalized()
	move_and_slide(direction * speed * delta, Vector3.UP)


func _on_Area2_body_entered(body):
	if body.is_in_group("Player"):
		print(body.name + " was killed")
		target = body
		get_tree().reload_current_scene()

I’m really not sure what the problem is so I’ll include the Player script here too for reference:

extends KinematicBody

export var speed = 10
export var acceleration = 5
export var gravity = 1.38
export var jump_power = 34
export var mouse_sensitivity = 0.1

onready var head = $Head
onready var camera = $Head/Camera
onready var anim_player = $Head/Camera/AnimationPlayer
onready var raycast = $Head/Camera/RayCast

var velocity = Vector3()
var camera_x_rotation = 0

func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	yield(get_tree(), "idle_frame")
	get_tree().call_group("zombies", "set_player", self)
	add_to_group("Player")

func _input(event):
	if event is InputEventMouseMotion:
		head.rotate_y(deg2rad(-event.relative.x * mouse_sensitivity))

		var x_delta = event.relative.y * mouse_sensitivity
		if camera_x_rotation + x_delta > -90 and camera_x_rotation + x_delta < 90: 
			camera.rotate_x(deg2rad(-x_delta))
			camera_x_rotation += x_delta

func _process(delta):
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)

func _physics_process(delta):
	var head_basis = head.get_global_transform().basis
	if Input.is_action_pressed("exit"):
		get_tree().quit()
	if Input.is_action_pressed("restart"):
		kill()

	var direction = Vector3()
	if Input.is_action_pressed("move_forward"):
		direction -= head_basis.z
	elif Input.is_action_pressed("move_back"):
		direction += head_basis.z
	
	if Input.is_action_pressed("strafe_left"):
		direction -= head_basis.x
	elif Input.is_action_pressed("strafe_right"):
		direction += head_basis.x
	
	if Input.is_action_pressed("primary fire") and !anim_player.is_playing():
		anim_player.play("shoot")
		var coll = raycast.get_collider()
		if raycast.is_colliding() and coll.has_method("kill"):
			coll.kill()
	
	direction = direction.normalized()
	
	velocity = velocity.linear_interpolate(direction * speed, acceleration * delta)
	velocity.y -= gravity
	
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y += jump_power
	
	velocity = move_and_slide(velocity, Vector3.UP)

func kill():
	get_tree().reload_current_scene()

Can anyone help me out here? I’ve been struggling with this for weeks now. I would greatly appreciate help.

The problem here is that enemies move away from the target instead of toward it.

thebraveknight999 | 2020-05-11 00:11

:bust_in_silhouette: Reply From: Svalokai

I’m pretty new to 3d programming myself, so I could be wrong about this, but I’m pretty sure that the problem is that you are mixing local and global coordinates in your movement logic. As far as I know, move_and_collide() and move_and_slide() expect the velocity vector to be in world space, but the transform.origin and the .translation properties that you are using to get your directional vectors for those functions return the node’s local transform.

If you want to test to see if this is the issue, try adding a debug print statement to those calculations, something like:

var vec_to_player = player.translation - translation
vec_to_player = vec_to_player.normalized()
print("vec_to_player directional vector is: ", vec_to_player)
var g_vec_to_player = player.get_global_transform().origin - get_global_transform().origin
g_vec_to_player = g_vec_to_player.normalized()
print("g_vec_to_player directional vector is: ", g_vec_to_player)
raycast.cast_to = vec_to_player * 1.5

move_and_collide(vec_to_player * speed * delta)

Comparing the two vectors that print to the debug output should tell you if that is the issue or not.

This is how I usually debug vector calculations, but there is probably a better way, so if anyone else knows a better method, feel free to correct me.

Other than that, I don’t see anything wrong with the logic or your math either, so I hope this helps you solve the problem.

Cheers.

:bust_in_silhouette: Reply From: jasperbrooks79

IF distance to player is LESS than react_distance, calculate vector from enemy to player and, add that to ’ enemy location ', every frame . . .

You can use look_at to make the enemy face the player, and then get the rotation, after that has been calculated, to get the vector <3

:bust_in_silhouette: Reply From: jasperbrooks79

Imo, first thing you need to do, is add the look_at function / method, then try printing rotation, after that, you can use degrees to radians ( deg2rad in visual script ) and, then use sin and, cos ( cosine ), to calculate the vector, towards the player, if you have that, just make the enemy move ’ that ’ direction, every frame . . . Made it work, a while ago, coding a look_at function from scratch can be done, but it’s difficult, because the engine goes from -180, to 180 degrees, I think, so you have to write some nasty code, to take that into account . . . I used look_at, that was enough, it’s implemented code, but you can also code everything, yourself . . . Good luck, it took me a few days, to figure it out, really nasty problem, used look_at in the end, got lost, it worked, somehow . . . :open_mouth: :open_mouth: <3

Almost had it, but in the end, it was easier to used a pre-made function, that worked, but bit noob, still, stuff works . . . . Good luck, you’ll figure it out, easier if you debug, by printing variables, like rotation, so you can see what happens, as well . . .

:bust_in_silhouette: Reply From: Yuminous

I do it like this: everything is contained within _physics_process, subject to an if expression so the behaviour can be switched on or off.

var player = $PlayerNode
var enemy = $EnemyNode
var enemy_following = true
var gravity_direction_and_speed = -10
var up_vector3 = Vector3(0, 1, 0)

func _physics_process(delta):
    if enemy_following == true:
        enemy.move_and_slide(Vector3(min(player.translation.x - enemy.translation.x, enemy_max_speed), gravity_direction_and_speed, min(player.translation.z - enemy.translation.z, enemy_max_speed), up_vector3)

For the most basic ‘follow’ command, that’s all you need.

In an even more rudimentary style, you could technically go with move_and_slide(player.translation - enemy.translation, up_vector3) without bothering about breaking down a Vector3, but it would mean that your enemies will fly directly toward the player (unless you want that), and if you don’t include an enemy_max_speed then you’ll be creating a horrifically fast enemy. Alternatively you can also use [translation] * delta * speed_multiplier if you don’t trust _physics_process to do its one and only job, but you still have to include a max speed for the enemy because the raw equation is comparing the differences in global translation between the player and the enemy, so its speed is much higher the further they are apart. Naturally, this means the enemy slows right down when they are close to the player, but I find this to actually aid in better design since it gives the player time to react. But if you don’t want this as a mechanic then clamp the value with both a min and a max speed.

move_and_slide by itself is a good enough system that enemies can eventually navigate around any terrain, so long as the player keeps moving and the enemy isn’t trapped. So that’s all you need for a simple follow, but emphasis on the “simple” part. You’ll need to wrack your brains quite a bit and come up with a novel solution if you want smart pathfinding / “AI” movements.

:bust_in_silhouette: Reply From: phenomkill2

I fixed this by making enemy fly towards us.
then i got a problem because idk how to increase or decrease speed:

extends KinematicBody

var health = 100
var target
var velocity = Vector3()
var UP = Vector3.UP
onready var body_turn = $CollisionShape
onready var player = $“…/Player” as KinematicBody

func _process(delta):

if health <= 0:
	queue_free()

func _ready():
target = player
func _physics_process(delta):
velocity = move_and_slide_with_snap(target.global_transform.origin - body_turn.global_transform.origin, UP)
body_turn.look_at(target.global_transform.origin, UP)