DES311 #2: Chilling with the BOIDS

This week I have implemented two parts of the BOID’s behaviour: Obstacle avoidance (separation) and the tendency to following nearby flock mates (alignment). I’ve also started the pilot module which makes the overall decisions on which way to go.

Quick side note. The plan for this project is to create a simulation of a school of fish, but for now I will be referring to the objects in the simulation as BOIDS (meaning bird like object) since that’s their common name in simulations of this nature. I’ll probably start calling them fish when I get round to making some sprites and artwork.

Separation

BOIDS with separation

Currently each BOID sends out a ray cast directly in front of it. If that ray cast returns with a BOID in front of it starts sending out ray casts at increasing angles until it finds a clear path. The vector of this clear path is sent to the pilot module and used in navigation.

At present the script that sends the rays out at increasing angles is functional, but I’m not overly happy with it. It feels a little over complicated and clumsy and could be made more efficient.

//if an object in front is detected
        if (rays.Length > 1)
        {
            //sets the arc to be scanned and increment it is done in
            float resolution = 5f;
            float angle = 120f;
            float radius = 2f;

            float Y, X, Z, tempAngle, tempAngle2;
            Vector3 direction;
            Vector3 center = transform.position;

            RaycastHit2D[] dangerCheck;


            for (tempAngle = 0; tempAngle <= angle; tempAngle += angle / resolution)
            {

                Y = -Mathf.Cos(tempAngle * Mathf.Deg2Rad);

                for (tempAngle2 = 0; tempAngle2 <= 180; tempAngle2 += angle / resolution)
                {

                    X = Mathf.Cos(tempAngle2 * Mathf.Deg2Rad) * Mathf.Sqrt(1 - Mathf.Pow(Y, 2));
                    Z = Mathf.Sqrt(1 - Mathf.Pow(Y, 2) - Mathf.Pow(X, 2));

                    //checks angle on right side
                    direction = Vector3.Normalize(new Vector3(X, Y, Z));
                    direction = transform.rotation * -direction;

                    Debug.DrawRay(center, direction * (radius), Color.green, .5f);
                    dangerCheck = Physics2D.RaycastAll(center, direction, radius);

                        if(dangerCheck.Length < 2)
                    {
                        //if a clear path is found breaks loop
                        pilot.seperationVelocity = direction;
                        return;
                    }

                    //checks angle on left side
                    direction = Vector3.Normalize(new Vector3(X, Y, -Z));
                    direction = transform.rotation * -direction;

                    Debug.DrawRay(center, direction * (radius), Color.green, .5f);
                    dangerCheck = Physics2D.RaycastAll(center, direction, radius);

                    if (dangerCheck.Length < 2)
                    {
                        //if a clear path is found breaks loop
                        pilot.seperationVelocity = direction;
                        return;
                    }
                }

            }
        }

Alignment

BOIDS with separation and alignment

Next I added the alignment behaviour. Now as well as avoiding other BOIDS in front of them BOIDS will find calculate the average velocity of their surrounding flock mates, resulting in them following each other. Seriously though, look at them go. I’m amazed how quickly these little triangles are starting to seem “alive”.

The code I wrote for this behaviour was a lot easier to understand and a lot more efficient.

        //checks if a another BOID is nearby
        if(nearbyBodies.Count != 0)
        {
            float sumX = 0;
            float sumY = 0;

            //sums velocity components of each nearby body
            for(int i = 0; i < nearbyBodies.Count; i++)
            {
                sumX += nearbyBodies[i].velocity.x;
                sumY += nearbyBodies[i].velocity.y;
            }

            //averages velocity components
            float meanX = sumX / nearbyBodies.Count;
            float meanY = sumY / nearbyBodies.Count;

            targetVelocity = new Vector2(meanX, meanY);
        }

Pilot Module

So I’ve mentioned the pilot module a few times. What does it do? Well so far there are two behaviours (alignment and separation) giving the BOID different direction vectors to move in. The pilot module receives these vectors and makes a decision on which way to go. But how should it make this decision? A simple solution is to average the incoming vectors and tell the BOID to go that way. In quite a few online tutorials this is the solution, and for the most part it works, but there is a problem. If separation detects an obstacle directly ahead and says move left and alignment sees a bunch of other BOIDS going right, the average vector would be straight ahead, and the BOID would drive straight into the obstacle. And this problem will only get worse as more behaviours are added. A real Buridan’s ass situation.

So clearly priority needs to be given to one of the behaviours. Since avoiding crashes is more important than following the group, separation is priority one. But how should priority be set, should each incoming vector be given a weighting based on what behaviour its from? Say everything from separation is multiplied by 3 and everything from alignment by 2… and so on. Well this leads to the problem of how do you decide the weighting, it’s just an arbitrary number that you’d have to test and adjust until the right balance is found. Yeah-nah, that would get tedious real quick.

The solution I reached was to have a maximum vector magnitude that the pilot’s target vector can not exceed. So the separation vector comes in and it is added to the target vector. Next comes separation, if adding it to the target vector means the target doesn’t exceed the limit then its added. If it does exceed the limit then its reduced so it can fit. This would then continue through each behaviour in order of their importance parcelling out parts of the target vector until its all used up. This means if a collision is imminent then separation can take full control, and when the BOID is traveling along safely is can make decisions based on other behaviours.

What’s next…

Here’s my plan for the upcoming weeks:

  • Create a Gantt chart breaking down the project
  • Streamline the separation script
  • Experiment with using colliders rather than ray casts to detect impending collisions
  • Implement the cohesion behaviour