Subtract two positions to get an arrow pointing at the player, then walk along it. That is the whole trick.
Loading the game engine. This takes a moment the first time.
The enemy has no code in this file. Everything it does lives in chaser_enemy.gd. This file just holds the scene together.
Take the target's position and subtract your own. That gives you an arrow pointing at them. Shrink the arrow to length 1, multiply it by your speed, and you have a velocity. It is the same math for anything that homes in: a chasing enemy, a guided missile, a pet that follows.
■ UI node · ■ 2D node · % unique name · .tscn instanced scene
extends Node2Dextends CharacterBody2D
## NOTE: "motion_mode" is set to "Floating" in the scene file (.tscn).
## The default "Grounded" mode is built for side-scrollers. It treats a bump
## from above as "the floor", which makes things stick in a top-down game.
## "Floating" treats every direction the same.
## How fast the enemy moves toward the player.
@export var speed: float = 120.0
## Which node to chase. You pick this in Godot by dragging the player in.
@export var target_path: NodePath
# Holds the real player node once we look it up. Starts out empty.
var _target: Node2D
func _ready() -> void:
# "target_path" is only an address. "get_node_or_null()" goes and fetches
# the actual node. The "or_null" part means: if nothing is at that
# address, put nothing in "_target" instead of crashing the game.
_target = get_node_or_null(target_path)
## How close the enemy gets before it stops. Without this it would shove
## the player around forever.
@export var stop_distance: float = 34.0
# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(_delta: float) -> void:
# No target means nothing to chase. "return" leaves the function early,
# so none of the lines below it run this time around.
if _target == null:
return
# "distance_to()" measures the straight line between two points.
var distance: float = global_position.distance_to(_target.global_position)
if distance > stop_distance:
# Subtracting two positions gives an arrow pointing from us to them.
# ".normalized()" shrinks that arrow to length 1, so multiplying by
# "speed" decides exactly how fast we go.
var direction: Vector2 = (_target.global_position - global_position).normalized()
velocity = direction * speed
else:
# Close enough. "Vector2.ZERO" is an arrow of length 0, which means
# do not move at all.
velocity = Vector2.ZERO
move_and_slide()class_name ChasePlayer
extends CharacterBody2D
## NOTE: "motion_mode" is set to "Floating" in the scene file (.tscn).
## The default "Grounded" mode is built for side-scrollers. It treats a bump
## from above as "the floor", which makes things stick in a top-down game.
## "Floating" treats every direction the same.
## How fast the player moves. "@export" puts "speed" at the top of the
## Inspector panel, so you can change it there instead of in this file.
@export var speed: float = 200.0
# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(_delta: float) -> void:
# "Input.get_vector(...)" checks all four keys at once and hands back
# an arrow pointing where to go. The four names in quotes are ours.
# See them under Project > Project Settings > Input Map.
var direction: Vector2 = Input.get_vector(
"move_left", "move_right", "move_up", "move_down"
)
# "direction" is only WHICH WAY (its length is always 1).
# "direction * speed" stretches it, so "velocity" is also HOW FAST.
velocity = direction * speed
# Nothing moves until "move_and_slide()" runs.
# "Slide" means we slide along walls instead of sticking to them.
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if direction.x != 0.0:
$Sprite2D.flip_h = direction.x < 0.0