4.1 — The Enemy: Patrol
The turret never moves. This enemy walks a beat with nobody at the keyboard, and it ships as a picture with an empty script: no collider, no body, and no memory of anything. Today it gets all three.
The enemy should walk a beat — back and forth, forever, with nobody at the keyboard. "Make the enemy patrol" is the whole feature, and at this moment the enemy is a picture with an empty script on it: no wall can stop it, and it has never moved once.
First, before any code: cut that into parts small enough that you could look each one up on its own.
- Give the enemy a bodyA Collider2D on the prefab root, sized by hand against art that lives on a child, plus a Rigidbody2D with Body Type Dynamic and Gravity Scale 0 — the same pair you gave the Player in 2.1. Until both exist, no wall can stop the enemy and no collision message will ever fire on it, which is exactly what 4.4 needs.Read: the
Collider2DandRigidbody2Drows and the Body Type warning in the quick reference, and the recipe you followed for the Player in 2.1. - write this partRead: the
[SerializeField] private Transformrow of this lesson's table, and the field that remembered a moment on the turret in 3.3. - Move it toward the end it is holdingEvery frame: subtract the two positions to get an arrow, normalize it so the enemy does not sprint when it is far away, multiply by a speed and hand the result to the body. Velocity is measured per second, so that line takes no Time.deltaTime.Read: the
(a - b).normalizedrow and therb.linearVelocity = vrow in the quick reference — the pair you used on the diagonal in 1.5 and on the Player in 2.2. - Notice it has arrived, and turn aroundThe distance check from 3.1, against a small threshold instead of a radius. When it is true, the field from part 2 is handed the other end of the beat — nothing about the enemy moves at that moment, only that field changes.Read: the
Vector3.Distance(a, b)row in the quick reference, and the range check you wrote on the turret in 3.1.
Three of those four parts are work you have already done: a collider and a Dynamic body is 2.1, a normalized direction and a velocity is 2.2, and a distance compared against a threshold is 3.1. Only one part has no precedent at all, and it is the smallest one: until today everything this game remembered was a number that changed, and today the memory is where you are going. That is the whole reason a beat can run with nobody at the keyboard: a player turns because a key was pressed, and the enemy has no keys.
The test: could you look this part up on its own, right now, in one search? "Make an enemy patrol" cannot be looked up. "Move an object toward a position at a constant speed" can — and you wrote that line in 2.2.
Your turn
The reading prices three ways to define a beat. Which did you choose, and what does your choice cost you — in your own words, not the table's?
A patrolling enemy has no keyboard. Name one thing the Player gets from Input that the enemy now has to keep in a field instead.
-
Hierarchy → count the
MobileEnemyinstances in the Overworld: four, against exactly one Coin. Write the count down before you move anything. -
Project window →
Assets/Prefabs/MobileEnemy, and select the prefab asset, not an instance in the scene. Its root carries a Transform, an emptyEnemyControllerand one child named Sprite — and no collider and no body at all. - Inspector → Add Component → Box Collider 2D, on the root. Then Scene view → drag the handles until the green outline covers the enemy you can actually see: the art is not on the root, it lives on the Sprite child, offset and scaled up, so a default collider on the root is a small square in the middle of a much bigger body.
- Inspector → Add Component → Rigidbody 2D, then set Body Type = Dynamic, Gravity Scale = 0 and Constraints → Freeze Rotation Z ☑ ON. Only a Dynamic body is stopped by the world, and only a frozen rotation keeps a shoved enemy from spinning.
- Game view → press Play with no patrol code at all, then walk into an enemy. It is solid now, and it still does not move on its own. Both halves of that are the point of today.
- Hierarchy → right-click → Create Empty, twice, in the Overworld. Rename them WaypointA and WaypointB, and place them with the Move tool in the Scene view at the two ends of the beat you want — the setup for the waypoint row. If you picked a different row in the table below, you need no scene objects at all.
-
Hierarchy → select each enemy instance, then Inspector
→ drag WaypointA and WaypointB into the two
slots on
EnemyController. An empty slot is a null reference: it throws on the first frame your code reads it, which looks like a patrol bug and is really a wiring bug.
Read the three rows below before you write anything. All three are the same four parts in different clothes, and only what the field remembers differs — a sign, a target, or a limit.
| The row | What it remembers, and how it turns | What it costs you |
|---|---|---|
| A leash from the spawn point | The position it started at, plus how far it may wander. It measures the drift with Vector3.Distance(a, b) and flips the stored sign once that exceeds the leash. |
No scene setup at all, and the beat follows the enemy wherever you drag it. But it is always a straight line centred on the spawn point, and the turn is noticed only after the enemy has moved — so it always overshoots by up to one frame of travel. |
| Two placed waypoints | Two Transforms wired into two Inspector slots. It walks toward the current one and hands the field the other when the distance falls under the arrived threshold. | You must create, place and wire two objects, and invent a threshold small enough to be accurate but large enough to be reachable — travel further in one frame than the threshold and the enemy sails straight past and never turns. |
| Two absolute bounds | Two numbers, a minimum and a maximum, and a sign the movement multiplies by. | The simplest maths and the most brittle. Those bounds are world coordinates, so moving the enemy leaves its beat behind and a copied prefab turns on frame one. Let the sign ever reach 0 and the walk stops for good. |
Socratic: Designing a Beat
- Which of the three beat definitions fits an enemy you will place four or five times across a map and keep nudging while you design the level? What does your choice cost you?
- The leash approach stores the position the enemy started at. Which lifecycle method must save it, and what would the enemy do if you saved it every frame instead?
- A single int holding
1or-1is the enemy's entire memory of where it is going. Why is that enough — and what extra state would a bool such asisGoingRightforce you to keep in sync?
The question this answers“how do I make an enemy follow a patrol route?”
You are editing Assets/Scripts/EnemyController.cs. The enemy ships as a stub: an empty EnemyController on a prefab you just gave a body to, standing in the Overworld four times over. Today it gets two ends to its beat and one field that remembers which end it is walking toward. The movement line is the one you chose in 2.2 — if you move your Player a different way, keep your method and read the shape.
This build leaves 1 line for you to write. Everything around it is given, so you always know the shape of what you are producing.
Step 1Declare the two ends of the beat and the numbers that tune it
Four lines in the class body, and every one of them is a line a designer can see in the Inspector — the [SerializeField] pattern from 1.3.
// EnemyController.cs — class body, above Start(), with your other fields.
[SerializeField] private Transform waypointA;
[SerializeField] private Transform waypointB;
[SerializeField] private float moveSpeed = 2f;
[SerializeField] private float arriveDistance = 0.1f;
private Rigidbody2D body;- 2One end of the beat. A
Transformholds a position in the world — and, wired from the Inspector, it holds a reference to another object, which is what a waypoint slot is.Slots, not typed-in coordinates. A waypoint is a place in the scene, so it belongs to the scene: you can drag either end while the game is running and watch the beat move. Two hard-coded x values would give the same straight line and break the moment you moved the prefab somewhere else. - 3The other end. Two slots rather than one, because a beat has two ends and the enemy has to be able to reach both.
- 4How fast it walks, in units per second — the same kind of knob, with the same name, as
moveSpeedon your Player. - 5How close counts as arrived — the number that decides when the beat turns around.It has to be small enough that the enemy looks like it reached the waypoint, and large enough that it can actually reach it. An enemy that travels further in one frame than this number walks straight past the waypoint and never arrives at all.
- 7A place to keep the reference to the enemy's own body, so the movement line in step 3 does not have to search for it every frame. You keep one of these on the Player for exactly the same reason.
Step 2Declare the memory, and give it a value before the first frame
Start() runs once, before Update() has ever run — you already use it in 2.2 for the Player's body lookup, and the enemy's stub already has the method waiting.
// EnemyController.cs — the memory field in the class body, the seed inside Start().
private Transform target;
void Start()
{
body = GetComponent<Rigidbody2D>();
target = waypointB;
}- 2The memory. One field holding which end of the beat the enemy is walking toward right now. It is the only thing in the file that changes when the enemy turns around.Notice that it has no value yet — the next lines give it one. And notice where it lives: in the class body, not inside
Update(). Move this line into a method and it is rebuilt from nothing sixty times a second, so the enemy can never remember which way it was going. - 5Finds the Rigidbody2D you added in the editor half and keeps the reference.
GetComponent<T>()is the lookup from 2.1, and calling it once inStart()instead of every frame is the rule from 2.2.Without this line the movement in step 3 has nothing to write to. The field would still be empty, and the Console fills with a NullReferenceException on the first frame, pointing at the line that uses the body. An empty reference is a valid value in C# — nothing complains until you use it. - 6Seeds the memory, so the enemy knows which way it is walking before the first frame runs. Either end works; starting toward B gives a full first beat.Leave this out and the first frame throws. A field with no value is
null, and the arrival check in step 3 asks that reference for its position. The memory needs a value before anything reads it, and the value has to come from somewhere that runs once.
Step 3Walk toward it every frame, and turn when you get there
Two jobs, and together they are the whole beat: go toward the end you are holding, and — when you have arrived — hold the other one.
// EnemyController.cs — inside Update().
void Update()
{
Vector2 direction = (target.position - transform.position).normalized;
body.linearVelocity = direction * moveSpeed;
if (Vector3.Distance(transform.position, target.position) < arriveDistance)
{
________________
}
}- 4The arrow from the enemy to the waypoint it is walking toward, at a length of exactly 1: subtract the two positions, then normalize — the pair you used on the diagonal in 1.5 and to aim a projectile in 3.2.Do not skip
.normalized. Without it the vector is as long as the distance to the waypoint, so the enemy sprints when it is far away and creeps when it is close — and on a long beat it covers the whole waypoint in a single frame and never arrives. A position is a Vector3 and a velocity is a Vector2; Unity converts between them by droppingz, which is harmless in a flat world. - 5Hands the body a velocity instead of moving the Transform yourself — the movement method you chose from the 2.2 table, and the only reason a wall can stop the enemy at all.No
Time.deltaTimeon this line. Velocity is measured in units per second, so the physics engine already applies the time for you. Multiply bydeltaTimeand the enemy's speed starts depending on your frame rate. - 7The arrival test: the distance check from 3.1, with the threshold you declared in step 1 instead of a radius. True on every frame the enemy is standing within
arriveDistanceof the waypoint it was walking toward. - Your turn9The turn. The enemy has arrived at the waypoint it was walking toward — hand the memory from step 2 the OTHER end of the beat. Nothing else about the enemy changes at this moment: not its position, not its speed, not the waypoints. An
if/elseyou already know is enough to write it.The field holds which end it is walking toward. Ask it which one it is holding right now — a Transform can be compared with==— and send it to the other. Both waypoints are in scope, and only one of them is the right answer on any given frame.Leave this line out and the enemy walks to the first waypoint and stands there forever. Nothing else in the file ends the journey: on the Player, letting go of the keys was the turn, and the enemy has no keys. And a line that always writes the same waypoint is only one beat — it arrives, sends itself back to the place it is already standing, and never walks again. The two ends have to alternate, which means the line has to ask what the field is holding.
Check your work: the finished filetry every gap first — then open this to compare
Compare against your own file rather than copying it. The gap is the turn, and it is the one line in this lesson with nothing earlier to copy: the field remembers which end of the beat the enemy is heading for, and the turn is that field changing its mind. This file writes it as a small method with an if/else, and turns before it walks where the walkthrough turns after — on every frame but the one where the enemy arrives, those two orders are the same file, and any form that hands the field the other waypoint is correct. Your waypoint placement, your speed, your threshold and your movement method from 2.2 are yours; the shape is what has to match.
// ------------------------------------------------------------------
// REFERENCE FILE — Assets/Scripts/EnemyController.cs
// Written to be read, not pasted. Build it with the walkthrough
// above, one step at a time, then open this and compare.
// If it is sitting in your project unchanged, that is the thing
// to go and do now — the walkthrough is still above you.
// ------------------------------------------------------------------
using UnityEngine;
// EnemyController.cs — one guard, one beat, forever.
// The two ends of the beat are objects in the scene. This script keeps exactly
// one thing about the walk: which end it is heading for. The turn is that
// field changing its mind.
public class EnemyController : MonoBehaviour
{
[SerializeField] private Transform waypointA; // one end of the beat
[SerializeField] private Transform waypointB; // the other end
[SerializeField] private float moveSpeed = 2f; // units per SECOND
[SerializeField] private float arriveDistance = 0.1f; // how close is "there"
private Transform target; // which way it is walking
private Rigidbody2D body;
void Start()
{
// Looked up once, never inside Update() — the rule from 2.2.
body = GetComponent<Rigidbody2D>();
// Seed the memory. Either end works; this one gives a full first beat.
target = waypointB;
}
void Update()
{
// Arrive first, walk second. The order only matters on the one frame
// where the enemy reaches a waypoint.
if (Vector3.Distance(transform.position, target.position) < arriveDistance)
{
TurnAround();
}
// No Time.deltaTime: velocity is per second, not per frame (2.2).
Vector2 direction = (target.position - transform.position).normalized;
body.linearVelocity = direction * moveSpeed;
}
// The only thing that ends a beat. The player lets go of the keys; the
// enemy has no keys, so the turn has to be written here.
private void TurnAround()
{
if (target == waypointA)
{
target = waypointB;
}
else
{
target = waypointA;
}
}
}
// Inspector, on Assets/Prefabs/MobileEnemy (this lesson's editor half):
// Collider 2D = on the ROOT, resized by hand until the
// outline covers the art on the Sprite child
// Rigidbody 2D ▸ Body Type = Dynamic <- only Dynamic is stopped by walls
// Rigidbody 2D ▸ Gravity Scale = 0 <- the Overworld has no down
// Rigidbody 2D ▸ Freeze Rotation Z = ON <- solid hits impart spin
// Waypoint A / Waypoint B = two empty GameObjects placed in the
// Overworld, wired per enemy INSTANCECheckpoint — before moving on, you must be able to:
- Say what the MobileEnemy prefab was missing, and why that matters beyond this lesson
- Compare the three ways to define a beat, name the one you chose, and defend it
- Explain why the memory gets its first value in
Start(), and what breaks if you move that line intoUpdate() - Point at the one field that is your enemy's whole memory of where it is going, and the single line that changes its mind
- Say how many enemies and how many coins the Overworld shipped with
- Commit and push
Reference Docs
Need a deeper explanation? These pages cover the same concepts:
- Unit 4: The Enemy — patrol, chase and the full state machine
- Enemy State Machines — the reference you will build against all unit
- Triggers vs Collisions — what a body buys you, reused from Unit 2
- Unity + C# Quick Reference — Transform, distance and Inspector fields
- How do I…? index — find this lesson again by the question you were asking
Next: 4.2 — Enemy Animation: A Different State Machine, where the enemy's animator turns out to follow none of your rules.
Saved on this device only — the Dashboard tracks your progress.