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.
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.
None of the earlier lessons use these. Each one shows the line where it appears here.
Means "nothing here". A variable that is not holding a node yet holds null.
if _target == null:How far apart two spots are, in pixels.
var distance: float = global_position.distance_to(_target.global_position)The same direction, but shrunk or stretched to a length of exactly 1, so only the way it points is left.
var direction: Vector2 = (_target.global_position - global_position).normalized()Runs the lines under it when the if above it was false.
else:The arrow (0, 0): no movement at all.
velocity = Vector2.ZERO■ UI node · ■ 2D node · % unique name · .tscn instanced scene
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()extends CharacterBody2D
## How fast the enemy walks 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
## How hard the enemy jumps. Negative is up. Same as the player's.
@export var jump_velocity: float = -620.0
## How hard gravity pulls down. Same as the player's.
@export var gravity: float = 1800.0
## How much higher the player must be before the enemy bothers to jump.
@export var jump_height_needed: float = 40.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"
# The ray that looks for something to land on.
@onready var platform_check: RayCast2D = $PlatformCheck
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
if _target == null:
move_and_slide()
return
# Arrow from us to the player. Negative y means the player is ABOVE us.
var to_player: Vector2 = _target.global_position - global_position
# Walk toward the player, but only left and right. "stop_distance" only
# counts when we are on the same level: if the player is up on the
# platform, keep going until we are right under them, or we would jump
# too early and miss the edge.
var same_level: bool = absf(to_player.y) < jump_height_needed
if absf(to_player.x) > stop_distance or not same_level:
velocity.x = signf(to_player.x) * speed
else:
velocity.x = 0.0
if is_on_floor():
if to_player.y < -jump_height_needed and platform_check.is_colliding():
velocity.y = jump_velocity
elif to_player.y > jump_height_needed:
position.y += 2.0
move_and_slide()class_name Player
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
@onready var sprite: Sprite2D = $Sprite2D
# "_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 turns them
# into one arrow pointing where you want to go. The names in quotes are
# Godot's built-in names for the arrow keys.
var direction: Vector2 = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_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:
sprite.flip_h = direction.x < 0.0extends CharacterBody2D
## How fast the player runs left and right.
@export var speed: float = 200.0
## How hard the player jumps. Negative is up.
@export var jump_velocity: float = -620.0
## How hard gravity pulls down.
@export var gravity: float = 1800.0
@onready var sprite: Sprite2D = $Sprite2D
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y += gravity * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = jump_velocity
velocity.x = Input.get_axis("move_left", "move_right") * speed
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if velocity.x != 0.0:
sprite.flip_h = velocity.x < 0.0