Advertisement

Need Help with flocking AI

Started by June 17, 2009 12:38 AM
5 comments, last by Thunder0ne 15 years, 8 months ago
Hey, guys. I have a problem with my first AI project. It's about flocking. The problem is, it looks so unnatural. They tend to flock with no more than 2 or 3 boids. SWF : http://megaswf.com/view/03279d52b96a29a5590e45eff91e20a3.html Yes, I've read the boid's page. I've understood the algorithm ( I think) but I failed to implement it. Dunno whats wrong with my code. Okay, my approach for this AI is : 1) Find the "desired" direction based on separation, alignment, and alignment rule 2) Steer to "desired" direction However, this is my code. It's done in AS 3 (FYI I've just start learning AS 3 few days ago). For simplicity, I set all the boids speed to a same value. So we will find the desired direction :

function Flock_Calc(me:MovieClip):void {
	//begins flocking AI
	var Dx:Number = 0; //Resultant Vector for X axis
	var Dy:Number = 0; //Resultant Vector for Y axis
	var count:Number = 0;
	var avgDir:Number = 0; //avg direction - for alignment rule
	
	for(var i = 0; i<BodyArray.length; i++) {
		//if the boid is me, then continue to next iteration
		if(i==me.id) continue;
		
		var dx:Number = (BodyArray.x-me.x); //distance in X axis between me and other boid
		var dy:Number = (BodyArray.y-me.y);
		var dr:Number = Math.sqrt(dx*dx + dy*dy); //distance
		
		//if the boid is out of my sight, continue to next iteration
		if(dr>me.sightRadius) continue;
		
		//cohesion rule
		//the farther the other boid, the stronger the desire to steer to that location
		Dx += LinearFunction(dx,0,0,me.sightRadius,1); //this function returns 0 when dx = 0
		Dy += LinearFunction(dy,0,0,me.sightRadius,1); //return 1 for dx = sightradius and -1 for dx = -sightradius
		//alignment
		avgDir += BodyArray.rotation;
		count++;
		//separation
		//the nearer the boid, the stronger desire to steer away from that location
		if(dr<=me.sepRadius) {
			if(dx>0) Dx -= LinearFunction(Math.abs(dx),0,1,me.sepRadius,0); //return 1 if dx = 0 and 0 if dx = sepradius
			else Dx += LinearFunction(Math.abs(dx),0,1,me.sepRadius,0);
			if(dy>0) Dy -= LinearFunction(Math.abs(dy),0,1,me.sepRadius,0);
			else Dy += LinearFunction(Math.abs(dy),0,1,me.sepRadius,0);
		}
	}
	if(count > 0) {
	//computing the final alignment rule
	avgDir += me.rotation; //add my direction
	count++;
	avgDir /= count; //get the average
	Dx += Math.cos(avgDir); //Math.cos returns value between -1 and 1
	Dy += Math.sin(avgDir);
	//the final magnitude of Dx and Dy doesnt matter since we just find for the direction
	me.toRotate = Math.atan2(Dy,Dx)/Math.PI*180;
	}
	else me.toRotate = me.rotation;
}
Here is the steering method:

	//calculate steering
	me.rotation += (me.toRotate - me.rotation)*0.1;
and here is the linear function, just in case you need it

//LinearFunction()
//(y-y1)/(y2-y1) = (x-x1)/(x2-x1)
//(y-y1) = (x-x1)/(x2-x1)*(y2-y1)
// y     = [(x-x1)/(x2-x1)*(y2-y1)] + y1
//LinearFunction returns y for any given x passed by valx 
function LinearFunction(valx:Number, 
						x1:Number, y1:Number, 
						x2:Number, y2:Number):Number {
	return (valx - x1)/(x2-x1) *(y2-y1) + y1;
}
I haven't really looked at your code, but from looking at the SWF, it seems that the "area of influence" is too small. Try increasing the value of sightRadius and see if they form larger flocks.
Advertisement
Hi,

//cohesion rule//the farther the other boid, the stronger the desire to steer to that locationDx += LinearFunction(dx,0,0,me.sightRadius,1); Dy += LinearFunction(dy,0,0,me.sightRadius,1); 

This is the same as:
Dx += dx/me.sightRadius + 1
Dy += dy/me.sightRadius + 1

Is this really what you want?



//calculate steeringme.rotation += (me.toRotate - me.rotation)*0.1;

So you will never reach the desired angle! You might want to use something along the lines
rotationDiff = me.toRotate - me.rotationif rotationDiff > maxRotation:    rotationDiff = maxRotationelif rotationDiff < -maxRotation:    rotationDiff = -maxRotation



