Picnic is Released!

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.

Download the game on itch.io!

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!

Midnight

This week, I’ve been writing the proposal for my honours project. Unfortunately, sitting in a library for multiple hours a day doesn’t make for an exciting blog post. So I thought I’d do another mini-case study inspired by my research like I did with Shrek Swamp Sim, where I talked about Freud’s concept of the uncanny. So today, I will discuss the Other, abjection and doppelgangers and how they relate to my favourite episode of Doctor Who, Midnight.

Yes, it’s that episode with Merlin

Midnight aired in 2008, at the height of the David Tennent era. Although it seems like a simple bottle episode on paper, it’s a perfectly paced little horror drama. 

A sci-fi tour bus filled with a group of tourists travelling across a planet inhospitable to life has a breakdown. As they wait for rescue, they hear a knock at the door. And while each tourist reacts to this impossible threat in a way fitting their character, the lights go out. In the darkness, whatever was outside seeks out the weakest in the group and possesses them. Unfortunately, the victim, Skye, was a bit of a loner and outsider, so she was already slightly mistrusted by the group.

Now, what does the possessed victim do? Does she start levitating and screaming in tongues? Does her neck snap round and projectile vomit? Nope, she merely copies what people say, just like that irritating game children play to annoy their friends. Already my uncanny sense is tingling with the connections to innocence and childhood but let’s not go there. Let’s talk about the Other instead. Everything that we consider to be in our image, be that physically, mentally or socially, is referred to as the Self. By contrast, everything we believe to be different, opposite and deviant from our self-image we call the Other. The alien entity in Midnight is a manifestation of the Other; what it is exactly we don’t know, but all the characters and audience know that it’s terrifyingly different and strange.

What happens next is the possessed Skye starts speaking precisely in time with the other characters. At this point is some of the characters turn from fear to violence. Going by these reactions, a definite theme of this episode is xenophobia, which is often connected to the idea of otherness. By speaking in time with the other characters, the lines between the Self and Other become blurred. This blurring and breakdown of the distinction between Self and Other is called abjection. Blood, pus and vomit are very primal examples of things that can cause an abject reaction since they remind us of our mortality and that we are not so different from a corpse that we think of as Other.

Skye has essentially become a doppelganger of all the characters, a trope that is well suited for exploring abjection. In Twin Peaks, a doppelganger of the mild-mannered and kind FBI Agent Dale Cooper goes on a rampage, destroying Cooper’s life while he is trapped in the limbo-like black lodge. This scenario is uncomfortable for an audience to watch as they may have grown attached to Cooper and now witness as his double commits horrible crimes. This is a very literal but very effective visual depiction of abjection.

The rest of the episode deals with the human reaction to abjection as the characters debate killing Skye. In a short 45 minute duration, this episode uses the Other and abjection to explore xenophobia, racism, fear of the unknown and childhood innocence, all tied up in a nice bow of cosmic existential horror. No wonder 10-year-old me didn’t sleep for days after.

Onions

In his essay of the same name Sigmund Freud describes the uncanny as an unhomely and creepy sensation that comes about when the subconscious mind projects its repressed urges and feelings onto the world. This phenomenon can explain the unease people can feel when dealing with things such as the occult and arcane; it can also help understand the effectiveness behind many horror movie tropes such as creepy children and animate dolls. 

Today, I will analyse a piece of media using Freud’s work to explain why it is scary. The media I have chosen to examine is a 2014 work by Arman Karshenas entitled Shrek Swamp Sim.

This clone of Slender Man may initially seem like another addition to the long line of Shrek parodies and memeable internet content. I would be inclined to agree if it wasn’t capable of producing the most intoxicating feeling of dread in me. 

As for many people my age, the Shrek movie franchise was a cornerstone of media growing up. This childhood link is the first reason why Swamp Sim is so uncanny. Since Freud believed that childhood memories make up a large volume of the ocean that is our subconscious, it is no surprise that memories of the lovable titular Scottish Ogre exist there. These memories harken back to simpler, more carefree days. So when these happier memories meet with the dark threat of this friendly character chasing and trying to kill you, a feeling of unease and fear is to be expected.

Next is the gameplay. Like the original Slender Man, objects are placed around the map for the player to collect. If the player collects all the objects, they win the game, but as they collect more, the adversary gets faster. In Slender, the objects are pages; in Swamp Sim, they are onions. All the onions are not present at the start of the game; as the onions are collected, more gradually spawn in. This feature forces the play to return to the same areas of the map continually. This compulsive repetition can also invoke a feeling of uncanniness. Freud described an event from his childhood when in an Italian city, he was lost and, despite trying to avoid a creepy plaza, kept inadvertently returning to it. This experience instilled in him a particular type of uncomfortable panic. He felt trapped, like he couldn’t escape and anxious that he was drawing attention from onlookers by continually returning to the same area. Freud attributes this repetitive behaviour to a primal lizard brain instinct in our unconscious to constantly go in circles when lost to prevent oneself from getting more lost. However, this instinct directly opposes the conscious mind, which is trying to get away and find its way somewhere else. This type of experience, I imagine, is quite common to people. Many of us will have had nightmares where we were trapped going in circles or memories from childhood of losing our parents in a supermarket and continually returning to the same aisles.

My final point on this games uncanny nature is Shrek’s stunted and inhuman looking walk-cycle. The amateur nature of the animation is likely intentional in both a humorous and unsettling way. The reason it is disturbing is our unconscious id, and the conscious ego is confused by the unnatural nature of the walk, making us wonder if the animation is alive or not. This unease around whether something is alive or not is more commonly known these days as the uncanny valley, an effect where objects that appear almost human unsettle us because they seem slightly off and our brains are unsure if it is a threat or not. Animators and roboticists have to regularly contend with this effect to ensure their creations don’t creep us out. Of course, this creepiness in the case of Shrek is intentional.

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.