Wednesday 30 December 2015

Arrays, Sound, Collision Detection and Handling, Collectable Objects and Displaying Output (Score) in my Unity game prototype

Today's blog post will cover examples of:

-Arrays
-Sound
-Collision Detection/ Handling
-Behaviours/ Properties of objects modified in the code
-Collectable Objects (Coins)
-And Displaying a Text Output (Score)

In my Unity game.


Firstly, I used arrays and sounds in the game (all SFX and music used are royalty free and sourced from http://incompetech.com/music/ and http://www.freesfx.co.uk/) to organise certain sound effect variables together which use the same variable type (in this case being references to similar components), and the arrays thus allow my code to run more efficiently. In this case I was organising separate sound effects, since the Bird character's game object has more than one audio source component attached (each containing a different sound, which are sounds for jumping/double jumping and collecting a Coin). Here's the C# code I wrote to play my sounds inside the main player script:

Sound variables:

'public AudioSource[] sounds; //Organising all my different sound effects into arrays with []
public AudioSource jump; //Sounds for the Bird jumping and collecting a coin. These AudioSource components both attached to the Bird game object, but are assigned separately in the Bird's Inspector view, and are called by the variable's name
public AudioSource coin;'

Getting the components at the start:

'void Start () {
sounds = GetComponents<AudioSource>(); //Gets the audio source component attached to the Bird character and loads it into memory, so it can play the sounds in the array's collection (a jumping and collecting a coin sound effect) when they're referenced in their seperate sections
jump = sounds[0];
coin = sounds[1];
'

Playing the sound at the same time as jumping:

'{
rb2d.AddForce(Vector2.up * jumpPower); //Add the force of 150 (the jump power variable) in the upwards direction to the Bird's rigid body, so the player can jump!
canDoubleJump = true; //When grounded, you can double jump again
jump.Play();
}'

The sound code (using arrays) are organised into three different sections since they must be in their proper places, and wouldn't function if everything was bundled together in one area. The arrays both as a variable and within the 'void Start' function help organise my sounds (since you can't rename game object components, you must instead organise them, in my case in a numerical order). Arrays basically help streamline all of your variables using [square brackets] in one compact collection. So once the game loads with 'void Start' the component will be instantly loaded into memory, ready to be played at anytime needed (or else the game wouldn't be referencing the audio source component attached to the Bird game object). Then, wherever I want to play the sound in my code, I'll add it using 'soundVariableName.Play();', so in the case above, with the code for my Bird jumping. So it'll apply a force upwards when the user presses Space, set the ability to double jump as 'true', and play the jumping sound once (since looping is disabled in the Unity editor).

The Bird's sound settings, note the audio source component and also the two sound effects attached to the main player script.

I also had background music in the game, which is different in all three levels of the game and the main menu. One level is set in the desert, one outside the forest and one in the forest, as the Bird tries to get back home. And the music I chose is appropriate for the level's theme and fits the atmosphere well, with the desert/ outside the forest music being the same tune with different instruments, which adds a strong feeling of progression and fits the theme. Whereas the forest theme is a remixed version of 'In the Hall of the Mountain King, by Edvard Grieg', which also fits the theme well showing the Bird's desperation trying to get home and the fact the last level is the toughest to beat. The main menu music on the other hand is calming and peaceful titled 'Pamgaea', in order to calm the player before the game begins. All of this music wad added to the game by creating a simple, blank game object, adding an audio source component to it, attaching the song of choice for each level and ticking the 'play on wake' and 'loop' boxes (so it starts when the game begins and repeats over and over).

The game object that plays music's settings.


Now I'll talk about collision and collision handling in my Unity game. Essentially every intractable game object in the game's levels (like the Bird, platforms, enemies, coins, etc) have box and physics colliders attached to them as components. Objects that the Bird will hit and not stop moving after hitting them (like coins and fireballs that vanish after a collision) are marked as 'is trigger' in the inspector, in order for them to trigger the corresponding code in the player script to run. Whereas other things like platforms and enemies are not, since the Bird has a rigidbody2d component attached to it, which means Unity simulates the physics (like gravity) and collisions for you. This means that any sprite I add to the game, I can add a custom collider to it (either a box, circle or custom-shaped polygon collider) and the Bird will be able to walk on it. However I wanted to add custom friction to these platforms, since I wanted to refine the game's mechanics and make it feel more precise to play, with a few lines of C# code in the player script (shown below) so the Bird stops faster when the player stops moving, and a custom physics material attached to every platform that simulates the friction. Here's the code that goes hand-in-hand for the Bird's friction (where all it does is if grounded = true (where the Bird is on the floor), then ease/ slowdown the horizontal movement speed for the Bird's rigidbody):

