DES311 #9: The Final Sprint

This is it, the final sprint, the last push. This week I have added in a music player and have begun my last round of bug fixing and optimization before submission.

My music player script stores a number of licenced music tracks in an array, and plays them in a shuffled order.

        while (true)
        {
            // Picks next track
            nextTrack = Random.Range(0, tracks.Length - 1);

            // Avoids the same track replaying
            if (nextTrack == currentTrack)
            {
                nextTrack += 1;
            }

            // Starts playing the next track
            audioSource.clip = tracks[nextTrack];
            audioSource.Play();
            currentTrack = nextTrack;
            trackName.text = tracks[currentTrack].name;

            // Waits until track is finished playing
            yield return new WaitWhile(() => audioSource.isPlaying);
        }

By naming each audio clip as the track title and artist separated by a dash then I can send the name of the currently playing trach to a text element in the UI. Also in the UI I’ve added a slider that controls the audio source volume. If I were adding more sound effects I would instead control the volume of a mixer where multiple audio sources would be sent. However due to the creative decision to avoid sound effects this was unnecessary.

What’s Next

The remainder of this sprint is going to be spent bug fixing and completing my portfolio submission for the module.

DES311 #8: DOTS or how I learned to stop worrying and love my garbage code

This week just a short post about the user testing I’ve been conducting. I’ve sent the game to a number of people who have been reporting any bugs they’ve found back to me. So far I was aware of all the bugs sent to me and have a plan in place to sort each one. The main take away from the testing is the performance limit.

My laptop can simulate 100 BOIDs before a significant frame rate drop. Initially I assumed this was just the limit of processor but this limit appears to be universal on different hardware.

After some back of the napkin calculations I worked out that the number of calculations per frame increases at a factor of:

f(x) = x2 – x

…where x is equal to the number of BOIDs. This is because each BOID is collecting vector information on every other BOID. Which means there’s no way round this.

However I have noticed the simulation doesn’t take full advantage of all the cores on mine and other testers processors. Which has lead to me researching the data orientated tech stack, or DOTS in Unity. It would allow the simulation to run on multiple threads and therefore utilise all a processors cores. Rather than using game objects DOTS keeps track of entities and pieces of data. Here’s a tech demo of DOTS being used to simulate a futuristic city in Unity:

After reading the documentation it is clear DOTS is perfect for this type of application, but though super cool far exceeds this projects scope. If I where to implement DOTS it would require rewriting most of my object oriented code with new syntax I’m unfamiliar with. If the project had set out to use DOTS from the beginning it might have been possible but with the due date in a months time and with the development portfolio and video still to make I don’t have the time.

What’s Next

Come to terms with the fact that without DOTS the simulation will probably be limited to 100 fish 😥 . Continue bug fixing and user testing. Implement the music and UI elements I talked about last week.

DES311 #7: Version 1.0

Sprint 3 is finished and a version of the simulation with all the major features is complete. I’ve begun sending the simulation to people for testing and am hosting it on itch.io. Click here to download the latest version.

UI

The features I spent this week working on were the UI and menus. The UI is made up three parts, the main menu, the tutorial screen and the parameter menu. All the UI elements were designed in a flat style using the British colour palette from flatcolors.com.

Main Menu Screen

I started the main menu by designing a simple logo for the simulation which I have simply named ‘shoal’. The menu has four options: a quit button which closes the application; the inspiration button which opens a link to the YouTube video that inspired the project; the blog button which links to this blog and the play button which opens the tutorial screen.

Tutorial Screen

The tutorial screen tells the player how to place food for the fish and open the parameter menu. The contents of the screen fades in sequentially after the main menu has faded out when the play button is clicked. By clicking anywhere on the screen after the play button is clicked will trigger the the scene manager to change the games scene to the main simulation scene. By keeping the backgrounds of each scene the same transitions appear seamless.

Parameter Menu

