Simulating Boids

Boids are used to simulate bird flocks, animal herds, and fish schools & is an example of swarm intelligence. Swarm intelligence refers to the collective behavior of individual agents who interact locally with one another and their environment to produce global patterns or behaviors. Individual members exhibit simple behaviors, but together they create complex movements or patterns that benefit the group as a whole, such as predator evasion, foraging efficiency, or navigation.

In my birds game, I did not use swarm behavior in birds motion, which resulted in unrealistic birds behavior. Using models such as boids, bird flocks could have been simulated much better.

Modelling Boids

Flocking behavior can be modelled with some basic rules (paper):

  1. Separation: Each boid tries to maintain a minimum distance from other nearby boids to avoid collisions. It moves away from other boids that are too close. This avoids clumping.
  2. Alignment: Each boid adjusts its velocity to match that of its neighbors. It tries to align its direction with the average direction of nearby boids.
  3. Cohesion: Each boid moves towards the average position of its neighbors. It tries to stay close to other boids, thus maintaining the cohesion of the flock.

Improvements

The above mentioned algorithm is very basic. To improve it, we can take into account things such as collision avoidance with objects, path finding (towards food sources or bird migrations) & so on. All of it depends on the kind of game in which it is being implemented.

Boids Implementation Python

import pygame
import random
import math

# Constants
WIDTH, HEIGHT = 512, 512
NUM_FISH = 50
MAX_SPEED = 2
NEIGHBOR_RADIUS = 50
SEPARATION_RADIUS = 30
SEPARATION_FORCE = 0.8
ALIGNMENT_FORCE = 0.1
COHESION_FORCE = 0.05
SCREEN_CENTER = (WIDTH // 2, HEIGHT // 2)
BG_COLOR = (15, 10, 15)
FISH_COLOR = (255, 100, 200)


class Fish:
    def __init__(self, x, y, vx, vy):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy

    def update(self, flock):
        # Rule 1: Separation
        separation = self.separate(flock)
        # Rule 2: Alignment
        alignment = self.align(flock)
        # Rule 3: Cohesion
        cohesion = self.cohere(flock)

        # Update velocity
        self.vx += separation[0] * SEPARATION_FORCE + alignment[0] * ALIGNMENT_FORCE + cohesion[0] * COHESION_FORCE
        self.vy += separation[1] * SEPARATION_FORCE + alignment[1] * ALIGNMENT_FORCE + cohesion[1] * COHESION_FORCE

        # Limit speed
        speed = math.sqrt(self.vx ** 2 + self.vy ** 2)
        if speed > MAX_SPEED:
            scale_factor = MAX_SPEED / speed
            self.vx *= scale_factor
            self.vy *= scale_factor

        # Update position
        self.x += self.vx
        self.y += self.vy

        # Wrap around screen
        if self.x < 0:
            self.x = WIDTH
        elif self.x > WIDTH:
            self.x = 0
        if self.y < 0:
            self.y = HEIGHT
        elif self.y > HEIGHT:
            self.y = 0

    def separate(self, flock):
        separation_vector = [0, 0]
        for other_fish in flock:
            if other_fish != self:
                distance = math.sqrt((self.x - other_fish.x) ** 2 + (self.y - other_fish.y) ** 2)
                if distance == 0: # To avoid division by zero
                    distance = 0.001
                if distance < SEPARATION_RADIUS:
                    separation_vector[0] += (self.x - other_fish.x) / distance
                    separation_vector[1] += (self.y - other_fish.y) / distance
        return separation_vector

    def align(self, flock):
        avg_velocity = [0, 0]
        num_neighbors = 0
        for other_fish in flock:
            if other_fish != self:
                distance = math.sqrt((self.x - other_fish.x) ** 2 + (self.y - other_fish.y) ** 2)
                if distance < NEIGHBOR_RADIUS:
                    avg_velocity[0] += other_fish.vx
                    avg_velocity[1] += other_fish.vy
                    num_neighbors += 1
        if num_neighbors > 0:
            avg_velocity[0] /= num_neighbors
            avg_velocity[1] /= num_neighbors
        return avg_velocity

    def cohere(self, flock):
        center_of_mass = [0, 0]
        num_neighbors = 0
        for other_fish in flock:
            if other_fish != self:
                distance = math.sqrt((self.x - other_fish.x) ** 2 + (self.y - other_fish.y) ** 2)
                if distance < NEIGHBOR_RADIUS:
                    center_of_mass[0] += other_fish.x
                    center_of_mass[1] += other_fish.y
                    num_neighbors += 1
        if num_neighbors > 0:
            center_of_mass[0] /= num_neighbors
            center_of_mass[1] /= num_neighbors
            return [center_of_mass[0] - self.x, center_of_mass[1] - self.y]
        else:
            return [0, 0]

    def draw(self, screen):
        # Draw fish as an arrow shape
        angle = math.atan2(self.vy, self.vx)
        pygame.draw.polygon(screen, FISH_COLOR, [(self.x + math.cos(angle) * 10, self.y + math.sin(angle) * 10),
                                                  (self.x + math.cos(angle + 5 * math.pi / 6) * 10, self.y + math.sin(angle + 5 * math.pi / 6) * 10),
                                                  (self.x + math.cos(angle - 5 * math.pi / 6) * 10, self.y + math.sin(angle - 5 * math.pi / 6) * 10)])
        

def main():
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Boids Simulation")
    clock = pygame.time.Clock()

    # Create fish
    fish = [Fish(random.randint(0, WIDTH), random.randint(0, HEIGHT), random.uniform(-1, 1), random.uniform(-1, 1))
            for _ in range(NUM_FISH)]

    # Main loop
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # Update fish
        for fishy in fish:
            fishy.update(fish)

        # Draw
        screen.fill(BG_COLOR)
        for fishy in fish:
            fishy.draw(screen)

        pygame.display.flip()
        clock.tick(60)

    pygame.quit()


if __name__ == "__main__":
    main()

More Links

  1. There are some optimization algorithms based on how ants, bees or other species interact to find shortest distances (or lower-cost solutions to a problem). These algorithms are ant colony optimization for example.
  2. External links for boids: https://cs.stanford.edu/people/eroberts/courses/soco/projects/2008-09/modeling-natural-systems/boids.html
  3. Boids by Craig Reynolds: https://www.red3d.com/cwr/boids/