Friction code:

' //Fake friction to stop Bird sliding around after moving and when grounded, and easing the x speed of the player (make sure I apply the Ground Physics Material to every platform the player stands on to work with this code)
if (grounded)
{

rb2d.velocity = easeVelocity; //Resets the new variable easeVelocity making it equal to the current velocity

}'

Selecting a huge range of platforms in the game, note the red arrow, which shows that every piece of land has the ground physics material attached to it.


Now I will talk about bottomless pits in the game, which also make great use of the collision with the Bird. Essentially there is a giant game object (tagged with 'Pit') stretched out under every level with a trigger collider attached to them. And now the code it uses also couldn't be simpler (shown below, as a great way to streamline and optimise my code to a professional standard), basically whenever the player falls down a hole off the stage, the Bird will collide with the bottomless pit game object, and the Bird is constantly checking to see if it's collided with any object tagged with 'Pit', so when this all comes together it simply runs the 'Die' line of code that restarts the level. It's very simple and efficient!

Falling down a pit code:

'if (col.gameObject.tag == "Pit") {

Die ();


}'

Next up for collision events I created falling platforms for the Unity prototype. Where they's basically platforms that the player can stand on, but after 0.5 seconds the platform will fall, and if it's over a bottomless pit it will be tough to get off in time before you fall to your doom. I wanted to add these to spice up the challenge in the game, and make the player act more carefully when playing and not just blindly charge through the levels. Instead now they'll have to stop and asses the situation as they play. How these platforms work in-game with the code (shown below) is simple: They are easy to recognise by being thin and lightly coloured compared to the regular terrain in the levels, but also don't stick out like a sore thumb with a bad colour-scheme and ruin the suspense I'm trying to build in the game. Essentially the code modifies the platform's 2b2d kinematics, toggling the platforms rigid body on (which defaults to off) after the bird has been colliding with the platform for a certain amount of time (in this case, 0.5 seconds). So when the 2b2d (rigidbody2d) is enabled, Unity's physics will affecr the platform, so it will fall downwards due to gravity. I chose this specific time because during testing I found that if there is a longer limit the player can easily walk across the platform and jump off in time, but any shorter and the platform would fall too fast and be something many players will struggle to react too, making the game more frustrating to play than challenging.

Falling platform code:

'private Rigidbody2D rb2d;

//Float (For the time the Platform takes to fall)
public float fallDelay;

void Start() //Get the Platform's rigidbody when the get loads
{
rb2d = GetComponent<Rigidbody2D> ();
}

void OnCollisionEnter2D(Collision2D col) //If the object that colldies with the Platform is the Bird (tagged with 'Player')
{
if (col.gameObject.tag == "Player")
{
StartCoroutine(Fall()); //Begin the IEnumerator loop to make it fall when the Bird collides with/lands on the platform
}
}

IEnumerator Fall()
{
yield return new WaitForSeconds (fallDelay); //After a certain amount of time (the fall delay is set to 1, inside the Inspector of the Falling Patform)
rb2d.isKinematic = false; //The gravity will now affect it and the Platform will fall, as physics will now affect the game object

yield return 0; //Ends the IEnumerator loop

}'

The bird landing on a floating platform.
After a short delay the platform registers this collision and begins to fall down into the bottomless pit.