Hope this helps

“Always programm as if the person who will be maintaining your program is a violent psychopath that knows where you live”
Quote:
This is the same as:
Dx += dx/me.sightRadius + 1
Dy += dy/me.sightRadius + 1

Is this really what you want?


nope, actually it's the same as :
Dx += dx/me.sightRadius
Dy += dy/me.sightRadius

and thats what I want, returning value between -1 and 1.

okay I've increased the sight radius and tweaked to this :
//calculate steering	var rotdiff:Number = (me.toRotate - me.rotation);	var maxrot:Number = 15;	if(rotdiff<-maxrot)  rotdiff = -maxrot;	else if(rotdiff>maxrot) rotdiff = maxrot;	me.rotation += rotdiff;


still not much difference :/

By the way other problem that I could spot is the boid tends to leave the flock.
you may see it in the SWF I posted earlier.
I am assuming the following locomotion model: velocity is constant in magnitude always parallel to the agent facing, rotations are modified by changing the angular velocity like this

rotation += (deisredRotation - currentRotation) * angVelMag

So, provided there are not bugs (which I have not verified thoroughly) and that I understood the locomotion model correctly, I think you should decrease sepRadius which should be quite smaller than "sightRadius" (half maybe?).

Question / considerations
1) Are you using the rotation in degrees to draw the agent on the screen?
2) I think that the contribution coming from the "alignment" behaviour is too small compared to the others or, at least, their ratio depends on how many agents are in sight.

I hope this can help.
Quote:
I am assuming the following locomotion model: velocity is constant in magnitude always parallel to the agent facing, rotations are modified by changing the angular velocity like this

rotation += (deisredRotation - currentRotation) * angVelMag

So, provided there are not bugs (which I have not verified thoroughly) and that I understood the locomotion model correctly, I think you should decrease sepRadius which should be quite smaller than "sightRadius" (half maybe?).

Question / considerations
1) Are you using the rotation in degrees to draw the agent on the screen?
2) I think that the contribution coming from the "alignment" behaviour is too small compared to the others or, at least, their ratio depends on how many agents are in sight.

I hope this can help.


thank you for your reply

Actually, I have rewrite the code, just in case my old code contains bugs (which actually it is) and finished just now.

the bug was few math errors.. for example, I forgot to convert from degree to radian.

yes, in the new code, I implemented changing rotation technique just like the one you mention. And it gives a smooth turn :)

The new code is using a slight different algorithm. The new algorithm makes separation rules run exclusive than the other 2 rules. meaning thatif separation rules work, then alignment and cohesion rules are ignored. And thats slight different, from Reynold's. AFAIK Reynold's does run all rules whenever possible.

here is the result : http://www.swfcabin.com/open/1245403988
tell me what you think.

Honestly I'm not quite happy with it since the boids sometime collide with each other.
Advertisement
Before proceeding just try with 2 or three agents in order to thoroughly understand what's going on.

Prioritised weighted truncated sum, that's what you could need.
After you have calculated your DX and DY normalised (their range is -1 <-> 1 isn't it?) you just multiply them by a weight (maybe between 0 and 1) (you will need to tune those though) and comparing them against a threshold which will truncate your running total.
This will enable a more smooth mixing of the three behaviours having a priority defined.
A high weight for the separation behaviour will make overlapping less likely, however if you want to keep them tight you shall have the separation behaviour effect take place only at a close range that is you should keep the "sepRadius" parameter quite smaller than "sightRadius".

The locomotion model makes things quite difficult since it is the rotation that defines the vector of the velocity, not the steering force (which there is not in your program). So if two agents are going one towards another, when the separation behaviours comes in, it could be too late since its effect is rotating the agents and not directly changing their velocity. You could try and increase the maximum angular speed and do something more sophisticated like:

currentAngularVelocity = 0;
if(Math.abs(desiredRotation - rotation) > PI / 2.0){//if you use a smaller angle you can have hard turns more often
currentAngularVelocity = MAX_ANG_VEL;
}
else{
currentAngularVelocity = MAX_ANG_VEL * Math.sin(desiredRotation - rotation)
}
deltaRotation = dt * currentAngularVelocity

rotation += deltaRotation;
rotation = rotation % (Math.PI * 2.0);//check this since maybe the rotation value should have the same range as desiredRotation

This should avoid trembling and oscillations or make them less frequent.


I hope I didn't make too many mistakes :-) and I helped a bit.

This topic is closed to new replies.

Advertisement