Based on the lessons I learned from my honours project, I have been creating plans for a new horror game. The simple goal is to do more of the things that worked: robust sound design, breakdown of self and repetitive compulsion. All while improving what was lacking, like the feedback to the player and the cumbersome and hard-to-adjust AI.
I’ve realised this project would be a good chance to learn the new features of Unreal Engine 5. So for the last week, I’ve been getting to grips with Quixel bridge, megascans and nanite to create the building blocks of the environment for my new game. The results can be seen in the screenshots in this post. Honestly, it’s almost unfair how good megascan assets look and nanite, which dynamically adjusts the number of polygons on models, has been really easy to set up.
What is this new game going to be about, you may ask? Well, good question. Currently, I plan to use the same core mechanics of Picnic. Have the player collect randomly distributed goals – though this time, I will add story context – all while being hunted by something.
The game’s aesthetic is heavily inspired by the film Annihilation, the short film Irradiation and the STALKER game series. I plan to set the game in a post-apocalypse setting where some weird cosmic anomalies and shenanigans are occurring.
A couple of days ago, I posted Picnic to itch.io! Version 1.0 of my honours project is finally complete. This is an exciting milestone because now my focus can shift almost entirely to writing my dissertation.
Furthermore, I am amazed that with no marketing, several streamers have already played Picnic! It shows how popular indie-horror titles are at the moment, especially if they are free and easy to pick up and play. I should consider this when making my next game, which may be another horror experience.
To see people playing and enjoying the game is incredibly rewarding. But also incredibly humbling as already I have spotted lots of things I can do to improve the experience in the next version!
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.
// 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];
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.
The remainder of this sprint is going to be spent bug fixing and completing my portfolio submission for the module.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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:
animator = GetComponent<Animator>();
parentRb = GetComponentInParent<Rigidbody2D>();
maxSpeed = GetComponentInParent<Pilot>().speed;
//set animation playback start position
//calculate current velocity as a fraction of max velocity
float speed = parentRb.velocity.magnitude / maxSpeed;
//set animation speed multiplyier as fraction
And here is the result:
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.
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.
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:
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:
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)
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?
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.
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.
This week I’ve made some improvements to the cohesion behaviour and implemented goal seeking.
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:
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.
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.