Now moving on from custom tags and collisions I have collectable coins in my game (and every instance of this game object also has the custom tag of 'coin', for the same reason as the bottomless pit game object tagged 'pit', so various objects can have various functions as the bird hits them). These coins act as the game's main collectable and as a secondary win condition for the game since you can try to get as many as you can, and some are hidden in places to encourage exploration, so that you can try to get as a high score as possible (the sprite for the coin is from OpenGameArt.org and sourced royalty-free). The coin game object instances throughout the levels are all based off of one main coin object, that is stored in a prefab that can place as many duplicates as I need. This is an example of me following professional working standards, as with that one prefab in my Unity project files I can create as many duplicate coins in my level as I like while creating the game, and can edit them all at once. This way I don't have to copy and paste each coin into my game at a time, or select them all manually if I want to change anything in the inspector view, so this is very good time management in the long run.

During the player's journey to the door at the end of each level, these coins are scattered about everywhere, and they also have a gameplay advantage. I can also use coins to assist the player as they play the game, like subtly drawing a path to the goal, lining up a discrete trail to a secret area and even use them as a guide for a tricky section of a level (positioning them in such a way the player knows when to double jump up a ledge, for example). The coins also lend themselves well to my game's design, as throughout the three levels of the game I will put less and less of them into the levels, starting off with an abundance of them and finishing with coins being pretty rare. This is because as they player plays through the game their skills also increase, and that means less coins are needed to help the player and they can start thinking for themselves.

I have put a portion of the script for the coins below. How it works is simple: When the Bird collides with one, it will play a 'ding' sound (so the player definitely knows it's been collected, and it starts to become an iconic sound to them) destroy that instance of the game object (so the player can't collect the same coin twice) and add 1 to the game's score (which starts at 0 when the first level of the game loads, since there's no reason to have any more than that at the start). It adds 1 to the score by changing the text input field within the 'game master' script in the game master object, which handles all of the text in the game (such as the text the door uses to draw text too). The score can also carry over from level to level, and the code does this by getting the value of the current score in the integer variable 'points' from the previous level, and setting it to that value using the built-in PlayerPrefs setting in Unity.


Coin collecting sample code:

' if (col.gameObject.tag == "Coin") { //If the bird touches a coin, despawn that coin (so they can't collect it again), then increase the value of the points total by 1

Destroy(col.gameObject);
gm.points += 1;
coin.Play(); //Then, similarly to the jumping sound, play the coin sound affect inside the second audio source component

}'


Writing the current score sample code:

'void Start() //Check if the score has saved before starting a new level so it can be carried over
{
if (PlayerPrefs.HasKey ("Score"))
{
if (Application.loadedLevel == 1) //If we are on the first level of the game, then we do not want to load the previous score and reset it instead!
{
PlayerPrefs.DeleteKey ("Score");
points = 0;
}

else //If it isn't the first level, then assign the score that is already located inside the integer points variable
{
points = PlayerPrefs.GetInt ("Score");
}
}
}'

The bird about to collide with a line of coins, note that the score is currently set to 0 as none have been obtained yet.
The Bird jumping over the bottomless pit onto the next platform, passing through a line of coins on the way (which have been destroyed and removed from the game once collected). Note that the score now reads '3', as a total of 3 coins have currently been collected by the player.


The main character Bird's tail also has a big collision box but it's set to 'is trigger', this was changed later on in the game's development, so it can still pick up coins and get hurt by enemies, but not physically collide with or get stuck on platforms. As it upon testing it felt unfair that the bird has such a large hitbox behind him while platforming, so it was modified making enemy attacks still tough to avoid, but jumping over terrain more manageable, as with a huge hitbox on the tail navigating the game's levels was more tedious at times as the bird sometimes hit overhanging platforms and got stuck in tight spaces. So now the collision boxes are more forgiving and squared shaped, to match the tiled nature of the game.

Different body parts of the bird (like the body, legs and tail) all have separate colliders associated with them, each inside empty game objects as children components to the Bird as the main parent game object. This way I can give them all individual properties, and in this case, set the Bird's tail to be a trigger (annotated above) so the large size doesn't get in the way when platforming, but can still both help and hinder the player.