The parameter menu is accessed in the main scene by pressing the escape key. Currently the screen allows the user to adjust sliders controlling different parameters of the simulation. The value of each slider is displayed in the input field next to it. The user can also type desired values directly into the box, these values can exceed the maximum values of the slider (though the slider values are my recommended parameters through testing). Due to time constraints the elements in this menu still use the Unity default elements. I aim to replace these with elements in a similar style to the rest of the UI in the next version.

What’s Next

With the major features implemented my time over the foreseeable future will be spent user testing and bug fixing. Though I would like to find time to include some stretch goal features I didn’t think I’d have time for, like putting in some relaxing royalty free music. I’d also like to have the option to change the tank size in the parameter menu.

DES311 #6: The Fish Locomotion Rabbit Hole

I have now started with my scheduled goal of adding animations and sprites to the simulation. To make this easier I decided to pick a species of fish and predator to base mine on. Inspired by this dramatic scene from the BBC documentary The Hunt in 2017 I decided to go with Sardines as the BOIDs and Tuna as the predators:

Another reason for picking sardines is they are of the Clupeidae family meaning they have short lateral lines. Lateral lines are sensory organs that aid fish sense water pressure and therefore objects around them. This means my model of fish vision in the simulation fits sardines quite well.

As for art style I decided to make things easy for myself and draw the fish as silhouettes viewed from the surface, much like the fish seen in Animal Crossing: New Horizons:

Me fishing in my town Driftwood

First I drew a silhouette roughly proportional to a sardine in Adobe Illustrator. I then took this silhouette into Unity, and after watching some tutorials used the skinning editor to create a skeletal mesh. To save myself key framing each bone I used the 2D Inverse Kinematics package to chain bones together. After a few more tutorials I got this working and created an animation that looks like this:

It wasn’t too bad, but it didn’t look quite right. So I’ve spent some time researching the strangely interesting world of fish movement and locomotion. I’m going to unload some useful terms and explanations I’ve learnt.

  • Sardines are of a fusiform shape meaning they are very hydro dynamic, like little torpedoes.
  • They use body caudal fin propulsion, meaning their bodies move in oscillating waves as they swim. There are five different classifications of body caudal fin propulsion that determine how much the tail moves in proportion the head.
  • Eels for example fall into the anguilliform group since the amplitude of the wave travelling through their body changes very little as it moves from head to tail.
  • Fish in the thunniform group however move their head very little and the tail does the majority of the work, this is useful for predators like sharks and tuna who use their heads when attacking.
  • Finally we come to sardines which primarily fall in the sub-carangiform group, they are relatively stiff with the tail doing most the work, but still use their upper bodies slightly when moving, this gives them a good balance of speed and manoeuvrability.

Armed with this knowledge I returned to Unity and created some new animations settling on this:

In order to make the animation seem more life like in the simulation I had to set an offset in the starting point of the animation cycle so all the fish aren’t in perfect sync when they spawn. I also set the animation speed to multiplied by a fraction of the current velocity and the max velocity. This means that the animation speed set in the clip is the max speed the animation can be played at, giving me more control over how the simulation looks. All this code is kept in a single class assigned to each BOID:

    void Start()
    {
        animator = GetComponent<Animator>();
        parentRb = GetComponentInParent<Rigidbody2D>();
        maxSpeed = GetComponentInParent<Pilot>().speed;

        //set animation playback start position
        animator.SetFloat("cycleOffset", Random.Range(0.0f,1.0f));
    }

    void Update()
    {
        //calculate current velocity as a fraction of max velocity
        float speed = parentRb.velocity.magnitude / maxSpeed;

        //set animation speed multiplyier as fraction
        animator.SetFloat("speed", speed);
    }

And here is the result:

What’s Next

Next I need to create a sprite and animation for the predator tuna, since they are of the thunniform group this animation will have less head and body movement than the sardines. Next week I also aim to implement a UI that allows the user to change parameters within the simulation.

DES311 #5: Predators

Progress continues, this week I’ve implemented the last feature on the sprint 2 backlog, predators. Furthermore I’ve been making tweaks to the flock behaviour that has lead to some interesting behaviours and have put in a spawning system that controls the number of BOIDs on screen.

