Forces from within — vehicles that seek, flee, arrive, wander, and flock. Craig Reynolds's steering behaviors bring our shapes to life.
In Chapters 1-5, we built objects that are pushed around by external forces — gravity, wind, springs. They're inanimate. They sit there and wait for something to happen to them.
What if a shape could want something? What if it could perceive its environment and decide to move on its own? This is the leap from physics to artificial life. We're going to design autonomous agents.
Three key properties of autonomous agents:
| Property | What it means |
|---|---|
| Limited perception | The agent can sense its environment, but only partially. An insect knows its immediate surroundings, not the entire world. |
| Processes and acts | The agent takes in information and calculates an action — a force. "There's a shark coming; I'll flee." |
| No leader | No centralized command. Complex group behavior emerges from local interactions. |
In the late 1980s, computer scientist Craig Reynolds developed algorithmic steering behaviors for animated characters: seek, flee, wander, arrive, pursue, evade, flock. These behaviors let individual shapes navigate their environment in a lifelike manner.
Reynolds was inspired by Valentino Braitenberg's 1986 book Vehicles: Experiments in Synthetic Psychology, which argued that extraordinarily simple mechanical vehicles can manifest behaviors like fear, aggression, and love.
Reynolds describes vehicle motion in three layers:
| Layer | What it does | Our focus |
|---|---|---|
| Action Selection | Choose a goal: "I see a zombie, I want to flee." | We design these goals. |
| Steering | Calculate a force to achieve the goal. | Reynolds's formula: steering = desired - velocity |
| Locomotion | The physical animation of movement. | Mostly ignored — our shapes just glide. |
maxspeed and maxforce to control its steering ability.A vehicle is moving with some velocity and wants to reach a target. We could use gravitational attraction (Chapter 2), but that ignores the vehicle's current state. Steering is smarter: the vehicle knows where it wants to go and how it's currently moving.
The desired velocity is a vector pointing from the vehicle's location to the target, scaled to maximum speed:
pseudocode desired = target - location desired.normalize() desired.mult(maxspeed) # Reynolds's steering formula steer = desired - velocity steer.limit(maxforce) applyForce(steer)
Two variables control the feel: maxspeed determines how fast the vehicle can travel. maxforce limits its steering ability. A high maxforce means tight, race-car turns. A low maxforce means slow, lumbering adjustments.
maxforce matter?Seeking works great, but the vehicle flies past the target and has to double back. Why? Because it always wants to go at maximum speed, even when it's inches away.
The arrive behavior adds a slowdown zone. Imagine a circle around the target. Outside the circle: full speed. Inside the circle: speed is proportional to distance.
pseudocode desired = target - location d = desired.mag() desired.normalize() if d < slowRadius: speed = map(d, 0, slowRadius, 0, maxspeed) else: speed = maxspeed desired.mult(speed) steer = desired - velocity steer.limit(maxforce)
A flow field is a grid where each cell contains a vector. As a vehicle moves, it asks: "What vector is beneath me? That's my desired velocity!"
The flow field is stored as a 2D array of vectors. The vehicle looks up its cell based on its position, then uses that vector as the desired direction.
pseudocode # Flow field: 2D array of vectors field[cols][rows] # Fill with Perlin noise angles for i in cols: for j in rows: angle = map(noise(i*0.1, j*0.1), 0, 1, 0, TWO_PI) field[i][j] = vectorFromAngle(angle) # Vehicle lookup col = vehicle.x / resolution row = vehicle.y / resolution desired = field[col][row]
In 1986, Craig Reynolds created a simulation of flocking behavior with three simple rules. Recreating this brings together everything in this chapter: steering forces, group behaviors, and emergent complexity.
| Rule | Description | Desired velocity |
|---|---|---|
| Separation | Steer to avoid crowding neighbors | Average of vectors pointing away from nearby boids |
| Alignment | Steer in the same direction as neighbors | Average velocity of nearby boids |
| Cohesion | Steer toward the center of nearby boids | Average location of nearby boids (then seek it) |
Each rule produces a steering force. The forces are weighted and combined:
pseudocode sep = separate(boids) ali = align(boids) coh = cohesion(boids) sep.mult(1.5) # Separation matters most ali.mult(1.0) coh.mult(1.0) applyForce(sep) applyForce(ali) applyForce(coh)
Separation is "flee from anyone who's too close." For each boid, we look at every other boid. If one is within a desired separation distance, we compute a vector pointing away from it. We average all those vectors and use the result as our desired velocity.
pseudocode function separate(boids): sum = (0, 0) count = 0 for other in boids: d = dist(me, other) if d > 0 and d < desiredSeparation: diff = me.location - other.location diff.normalize() diff /= d # Weight by distance: closer = stronger sum += diff count += 1 if count > 0: sum /= count sum.setMag(maxspeed) # Desired velocity steer = sum - velocity # Reynolds's formula steer.limit(maxforce) return steer
Alignment: A boid's desired velocity is the average velocity of its nearby neighbors. "Everyone around me is heading northeast, so I want to head northeast too."
Cohesion: A boid's desired velocity points toward the average location of its nearby neighbors. "Everyone is over there — I should join them." This is essentially a seek toward the center of mass.
A crucial implementation detail: both alignment and cohesion must skip the boid itself (distance > 0) and only count neighbors within a threshold distance.
pseudocode function align(boids): sum = (0, 0) count = 0 for other in boids: d = dist(me, other) if d > 0 and d < neighborDist: sum += other.velocity count += 1 if count > 0: sum /= count sum.setMag(maxspeed) steer = sum - velocity steer.limit(maxforce) return steer
Below is a flocking simulation. Triangular boids follow the three rules — separation, alignment, and cohesion — and wrap around the edges. Click or tap to add more boids.
Each boid steers based only on its nearby neighbors. Click/tap to add boids. Use the sliders to adjust behavior weights.
| Topic | Key Takeaway |
|---|---|
| Autonomous agents | Objects with internal forces, limited perception, no leader |
| Steering formula | steering = desired velocity - current velocity |
| Seek & Arrive | Seek always goes at max speed; Arrive slows down near the target |
| Flow fields | A grid of vectors; the vehicle follows the vector at its location |
| Separation | Average of vectors pointing away from close neighbors |
| Alignment | Average velocity of nearby neighbors |
| Cohesion | Seek the average position of nearby neighbors |
| Flocking | All three combined → emergent, lifelike group behavior |
What we covered:
• Craig Reynolds's steering behaviors
• Seek, arrive, flow fields
• Separation, alignment, cohesion
• Flocking as emergent complexity
• Weighting and combining forces
What comes next:
Chapter 7: Cellular Automata. We leave motion behind and explore systems of simple cells on a grid. Wolfram's elementary CA and Conway's Game of Life show how complexity emerges from the simplest possible rules.