Advertisement
Implementation

The Three Simple Rules of Flocking Behaviors: Alignment, Cohesion, and Separation

by

In the natural world, organisms exhibit certain behaviors when traveling in groups. This phenomenon, also known as flocking, occurs at both microscopic scales (bacteria) and macroscopic scales (fish). Using computers, these patterns can be simulated by creating simple rules and combining them. This is known as emergent behavior, and can be used in games to simulate chaotic or life-like group movement.

Note: Although this tutorial is written using Flash and AS3, you should be able to use the same techniques and concepts in almost any game development environment.


Introduction

In this tutorial, I will cover the three main rules used to simulate flocking and explain how to implement each one.  Before we begin, here's some terminology I'll be using:

  • Agent: A single entity or character.
  • Velocity vector: An agent's current velocity.
  • Neighborhood: A certain area around the agent, used to look for other agents.
  • Resultant: The vector obtained from the calculations of the rule.

This demo shows the effects of the three flocking rules which I'll explain in this tutorial: alignment, cohesion, and separation.

The full source code for this demo can be downloaded here, so this article will only highlight the most important aspects of the implementation. Feel free to download the source if you wish to learn more.


Alignment


Image adapted from Craig Reynolds' article

Alignment is a behavior that causes a particular agent to line up with agents close by.

First, we'll make a function that takes an agent and returns a velocity vector.

public function computeAlignment(myAgent:Agent):Point
{
}

We'll need two variables: one for storing the vector we'll compute, and another for keeping track of the number of neighbors of the agent.

var v:Point = new Point();
var neighborCount = 0;

With our variables initialized, we now iterate through all of the agents and find the ones within the neighbor radius - that is, those close enough to be considered neighbors of the specified agent. If an agent is found within the radius, its velocity is added to the computation vector, and the neighbor count is incremented.

for each (var agent:Agent in agentArray)
{
	if (agent != myAgent)
	{
		if (myAgent.distanceFrom(agent) < 300)
		{
			v.x += agent.velocity.x;
			v.y += agent.velocity.y;
			neighborCount++;
		}

	}

}

If no neighbors were found, we simply return the zero vector (the default value of the computation vector).

if (neighborCount == 0)
	return v;

Finally, we divide the computation vector by the neighbor count and normalize it (divide it by its length to get a vector of length 1), obtaining the final resultant vector.

v.x /= neighborCount;
v.y /= neighborCount;
v.normalize(1);
return v;

Cohesion


Image adapted from Craig Reynolds' article

Cohesion is a behavior that causes agents to steer towards the "center of mass" - that is, the average position of the agents within a certain radius.

The implementation is almost identical to that of the alignment behavior, but there are some key differences. First, instead of adding the velocity to the computation vector, the position is added instead.

v.x += agent.x;
v.y += agent.y;

Like before, the computation vector is divided by the neighbor count, resulting in the position that corresponds to the center of mass. However, we don't want the center of mass itself, we want the direction towards the center of mass, so we recompute the vector as the distance from the agent to the center of mass. Finally, this value is normalized and returned.

v.x /= neighborCount;
v.y /= neighborCount;
v = new Point(v.x - myAgent.x, v.y - myAgent.y);
v.normalize(1);
return v;

Separation


Image adapted from Craig Reynolds' article

Separation is the behavior that causes an agent to steer away from all of its neighbors.

The implementation of separation is very similar to that of alignment and cohesion, so I'll only point out what is different. When a neighboring agent is found, the distance from the agent to the neighbor is added to the computation vector.

v.x += agent.x - myAgent.x;
v.y += agent.y - myAgent.y

The computation vector is divided by the corresponding neighbor count, but before normalizing, there is one more crucial step involved. The computed vector needs to be negated in order for the agent to steer away from its neighbors properly.

v.x *= -1;
v.y *= -1;

Putting It All Together

Once these three rules have been implemented, they need to come together. The simplest way to do this is as follows:

var alignment = computeAlignment(agent);
var cohesion = computeCohesion(agent);
var separation = computeSeparation(agent);

agent.velocity.x += alignment.x + cohesion.x + separation.x;
agent.velocity.y += alignment.y + cohesion.y + separation.y;

agent.velocity.normalize(AGENT_SPEED);

Here, I simply compute the three rules for a particular agent, and add them to the velocity. I then normalize the velocity and then multiply by some constant representing the default speed for an agent.It is possible to enhance this further by adding weights for each rule to tweak the behaviors:

agent.velocity.x += alignment.x * alignmentWeight + cohesion.x * cohesionWeight + separation.x * separationWeight;
agent.velocity.y += alignment.y * alignmentWeight + cohesion.y * cohesionWeight + separation.y * separationWeight;

Modifying these weights will change the way the agents flock. Be sure to experiment with the numbers until you find something you like.

Here's the demo again so you can try it out:


Conclusion

Flocking is simple to implement, but it has some powerful results. If you are making a game with AI, especially large groups of AI that interact with each other, flocking may come in handy. Use it well.

Related Posts