Predators

How predators work is quite simple when compared to the BOIDs. After a set cooldown period a predator is spawned in a random position outside the cameras view. The fist thing the predator does is create a list of all the BOIDs in the scene:

        boids = GameObject.Find("BOID Spawner");

        foreach (Transform child in boids.transform)
        {
            prey.Add(child);
        }

This was actually the first time I had used the for each statement, which on reflection is crazy because its very handy. Next the predator averages the position of each BOID and creates a vector that intersects that position. I’ve written similar code several times already in this project, so I just copy pasted it from the cohesion script. This sends the predator after the flock and when it collides with a BOID that BOID is destroyed. A bool value keeps track of if the predator is hungry. If it has collided with a BOID its hunger is satisfied and it stops following the flock, leaving the scene to be destroyed when out of view:

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if(collision.tag == "BOID")
        {
            hungry = false;
            Destroy(collision.gameObject);
        }
    }

After the the predator is destroyed the predator spawner starts its cool down timer. When its up another predator will be spawned.

Since BOIDs are now being destroyed I’ve had to put in a spawner that adds new BOIDs to the scene when they are destroyed by a predator. I experimented with using a similar script to the predator spawner that brings new BOIDs in at random positions off camera. However this lead to a problem. This method meant that if a predator destroys multiple BOIDs then new ones appear from multiple random directions, this leads to quite a lot of chaos as the BOIDs attempt to reform a single flock again. The solution was to have new BOIDs spawned in batches from a single random position that changes periodically. This also has the added benefit of leaving the scene empty for a while if a lot of BOIDs have been destroyed. It gives the simulation the feel of an Attenborough documentary as you watch a few remaining survivors of an attack attempt to regroup and reform their flock.

The introduction of predators that could destroy BOIDs caused a lot of problems due to the BOIDs creating lists of nearby flock mates to generate their behaviours. As soon as null objects started appearing in those lists I was getting a lot of errors and crazy bugs. This was easily remedied by adding a reverse counter that goes through lists removing null objects (it goes in reverse as to not interfere with the index when objects are removed):

        //removes destroyed BOIDS
        for (int i = nearbyBodies.Count - 1; i > -1; i--)
        {
            if(nearbyBodies[i] == null)
            {
                nearbyBodies.RemoveAt(i);
            }
        }

It seems strange to admit but at this point I did get a little sad, preparing those little white triangles to deal with the death of their nearby friends. But deep down I knew this was a good thing. If I’m getting sad about the death of little triangles it means their movement and behaviours are life like enough to trick my brain into forming attachments… after all isn’t that the essence of game development?

Weird Behaviours

I thought I would take some time to talk about some patterns and behaviours I’ve been noticing. The first is how the BOID flocks tend to always turn left, leading to anticlockwise motion. At first I thought this was because the separation script that generates vectors to avoid collisions checks the left side first. So I added a function that randomises the direction that is checked first:

float directionFlip = Mathf.Sign(Random.Range(-1, 1));

direction = Vector3.Normalize(new Vector3(X, Y, -Z * directionFlip))

It’s not very clean code and has a slight bias to the right side, but that doesn’t matter, because the BOIDs still swirl in an anticlockwise motion. So far I haven’t found any other reasons in the code this may be the case. Its super weird.

Another interesting behaviour that I first thought was a bug was the seemingly random snap changes in direction. Randomly in unison the flock will flip 180 degrees and go the other way. This I don’t think is a bug, since this behaviour can be observed in real-world schools of fish. Some further research is needed but what I think is happening is the BOIDs at the front are slowing down to try and reach the centre of the school. In doing so they gradually change the average velocity of the flock, and when enough of them do this and the average velocity reach’s nearly zero a chain reaction starts as each BOID follows their neighbour whilst avoiding stopping. What looks like a complex decision made by some hive mind is really each BOID reacting to a situation in a similar way. For now I’m classing this as an emergent behaviour.