Likewise with me using collision above, there's also a game mechanic that takes advantage of the Bird character's well-positioned collision boxes, a maneuver that I call 'the hook'. This gameplay function doesn't use any code, instead it works by using the gap between two of the Bird's collision boxes to 'grab' onto an edge where you'll get stuck to it after landing. You can still move away if you don't want to jump (but that may usually lead to you moving into a bottomless pit and restarting the level), however you're able to jump off the ledge to land safely onto the platform next to it, and also gain a bit more vertical height in the process. It's also set-up so it's easy to do, as the Bird automatically 'grabs' onto the edge of the platform if you jump into it and aren't going to land on the ground properly. It's a great safety net type of feature that was included to make my game more accessible to a casual audience, without completely punishing a mistake with the player falling to their death. However the hook also works well for a more hardcore audience, as it adds another layer of depth and strategy to the game while also adding in another skill that speedrunners can use to play through my game quickly and effectively. Here are some images demonstrating how it works below:

A demonstration of the bird's colliders in the Unity editor (they are the three green boxes). The red arrow points to the 'ledge' where there's a space between the collision boxes, and the bird can use this to 'grab' onto ledges and jump up from them.
A picture showing the hook in action, the bird 'grabs' onto the ledge when you jump into it and the player can then jump off of it to gain extra height

I also added a timer to the game (that was suggested in the end-user review when people filled out my survey for the game). This timer is in every level and counts down from 3 minutes (180 seconds) to 0, if it reaches 0 the bird dies and the game will restart. I added this in order to add a sense of urgency to the game, since it's a driving force that pushes you forward. It helps add another layer of depth to my game and can make it more comparable to more professional games in the industry (like Mario or Sonic). In order to also appease the casual ordinance of my game too, since the timer makes the game harder, the timer also pauses with the rest of the game when you pause the game with the Escape key, meaning if it ever gets too hectic for the player you can take a break an come back to the level you were stuck on with a clearer mind. Here's an extract of the timer code and how it works:

Timer code extract:

'

                        if (timer > 0.1) {
                                    timer -= Time.deltaTime;
                                    inttimer = (int)timer;
                                    timerText.text = inttimer.ToString () + " Seconds Left!";
                        }
                                    else{

                                    Application.LoadLevel(Application.loadedLevel);
                                               
'

Essentially the timer counts down from 180 (as assigned in a float variable since the timer can be more precise using decimal points in its value, but it still displays the timer as an int (whole number) to not overcomplicate things and not distract the player with a changing time every 0.1 seconds). And all the time the timer is above 0.1 in value (as said by the if statement), it will continue to count down and change the displayed text every second. However, if the score is below 0.1, restarts the game and kill the bird. I did this because if the score never ended it would just continue counting down into negative numbers, which isn't what the score is intended to do.
The time at 177 seconds, just after the game has loaded

The time is now at 174 seconds, as it slowly counts down to 0
My research beforehand was incredibly useful for me when it came to these topics. For example, when I was looking up types of sound in Unity, I converted all of my sound files into audio clips when they were attached to their relevant audio source components, and used the audio.play code in my script to run the jump and coin sounds. This meant I could easily import the sound clips into Unity, and quickly add the relevant code in. My research of arrays also assisted me in Unity, using square brackets and listing my Player's sounds in numerical order really helped clean up the code but to also optimise it, so I could call different variable names like 'coin' and 'jump' in my audio playing section of the code whenever I needed them to play the relevant sound effect at the right time.

I also researched collision detection previously, which meant I had a greater understanding of separating out collision functions to objects with certain custom tags (like my 'coin', as the example used 'bullet' to hurt instead of help the player). It also expanded my understanding of collision just being when two colliders hit each other, it triggers code to run. This meant I could also implement the coins that draw text on the screen, as I understood to create a separate collision function that contains the code I want to run (so in this case, destroy the game object after's it has fulfilled its purpose).

No comments:

Post a Comment