Concept
Here I tried to simulate flocking behavior by creating a “school” of boids that move together in a coordinated, natural pattern. Each boid follows three core rules—separation, alignment, and cohesion—to avoid crowding, match direction, and stay close to neighbors. Together, these rules give rise to a lifelike group movement, where each boid is influenced by its surroundings to form a cohesive, dynamic flock.
Code Breakdown
Each Boid has properties for position, velocity, and acceleration, and the draw loop continuously applies flocking behaviors, updates the boid’s position, and renders it on the canvas with a trailing effect for smooth movement. The flock method combines the three behaviors by calculating forces based on nearby boids within defined distances. The separation force keeps boids from colliding, alignment adjusts direction to match neighboring boids, and cohesion steers toward the group center. The wrapEdgesmethod ensures that boids reappear on the opposite side if they move off-screen. An interactive feature, mouseDragged, adds new boids at the mouse location when dragged, adding flexibility to the simulation.
let boids = []; // Array to store boid instances let w = 600, h = 600; function setup() { createCanvas(w, h); background(0); // Initialize boids with random positions for (let i = 0; i < 50; i++) { boids.push(new Boid(random(width), random(height))); } } function draw() { background(255, 5); // Slight trail effect boids.forEach(boid => { boid.flock(boids); // Apply flocking behavior boid.update(); // Update position and velocity boid.wrapEdges(); // Wrap around edges of the canvas boid.show(); // Draw the boid }); } // Constants for boid behavior let M = 2; // Max speed let F = 0.28; // Max force let sepDist = 25; // Desired separation distance let aliDist = 50; // Alignment neighbor distance let cohDist = 150; // Cohesion neighbor distance // Class to represent a single boid class Boid { constructor(x, y) { this.position = createVector(x, y); this.velocity = createVector(random(-1, 1), random(-1, 1)); this.acceleration = createVector(0, 0); } // Method to apply flocking behavior flock(boids) { let separation = this.separate(boids); // Separation let alignment = this.align(boids); // Alignment let cohesion = this.cohesion(boids); // Cohesion // Adjust weights for the forces separation.mult(1.5); alignment.mult(0.99); cohesion.mult(0.99); // Apply forces to acceleration this.acceleration.add(separation); this.acceleration.add(alignment); this.acceleration.add(cohesion); } // Update position based on velocity and acceleration update() { this.velocity.add(this.acceleration); this.velocity.limit(M); // Limit speed this.position.add(this.velocity); this.acceleration.mult(0); // Reset acceleration } // Wrap boids around the screen edges wrapEdges() { this.position.x = (this.position.x + w) % w; this.position.y = (this.position.y + h) % h; } // Draw boid as a small circle show() { fill(255, 0, 0); ellipse(this.position.x, this.position.y, 10); } // Separation behavior: Avoid crowding neighbors separate(boids) { let steer = createVector(0, 0); let count = 0; boids.forEach(other => { let distance = p5.Vector.dist(this.position, other.position); if (distance > 0 && distance < sepDist) { let diff = p5.Vector.sub(this.position, other.position); diff.normalize(); diff.div(distance); steer.add(diff); count++; } }); if (count > 0) steer.div(count); if (steer.mag() > 0) { steer.normalize(); steer.mult(M); steer.sub(this.velocity); steer.limit(F); } return steer; } // Alignment behavior: Steer towards the average heading of local flockmates align(boids) { let sum = createVector(0, 0); let count = 0; boids.forEach(other => { let distance = p5.Vector.dist(this.position, other.position); if (distance > 0 && distance < aliDist) { sum.add(other.velocity); count++; } }); if (count > 0) { sum.div(count); sum.normalize(); sum.mult(M); let steer = p5.Vector.sub(sum, this.velocity); steer.limit(F); return steer; } return createVector(0, 0); } // Cohesion behavior: Steer towards the average position of local flockmates cohesion(boids) { let sum = createVector(0, 0); let count = 0; boids.forEach(other => { let distance = p5.Vector.dist(this.position, other.position); if (distance > 0 && distance < cohDist) { sum.add(other.position); count++; } }); if (count > 0) { sum.div(count); return this.seek(sum); } return createVector(0, 0); } // Seek method to steer boid towards a target position seek(target) { let desired = p5.Vector.sub(target, this.position); desired.normalize(); desired.mult(M); let steer = p5.Vector.sub(desired, this.velocity); steer.limit(F); return steer; } } // Function to add a new boid on mouse drag function mouseDragged() { boids.push(new Boid(mouseX, mouseY)); }
Future Improvements
While the current simulation focuses on realistic flocking dynamics, future enhancements will center around visual and auditory immersion. Adding a colorful background and subtle sound effects could heighten the simulation’s atmosphere, making the school of boids feel more alive and enhancing the viewer’s experience.