What’s Next

With the conclusion of this sprint I have completed all the main mechanic features I wanted for the simulation. What I plan for sprint 3 is to create some sprites and animations to make the simulation more interesting to look at, I’d also like to build a UI that lets the user change parameters of the simulation.

DES311 #4: Goal Seeking

This week I’ve made some improvements to the cohesion behaviour and implemented goal seeking.

Cohesion Improvements

Last week I spoke about how the cohesion behaviour wasn’t causing the swirling patterns I was looking for. I hypothesized this was because BOIDs were packed together too tightly and not able to jostle for central position. I attempted to improve this by having the separation behaviour use a collider to find nearby BOIDs rather than a single ray directly in front of it. This didn’t do the trick. Because separation looks for clear paths at increasing angles from the forward direction. This meant if a BOID entered the collider from the side the behaviour would try and find a clear path directly in front, which it would since the nearby BOID is at its side. This meant the separation was constantly maxing out the pilot module with a vector of straight ahead, meaning lower priority behaviours wouldn’t have any control.

I reverted the build back since this method seems like a dead end for now. Instead I made a change to the cohesion behaviour so it doesn’t take into account BOIDs that are too close:

                //objects too close don't get included, leads to natural separation
                if (Vector2.Distance(transform.position, nearbyBodies[i].transform.position) > targetSeperation)
                {
                    sumX += nearbyBodies[i].transform.position.x;
                    sumY += nearbyBodies[i].transform.position.y;
                }

With this emergent separation between BOIDs they now have room to jostle for central position, leading to the patterns I was looking for:

Goal Seeking

Next on my list of goals for this sprint was to add a goal seeking behaviour. This is the first behaviour that lets the user interact with the the flock.

How it works is when a static bool is read as true by the seeking behaviour in each BOID the position of the mouse (stored as a static vector) is used to calculate a new direction vector which is sent to the pilot module. The static bool is made true when the left mouse button is clicked. When the left mouse button is released a circle is instantiated at the mouse position and the BOIDs will move to that circle. The circle is destroyed when a BOID touches it.

When sprites are added this action can be made to look like the user is holding a piece of food above a fish tank, which they drop in when the mouse is released.

What’s Next

This concludes the first week of sprint two. For the second week I am aiming to implement predators and predator avoidance. These will be triangles who will move across the screen and the BOID flock will scatter to avoid.

DES311 #3: Coming Together

This week I have managed to implement the cohesion behaviour in my BOID simulation as well as completing a project timeline. I’ll discuss both in this post.

The Timeline

The project will be broken up into 4 fortnightly sprints whose tasks will be taken from a project backlog. This backlog will change over the course of the project based on research, feedback and lessons learnt from implementation, but I estimate the tasks for each sprint will look like this:

Sprint 1: 1st March – 14th March

  • Implement basic functions of:
    • Obstacle Avoidance
    • Follow nearby flock mates
    • Flock cohesion  

Sprint 2: 15th March – 28th March

  • Implement advanced features of:
    • Goal seeking
    • Predator avoidance

Sprint 3: 29th March – 11th April

  • Basic sprites
  • Basic animations
  • Basic sounds that provide feedback
  • A simple UI

Sprint 4: 19th April – 2nd May

  • Tweaking based on previous sprints and polish

Tasks and progress will be tracked on a public Trello board.

Cohesion

The cohesion behaviour makes BOIDs group and move closer together whilst also jostling for a central position in their local group. This is the behaviour that leads to the beautiful dynamic swirling seen in real world swarms.

In the simulation I currently have BOIDs keeping a list of any nearby flock mates inside a trigger collider around them. In the alignment behaviour the velocity of surrounding flock mates is averaged and sent to the pilot module. In the cohesion behaviour the position of surrounding BOIDs is instead averaged. A vector is then drawn between the BOID’s current position and the average position. This vector is then normalised, multiplied by the magnitude of the max velocity and then sent to the pilot module.

