Lesson 05

Enemy Follows Player

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.

Perspective

HOW CHASING WORKS

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.

New code in this lesson

None of the earlier lessons use these. Each one shows the line where it appears here.

null

Means "nothing here". A variable that is not holding a node yet holds null.

enemy.gd
if _target == null:

distance_to()

How far apart two spots are, in pixels.

enemy.gd
var distance: float = global_position.distance_to(_target.global_position)

normalized()

The same direction, but shrunk or stretched to a length of exactly 1, so only the way it points is left.

enemy.gd
var direction: Vector2 = (_target.global_position - global_position).normalized()

else

Runs the lines under it when the if above it was false.

enemy.gd
else:

Vector2.ZERO

The arrow (0, 0): no movement at all.

enemy.gd
velocity = Vector2.ZERO

The scenes

■ UI node · ■ 2D node · % unique name · .tscn instanced scene

game.tscn
  • Game Node2D
  • Player CharacterBody2D player.tscn
  • Enemy1 CharacterBody2D enemy.tscn
  • Enemy2 CharacterBody2D enemy.tscn
enemy.tscn
  • Enemy CharacterBody2D enemy.gd
  • Polygon2D
  • CollisionShape2D
player.tscn
  • Player CharacterBody2D player.gd
  • Sprite2D
  • CollisionShape2D

FileSystem

  • game.tscn
  • enemy.tscn
  • enemy.gd
  • player.tscn
  • player.gd

The code

Perspective
class_name Enemy
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 enemy moves toward the player.
@export var speed: float = 120.0
## How close the enemy gets before it stops. Without this it would shove
## the player around forever.
@export var stop_distance: float = 34.0

# Who to chase. ".." means "go up to whoever holds me", which is the level, so
# this reads: from the level above me, grab the player sitting beside me.
@onready var _target: CharacterBody2D = $"../Player"

# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(_delta: float) -> void:
	# If the player is missing, there is 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()