An enemy that walks a fixed route, switching to the next spot on its list each time it arrives.
Loading the game engine. This takes a moment the first time.
The enemies have no code in this file. Everything they do lives in patrol_enemy.gd. This file just holds the scene together.
The enemy keeps a numbered list of spots. It walks toward spot 0, and once it gets close it switches to spot 1, then spot 2, then back to 0. Wrapping back around to 0 is what makes it loop forever.
■ UI node · ■ 2D node · % unique name · .tscn instanced scene
extends Node2Dextends CharacterBody2D
## How fast the enemy walks.
@export var speed: float = 100.0
## The list of spots to walk between, measured from where the enemy starts.
## Vector2(-100, 0) means "100 pixels left of my start spot", and
## Vector2(100, 0) means "100 pixels right of my start spot".
@export var patrol_points: PackedVector2Array = PackedVector2Array([
Vector2(-100, 0), Vector2(100, 0)
])
# Which slot of the list we are walking toward. Lists start at 0.
var _current_index: int = 0
# Where this enemy was standing when the level began.
var _origin: Vector2
func _ready() -> void:
# Remember our starting spot. Every patrol point is measured from here.
_origin = global_position
# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(_delta: float) -> void:
# An empty list means there is nowhere to walk. "return" leaves early.
if patrol_points.is_empty():
return
# Add the patrol point onto our starting spot to get the real target.
var target_pos: Vector2 = _origin + patrol_points[_current_index]
# An arrow pointing from us to that spot, shrunk down to length 1.
var direction: Vector2 = (target_pos - global_position).normalized()
velocity = direction * speed
move_and_slide()
# Are we basically there? 5 pixels counts as close enough. We cannot
# check for exactly 0, because we move in jumps and would sail past it.
if global_position.distance_to(target_pos) < 5.0:
# Switch to the next slot in the list.
# The "%" wraps back around to 0 when we run off the end, so the
# enemy loops forever instead of crashing. With 2 spots it counts
# 0, 1, 0, 1, 0, 1 and never stops.
_current_index = (_current_index + 1) % patrol_points.size()class_name PatrolPlayer
extends CharacterBody2D
## 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.0extends PatrolPlayer
## 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
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
# "speed" is inherited from PatrolPlayer.
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:
$Sprite2D.flip_h = velocity.x < 0.0