The result looks like this…

Did you spot them? Some BOIDs are breaking the speed limit somehow. I spent the best part of this week trying to find out a reason and solution. My current hypothesis is that it has something to do with the edge warp. The edge warp is what teleports objects to the opposite side of the screen when they go outside the bounds of the camera, like in the game Asteroids. I think when a group of BOIDs approachs the edge the ones at the back are calculating the average position to beyond the edge, and then when they teleport to the opposite side of the map some value somewhere breaks and the excessive speed is it compensating. Honestly I have no idea.

But in true programming form I don’t need a reason so long as I have some sort of solution. For some reason adding a script that adds a small forward force to each BOID if its velocity is zero solves the problem. I think it pushes the BOID off the edge line before they have a chance to go weird.

        //if BOID gets stuck add forward force to keep it moving
        else if (rb2d.velocity == new Vector2(0, 0))
        {
            rb2d.AddForce(transform.up * speed);
        }

And hey, it works, look at this…

Separate groups of BOIDs are now attracted to each other. Already this motion is causing more interesting patterns to develop. However as you can see they aren’t really jostling for a central position. This is because I have a capsule collider on each BOID so they can’t get on top of each other. This is necessary because without it they all bunch on top of each other and look like a single BOID. To create better jostling I think they need to be able to go on top of each other briefly whilst attempting to maintain distance apart. To do this I will need to improve the separation mechanic so it isn’t just looking at a single ray in front but moves the BOID to avoid flock mates next to it too.

What’s Next?

I am happy with the outcome of this fortnight sprint and think I have quite a respectable little simulation. The improved separation behaviour is high on my list of priorates for the next sprint though. Starting on the 15th of March sprint 2 will aim to implement this improvement as well as the goal seeking and predator avoidance behaviours. But for now I’m celebrating the end of this sprint with a German Donor kebab.

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

DES311 #1: Fishy Business

Hello, I’m James and when grow I up I want to be a mechanics and gameplay designer. During my time at uni I’ve had the chance to take on a design role in a lot of cool projects – potential recruiters may I divert your attention to my portfolio. Of course mechanics design also requires a decent amount of programming ability, so this semester I’m taking on a project that will hopefully let me develop these skills.

Surviving Mars screenshot

If I were ever to rank my favourite games of all time, near the very top would no doubt be the simulators Tropico 4, Rimworld and Surviving Mars. I find simulations of people, cities and nature absolutely fascinating, because no matter how much I understand how they are programmed I always impart some emotional motivation on their actions. And so I want to programme a simulation of my own. Something complex enough that cool emergent behaviours develop but simple enough to build in a semester.

Seemingly complex but relatively simple. This, for whatever reason, reminded me of the beautiful patterns of flocking animals like the murmuration’s of starlings or bait balls of sardines. They may seem impossible without some sort of telekinetic powers or gestalt consciousness but are in reality quite simple. Each member of the flock or school simply has to follow three rules, move to avoid bumping into other members, try and follow other members to again minimise bumping and then try and get to the centre of the group to be more protected. These rules are the foundation of swarm intelligence as described by Craig Reynolds.

School of sardines in Finding Nemo

And just like that I had an idea. I’m going to simulate a school of fish that will follow these rules. I may try and gamify this simulation with the addition of more complex behaviours like obstacle avoidance or goal seeking and make it more visually interesting with animations and visuals, but the primary goal is the simulation. Thus I’ve broken the project goals down like this:

Must Contain

  • 2D simulation of fish
  • The fish in the simulation should follow Reynolds three rules of flock behaviour: separation, alignment, and cohesion.

Should Contain

  • Basic visuals
  • Basic sound
  • Goal seeking
  • Obstacle avoidance

Could Contain

  • Pretty visuals with animation
  • Simulation is in 3D

Over the course of the coming weeks I will aim to keep this blog updated with regular updates on the project and how it comes along. But for now I’m signing off and keepin’ it reel… sorry, I spent ten minutes thinking of that pun.