In this final chapter, we are going to go through the process of checking, supporting, polishing, and preparing our game so that it's built and ready to be played on a device, making it platform-independent. Because our game will be ready to be played on various devices, we need the game to support as many screen ratios as possible. Back in Chapter 8, Adding Custom Fonts and UI, we made our game's UI support various screen ratios. The game, however, was built purposely for a 1,920 x 1,080 resolution, as discussed in Chapter 2, Adding and Manipulating Objects.
In this chapter, we will make our game run at different screen ratios to support the use of mobile devices. This will involve changing Unity's Canvas scale and updating our Player script controls to update its screen boundaries, touch screen capability, and our ability to tap to move our ship. Furthermore, we will make our game aware that it is being played on a mobile device, and we'll make some changes, such as removing the AD button in the shop, as adverts aren't supported on PC devices.
The PC version of Killer Wave will have more polished effects applied, such as post-processing, which will basically make our game more pretty with effects such as motion blur, chromatic aberration, color grading, and a few more effects on top. We will also be looking at reflection probes to create a mirrored effect for some of our art assets in the level3 scene.
In this chapter, we'll be covering the following topics:
The next section will specify the exam objectives that will be covered in this chapter.
The following are the core exam skills that will be covered in this chapter:
Programming core interactions:
Working in the art pipeline:
Optimizing for performance and platforms:
Working in professional software development teams:
The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/tree/main/Chapter_13.
You can download the entirety of each chapter's project files at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition.
All the content for this chapter is held in this chapter's unitypackage file. This file includes a Complete folder, which holds all of the work we'll carry out in this chapter. So, if at any point you need some reference material or extra guidance, check it out.
Check out the following video to see the Code in Action: https://bit.ly/3rYA6k4.
Throughout this book, we have used colliders and trigger boxes to detect hits from a bullet or a selection made in the first rendition of our shop. We have also referred to applying a RigidBody component to some of these game objects with colliders to ensure that a collision is detected. We haven't made use of Rigidbody any other way so far. However, in this section, we are going to make more use of Rigidbody by making a collision happen – the blocks will collapse by our level3 boss game object going through them.
The following image shows the cargo art assets being smashed out of the way by applying and tweaking Rigidbody components, which is what we will achieve in this section:
Let's make a start by setting up our level3 scene with some pre-made assets:
The following screenshot shows physicsBarrier in the level3 scene. Note the green outline, which shows that this is our series of box colliders, which will contain our physics.
Is Kinematic will ensure that these three game objects aren't affected by the physics in the scene. Even if we did tick the Use Gravity box, the game objects won't begin to fall when the scene starts as expected. So, whatever happens in our scene regarding collisions, these three game objects will remain still and solid so that they cage all of the physics engine's reactions.
Further Information
With a game object selected that holds a Rigidbody component, the following properties will alter the game object's behavior when it's manipulated by Unity's physics engine:
Mass: The game object's mass in kilograms (Default value: 1)
Drag: The air resistance, with zero being no resistance (Default value: 0)
Angular Drag: Air resistance based on rotation (Default value: 0.05)
More information about Rigidbody and its properties can be found here: https://docs.unity3d.com/ScriptReference/Rigidbody.html.
Now, we can bring our cargo boxes into the scene.
To reinforce cargoBulk so that it collapses at the right time, a script needs to be applied called TurnOnPhysics. This will set all of the cargo game objects from Is Kinematic that are true to false after 38 seconds, (feel free to open the TurnOnPhysics script and adjust 38 to a different number in the Update function) which is the time the boss is due to crash through the cargo boxes.
physicsBarrier and cargoBulk and their children game objects are all set as colliders. Currently, our boss is set as a trigger for when they are shot by the player. However, we don't want it to be a trigger here, as the boss will move through the cargo boxes like a ghost.
We can make boss start as a non-trigger and then, at the end of the level, turn its trigger on with the use of Timeline. To alter the Is Trigger tick box, we need to do the following:
The following screenshot shows where to right-click and load the Animation window:
Our Animation window appears along with the keyframes from the boss game object that we placed in the previous chapter. We can add two keyframes to this window to turn it on and then off with the Is Trigger box. To do this, follow these steps:
However, after doing this, something doesn't seem right. By the time we reach the end of the level, the boxes have already collapsed and when the boss game object collides with them, the boxes appear to float away. This is because the game objects in our scene aren't scaled to real-world size, but the gravity is. To make things look heavier, we can change the gravity of the project, as follows:
Here, we have Physics Manager, which is where the gravity has been set to its default world scale.
Further Information
Your project's gravity can also be changed through scripting, as shown here: https://docs.unity3d.com/ScriptReference/Physics-gravity.html.
Further Information
Physics Manager contains global settings for your project's physics. One of the many useful settings at the bottom of Physics Manager is Layer Collision Matrix. This holds all the names of the layers in your project that can and cannot collide with each other. If you would like to know more about Layer Collision Matrix, check out the following link: https://docs.unity3d.com/Manual/LayerBasedCollision.html.
If you aren't happy with the way the cargo game objects react when they're hit by the boss game object, tweak its Rigidbody property values (including the ones mentioned in the first tip, earlier in this section).
Every collider can have a physics material applied to it, which will affect an object's bounciness and friction.
Creating and applying a physics material can be done in three steps:
Further Information
More information about Physic Material can be found at https://docs.unity3d.com/Manual/class-PhysicMaterial.html.
Our game now has some physics effects applied to it. Now, each time the boss crashes through the cubes, the reaction will be different each time and not like a fixed animation. This is because all the movement is based on the Unity engine's physics.
Now, let's move on and make our game more customized for multiple platforms.
Throughout this book, we have been developing and playing our game in the Unity Editor. In this section, we are going to start making some considerations regarding what will differ between the Android and PC versions of our game. For example, mobile devices have a touchscreen, so it would be useful if our game could detect that it's being played on a mobile device and, therefore, implement the correct controls.
Also, our game has been developed with a strict 1,920 x 1,080 resolution; we have introduced flexibility with the shop scene's UI and ensured that it accommodates various aspect ratios. In this section, we will go further and make our game support various aspect ratios.
Let's get started and modify our Player script so that it supports touchscreen movement and fires on mobile devices.
In this section, we are going to revisit the Player script and add some functionality so that if and when our game is ported to an Android device, the player has touchscreen capabilities.
To allow our player to auto-fire and navigate to a touch position, we need to do the following:
At the top of the Player script, we are going to add some new variables to support the new control system.
Vector3 direction;
Rigidbody rb;
public static bool mobile = false;
The direction variable will hold the player's touchscreen location; rb will hold a Rigidbody reference for our player's ship so that it can access other properties within, and the mobile variable is simply a static switch that lets the rest of the game know the player's controls.
We need to make the game recognize which platform the game is running on so that it can implement the player's mobile controls. Unity has a platform-dependent compilation that lets us choose from a list of directives so that we can determine what platform the game is running on.
mobile = false;
#if UNITY_ANDROID && !UNITY_EDITOR
mobile = true;
InvokeRepeating("Attack",0,0.3f);
rb = GetComponent<Rigidbody>();
#endif
Within the Start function, we set our mobile bool variable to false. Then, we run a platform defined directives check to see whether we are running an Android device and not using the Unity Editor.
Further Information
If you would like to find out more about other platform-dependent compilations, check out the following link:
https://docs.unity3d.com/Manual/PlatformDependentCompilation.html.
If we are using an Android device, we fall into the scope of this special type of if statement and do the following:
To make our InvokeRepeating method fire a bullet with the use of the Attack method at 0.3 seconds, we need to modify the Attack method's if statement.
if (Input.GetButtonDown("Fire1") || mobile)
By adding the mobile variable to the if statement's condition, we can check whether the player is pressing the fire button or whether the mobile bool variable is set to true.
Now, we need to add more functionality to the Update function within our Player script, which includes two new methods we haven't coded in yet but will after the following code block.
void Update ()
{
if(Time.timeScale == 1)
{
PlayersSpeedWithCamera();
if (mobile)
{
MobileControls();
}
else
{
Movement();
Attack();
}
}
}
Our refreshed Update function contains the following:
if(camTravelSpeed > 1)
{
transform.position += Vector3.right * Time.
deltaTime* camTravelSpeed;
movingScreen += Time.deltaTime * camTravelSpeed;
}
Now, the content of the PlayersSpeedWithCamera method will run for mobile and standalone platforms. If you would like to refresh yourself on the details of the camera's travel speed, take a look at , NavMesh, Timeline, and a Mock Test.
Now, let's take a look at the second method called MobileControls, which can be found in the Player script.
void MobileControls()
{
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
Vector3 touchPosition = Camera.main. ScreenToWorldPoint(new Vector3(touch. position.x,touch.position.y,300));
touchPosition.z = 0;
direction = (touchPosition - transform .position);
rb.velocity = new Vector3(direction.x, direction.y,0)* 5;
direction.x += movingScreen;
if (touch.phase == TouchPhase.Ended)
{
rb.velocity = Vector3.zero;
}
}
}
Keep in mind that the MobileControls method is called on every frame in the Update function. Inside the MobileControls method, we do the following:
Further Information
If you would like to know more about the Touch struct and its other properties, such as deltaPosition, which is useful for measuring swipe gestures, take a look at https://docs.unity3d.com/ScriptReference/Touch.html.
Further Information
If you would like to know more about converting a point into world space, check out the following link: https://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html.
So, now, the player's ship automatically fires and can move around the screen thanks to its Rigidbody component. Now, we need to make it that when either level ends, we stop the player from firing automatically and Rigidbody no longer has an effect on the player's movement. Otherwise, when the level ends, the player's ship won't stop firing, running the risk of not being able to animate out of the level.
To fix our player from continuously shooting and being able to be moved at the end of the level, we need to do the following:
if (!gameEnding)
{
gameEndine = true;
StartCoroutine(MusicVolume(MusicMode.fadeDown));
GameObject player = GameObject.Find("Player"); // ADD THIS CODE
player.GetComponent<Rigidbody>().isKinematic = true; // ADD THIS CODE
Player.mobile = false; // ADD THIS CODE
CancelInvoke(); // ADD THIS CODE
if (SceneManager.GetActiveScene().name != "level3")
In the previous code block, we have added four new lines of code that will do the following:
Now, we need to go into Input Manager and look at the Fire1 button. Here, the left mouse button is set to the Alt Positive Button property. To fix this in the Unity Editor, do the following:
Our game is now self-aware of what device it will run on, and if the device does run on a mobile Android device, the touch controls will be implemented.
Now, let's widen the support for our game and ensure our game covers various screen ratios and screen boundaries on either platform.
In this section, we are going to do two things. The first is to make it that no matter what aspect ratio our game is running at, our player will be able to fly around. The second is to make sure that the Text UI isn't affected by the different screen ratios.
So, let's start with our first task of making our game support multiple screen ratios during levels.
In the Project window, navigate to the Assets/Script folder and open the Player script. Then, follow these steps:
// float width;
// float height;
GameObject[] screenPoints = new GameObject[2];
The array we've just added will hold two points to represent our screen's boundaries.
// height = 1/(Camera.main.WorldToViewportPoint(new
Vector3(1,1,0)).y - .5f);
// width = 1/(Camera.main.WorldToViewportPoint(new
Vector3(1,1,0)).x - .5f);
// movingScreen = width;
CalculateBoundaries();
The method we've just entered does not exist yet, so let's add this new method now.
void CalculateBoundaries()
{
screenPoints[0] = new GameObject("p1");
screenPoints[1] = new GameObject("p2");
Vector3 v1 = Camera.main.ViewportToWorldPoint
(new Vector3(0, 1, 300));
Vector3 v2 = Camera.main.ViewportToWorldPoint (new Vector3(1, 0, 300));
screenPoints[0].transform.position = v1;
screenPoints[1].transform.position = v2;
screenPoints[0].transform.SetParent(this. transform.parent);
screenPoints[1].transform.SetParent(this. transform.parent);
movingScreen = screenPoints[1].transform. position.x;
}
So, let's go through the steps of the CalculateBoundaries method and see what it does to our game:
Continuing with the Player script, we now need to update the Movement method's directional conditions so that they support our new game boundaries.
//OLD
if (transform.localPosition.x < width + width/0.9f)
//NEW
if (transform.localPosition.x < (screenPoints[1].transform.localPosition.x - screenPoints[1].transform.localPosition.x/30f)+movingScreen)
//OLD
if (transform.localPosition.x > width + width/6)
//NEW
if (transform.localPosition.x > (screenPoints[0].transform.localPosition.x + screenPoints[0].transform.localPosition.x/30f)+movingScreen)
//OLD
if (transform.localPosition.y > -height/3f)
//NEW
if (transform.localPosition.y > (screenPoints[1].transform.localPosition.y - screenPoints[1].transform.localPosition.y/3f))
//OLD
if (transform.localPosition.y < height/2.5f)
//NEW
if (transform.localPosition.y < (screenPoints[0].transform.localPosition.y - screenPoints[0].transform.localPosition.y/5f))
Each of the new if statements in the previous lines of code will hold the same purpose of taking the value from the p1 or p2 game objects to get a restriction of the boundaries of the screen. This ensures that the player ship doesn't go too far out of view.
The following screenshot shows the level1 scene, with p1 and p2 representing the new gameplay boundaries in a different resolution from the usual 1,920 x 1,080 to show the flexibility that our gameplay boundary now has:
Lastly, we need to update our PlayerSpeedWithCamera method and set the movingScreen variable to zero if the game camera isn't moving to the right.
else
{
movingScreen = 0;
}
Now, let's move on and look at the second part of this fix. Here, even though the gameplay window now supports various aspect ratios, some images and text will struggle to look cosmetically pleasing. The following screenshots show what would happen to our game's pause screen if we changed the typical 1,920 x 1,080 resolution:
As you can see, the text and images lose their scale when they're in different aspect ratios. We can fix this by doing the following:
Now, our Game window, when shown at various screen sizes, will look more in proportion.
With that, we've made our game more compatible in that it supports various aspect ratios for platforms other than a standard 1,920 x 1,080 resolution. Also, our game controls are self-aware of whether the game's being played on a PC or Android device. We also made use of the Touch struct to move our player around the scene.
In the next section, we are going to finalize our game for mobile before adding extra effects and general polish for the PC build.
In this section, we will be finalizing our version of Killer Wave for Android. Before we build our game to Android, we need to apply some fixes that will only be necessary for the Android build.
The fixes we will be applying in this section are as follows:
After we've applied these minor fixes, we will build the game for our Android device.
So, let's get started with our first task by altering the lighting.
Each scene that contains a 3D model will require lighting to be generated. The Unity Editor's lighting will differ from the lighting provided on an Android device.
With the current default lighting settings applied, the following screenshot shows the difference between both platforms. The image on the left was taken on a PC, while the one on the right was taken from a mobile device:
So, let's adjust the lighting so that both platforms have a similar level of brightness and contrast:
We will cover these two settings when we apply visual improvements, but for now, Realtime Global Illumination affects the indirect lighting that's applied to other objects to help create a more realistic, soft-colored light. Baked Global Illumination will have lights stuck on 3D assets to give the appearance of light shining on a surface, but the majority of our lights move, so this will not work as a baked light.
The following screenshots now show that the PC and mobile versions are starting to look similar:
We explained how to change the emission of a material back in Chapter 2, Adding and Manipulating Objects. Changing these values will give us the following output:
Our game now looks nice and bright on either platform. Next, we'll fix the small issue with pausing the mobile version of the game.
When it comes to playing the game on a mobile device, we will want to press the pause button. But if and when we do, the game will also consider the press as a movement command, and the player's ship will move into the top-left corner where the press was made.
So, to fix this minor issue, we will apply an extra condition to our MobileControls method, as follows:
if (Input.touchCount > 0 &&
EventSystem.current.currentSelectedGameObject == null)
The preceding code block will run a check to see whether a finger is touching the screen as before but will also check that there isn't a game object in the location when being pressed. If any of these conditions aren't met, then the player will not move.
using UnityEngine.EventSystems;
In the next section, we will do some final texture optimizations and apply a ready-made and well-earned explosion prefab.
In this section, we will be adding some optimization to our mobile version of the game by reducing the size of the textures. We will also add explosions to our enemies and player.
Let's start by reducing our textures and compressing them.
To reduce the size of the .apk file that gets installed on Android devices, as well as the overall performance increase, we can reduce the size of the textures of our game through Unity and also apply compression, which lowers the size even more.
The trick is to reduce the size of the texture but not too much; otherwise, the textures themselves will begin to blur and look cheap.
In this section, we will be reducing the texture sizes of the following:
Let's start by selecting and reducing the player ship's texture sizes and compressing them:
Continue doing this for the rest of the textures, and see what the results look like in the game by playing between the shop and level1 scenes. Do this at your own discretion.
Further Information
If you would like to know more about the textures that get imported into a project and how to adjust their quality levels, check out the following link: https://docs.unity3d.com/Manual/ImportingTextures.html.
Now, let's move on and add a ready-made particle explosion to each of our players and enemies by making some minor scripting tweaks.
The time has come to add a prefab explosion to our game objects to represent their destruction and their general effect on the boss when they are being shot at. We covered particle systems back in Chapter 4, Applying Art, Animation, and Particles. Here, we will apply some scripting so that when a Die method is called, we will instantiate our explode prefab.
To instantiate the explode prefab when an enemy dies, we need to do the following:
GameObject explode =
GameObject.Instantiate(Resources.Load("explode"))
as GameObject;
explode.transform.position = this.gameObject.transform.position;
Destroy(this.gameObject);
In the previous code block, we added two extra lines above the current Destroy function. We covered this in detail in Chapter 2, Adding and Manipulating Objects. The two extra lines do the following:
Finally, for our Player, we will add something similar but also add a delay for when player_ship gets destroyed so that we can see the explosion before we reload the scene again.
GameObject explode =
GameObject.Instantiate(Resources.Load("Prefab/explode"))
as GameObject;
explode.transform.position = this.gameObject.transform.position;
GameManager.Instance.LifeLost();
Destroy(this.gameObject);
In the previous code, we have updated the player's Die method so that it creates a prefab explosion and houses its position where the player's position is.
However, we need to add a delay in the GameManager script where the previous code block was introduced.
StartCoroutine(DelayedLifeLost());
Here, we are delaying the content from our LifeLost method. However, here, we will be using StartCoroutine to create the delay, as shown in the previous line of code.
IEnumerator DelayedLifeLost()
{
yield return new WaitForSeconds(2);
// PASTE LIFELOST CONTENT HERE
}
In the preceding code block, we have added IEnumerator. This will be executed from StartCoroutine, along with a 2-second wait. If your IEnumerator has an error underlined in the IDE. Add the library using System.Collections; at the top of the script.
Paste in the LifeLost content we cut earlier and then save the GameManager script.
The following screenshot shows our game object with particle explosions applied:
Now, the time has come to create a build of our Android platform.
In this section, we are going to set up our Player Settings and build our Unity Project for an Android device. For testing purposes, I will be using a fairly old tablet and a recent phone to see whether there are any differences in terms of the setup between the two devices.
Before setting up our Player Settings, ensure you have copies of the Java Development Kit and Android SDK installed. To check this, do the following:
Further Information
If you require any more specific information about the development kit installation process, check out the following link: https://docs.unity3d.com/Manual/Preferences.html.
Now, let's continue to Player Settings and set up our game:
Tips
If you get a Gradle build failed error, try changing Build System in the Build Settings window to Internal.
Tip
When testing the game on an Android device, you may find it distracting that your device's brightness dims when the screen isn't being touched.
We can fix this by adding the following code, ideally in the Awake function of the GameManager script, as this relates to the game's overall interaction:
#if UNITY_ANDROID
Screen.sleepTimeout = SleepTimeout.NeverSleep;
#endif
This brings us to the end of building our game for mobile. In this section, we covered setting up our lighting settings so that they matched what we were seeing in the Unity Editor. After that, we cleared up some small fixes so that we wouldn't unintentionally move the player ship to where the pause button is when we press it on our device. We also reduced the size of our apk by reducing the size of the textures for our game. This also helps with the performance of Android devices when they're playing our game. Then, we added our explode prefab and made some fixes to our script to instantiate our explosions in the right place at the right time.
Finally, we went through the procedure of setting up our Unity build file and copied it over to the Android device so that it can be installed and run.
Congratulations if you have made it this far, built the game, and everything works as expected! If not, or you met some issues along the way, don't worry – other Unity users will have had similar problems, and the solutions to them aren't too hard to find with some Googling. Now, we will start bug-testing our game.
In the next section, we'll apply polish and shine to our PC version.
In this section, we are going to focus on the PC version, where we will have more leg room to apply effects, as it's likely the PC playing this game will be more powerful than a mobile device.
We will cover things such as post-processing, where we can create pretty effects to make our game shine even more. We can do this by applying effects such as blur motion, blurring to the edges of the screen, bending the screen to give it a dome screen effect, and altering the coloring.
We'll also be taking a look at lighting and reflections so that we have a slightly modified shop scene that will hold multiple lights and make the game stand out more. In the level3 scene, we will be adding reflective assets to show off the use of these reflection probes on our art assets.
Let's start by discussing post-processing.
In this section, we will be installing and applying post-processing effects to our game. This will provide us with effects that are used in films, such as film grain, chromatic abbreviation, color grading, and lens distortion. Let's make a start by installing this package into our project.
Post Processing is installed via the Package Manager directly into our project. In the previous chapter, we installed Default Playables in a similar way. This time, we won't be going to the Asset Store; we can download and install Post Processing from Packages: Unity Registry.
The following steps will take you through the process of installing Post Processing in the project:
Our Unity project now has post-processing installed. With that, we can begin preparing some scenes for our standalone game.
In this section, we are going to make some changes to our title scene so that it supports our image and text being affected by post-processing. By the end of this section, our title scene will look more impressive, as shown in the following screenshots:
To apply post-processing to our title scene, we need to do the following:
We now need to change some property values in the Canvas game object so that the post-processing changes come from the camera's feed, not just the Canvas itself.
Next, we will add two post-processing components to our Main Camera game object.
Tip
If you want to change the Post Processing Layer component from Everything to something else, you will need to create a new layer at the top-right of the Inspector window, as we did in Chapter 2, Adding and Manipulating Objects. Give it a name such as PostProcessing and change Everything to PostProcessing to remove the warning message in the Post Processing Layer component.
The following screenshot shows our title scene Game window, along with the two Post Process Layer and Post Process Volume components and the highlighted areas mentioned in the previous steps for reference:
The following image shows an example of the level3 scene with and without the DEFAULT post-processing profile applied:
That's all of the scenes we need to implement for a post-processing profile. In the next section, we will briefly go through each of the effects that we have and can apply.
In this section, we are briefly going to discuss the effects the post-processing package offers us. By the end of this section, you will be more familiar with the effects and be able to make your own post-processing profile.
Now that we've seen what post-processing does to our game, we can talk about each of the effects. Let's start by loading up the title scene and altering what we have:
Our main focus for this section will be the Overrides section in the Post-process Volume component:
So, let's go through some of these Overrides for Post Process Volume in the Inspector window. Then, I will provide a link that I encourage you to explore so that you can play around with some of the values.
This effect creates fringes of light extending from the borders of bright areas in an image.
We can extend the content by selecting the arrow to the left of the Bloom tick box (at the top-left corner in the following screenshot):
In the preceding screenshot, we have turned all of the properties on. Simply deselect and select each property to see what influences (if any) are made to each of the properties. Also, try and change some of the values. Use the preceding screenshot as a fallback if you feel you have gone too far with the effect.
An interesting property to take a look at here is the Threshold property, where, if we lower its value to under 1.14, the bloom effect will increase. However, if we make the value too low, we can overcook it and destroy the look of our game, as shown in the following:
Hopefully, I have made you curious enough to continue playing and experimenting with the bloom effect.
Further Information
More information about the Bloom effect can be found at https://docs.unity3d.com/Packages/[email protected]/manual/Bloom.html.
Next, we'll look at Chromatic Aberration.
This effect mimics what a real-world camera produces when its lens fails to join all the colors at the same point.
The following screenshot shows our current settings:
This effect is more noticeable around the edges of the Game window. As an example, in the following screenshots, I have moved the image and its text up so that we can see these two components begin to warp more obviously:
Further Information
More information about the Chromatic Aberration effect can be found at https://docs.unity3d.com/Packages/[email protected]/manual/Chromatic-Aberration.html.
Next, we'll look at the final effect we applied to our title scene – Color Grading.
This effect alters the color and luminance of the final image that Unity produces. Color Grading has the biggest range of properties throughout all of the post-processing effects. I've split these properties up into bulleted segments:
Here, we have a choice of three-color grading modes so that we can alter the camera's final image. In the preceding screenshot, Unity gave us a warning regarding changing ColorSpace from Gamma to Linear. If you want to do this, it can be changed in Edit | Project Settings | Player | Player Settings | Other Settings.
This hosts a selection of tonemapping algorithms that we can use at the end of the color grading process.
This alters the temperature and tint of the final picture.
Here, you can adjust the Saturation, Contrast, Hue Shift, Color filter, and Post-exposure (EV) options, which, similar to the Bloom effect's Threshold property, can easily be overcooked and provide some powerfully bright or dark results.
This changes each overall image's RGB channel.
Here, the three trackballs (Lift adjusts dark tones, Gamma adjusts mid-tones, and Gain adjusts highlights) affect the overall hue of the final image.
Grading Curves is an advanced way to adjust specific ranges in hue, saturation, or luminosity in the final image.
Further Information
More information about the Color Grading effect can be found at https://docs.unity3d.com/Packages/[email protected]/manual/Color-Grading.html.
That concludes our look at all three of the post-processing overrides for the title scene. If you would like to know more about the rest of the effects that are available, check out the following link, where you can read up on the other 11 effects that can be applied to a Unity scene: https://docs.unity3d.com/Packages/[email protected]/manual/index.html.
In this section, we are going to view the different types of anti-aliasing in the Post Process Layer component. As you may know, anti-aliasing smooths the rough edges of game objects in our game to get rid of staircase effects. Unity offers three different algorithms that smooth edges.
The following modes are offered:
The following screenshots show the player's ship with different anti-aliasing techniques applied to it:
As you can see, the purpose of anti-aliasing is to take off jagged edges, but with our game, these edges aren't as noticeable, since it's full of dark backgrounds.
Further Information
If you would like to apply anti-aliasing and want to find out more, check out the following link: https://docs.unity3d.com/Manual/PostProcessing-Antialiasing.html.
Next, we'll look at creating and applying our own post-processing profiles, which we created at the start of the Applying PC visual improvements section.
In the final section on post-processing, we will discuss creating a post-processing profile. From there, you can (if you want to – I encourage you to) create your own profile and apply it to the Post Process Volume component in the Inspector window. Finally, you will be able to add/remove your own effects to alter the final look of the standalone game.
So, to create and add our own effects, I suggest that we go back to a scene that we have already prepared – the title scene:
This was an extensive overview of the post-processing package that Unity has to offer. In this section, we imported our Unity package and added post-processing components to each of our game scenes. From there, we applied ready-made profiles to customize the scenes' post-processing effects. We then lightly reviewed some of the effects that can be added to the Post Process Volume component, which was already in our scenes.
We ended this section by altering our scene's anti-aliasing properties. With this, we took the rough edges off our art assets.
I encourage you to make your own profiles, but if you feel like you need more profiles to play around with, you can purchase a compilation of profiles from the Asset Store for a small price.
In the next section, we are going to take a look at the lighting settings and apply some global illumination, lighting, and fog to our shop scene.
In this section, we are going to give our shop scene a background by adding art assets from the level3 scene and adding a red emission material.
Unity is currently working on a new global illumination lighting package, which means this version we are using will be eventually phased out. The good news is that it will be supported during 2020 LTS.
We will activate the scene's real-time global illumination, which is where the red emission material will glow on the surface of the corridor. We will also be adding extra lights to our shop display and the player ship to make it stand out more. Finally, we will add some black fog to create some darkness creeping around the glowing lights.
The following screenshots show a comparison between the shop scene before and after we complete this section:
So, let's start this section off by adding the art assets that we are going to use for the shop scene.
In this section, we are going to drag and drop some pre-made art assets into our shop scene. From there, we can continue setting up our Lighting settings.
To apply the art assets to our shop scene, we need to do the following:
The scene, when viewed from the Game window, will look as follows:
The art assets that we have brought into the shop scene should be marked as static. Specifically, it's Contribute GI that needs to be marked so that we can generate the lights we need from our red emission strips, as shown in the preceding screenshot.
Further Information
If we did have moving game objects in the scene that we wanted to be affected by the lighting of the scene, we would need to add Light Probes to update any indirect colors on that moving game object.
If you would like to know more about Light Probes, check out https://docs.unity3d.com/Manual/LightProbes.html.
Now, we need to disable any kind of light we currently have in our scene so that we don't dilute the effect we are trying to achieve:
Now, we can set up our Lighting settings so that they support Realtime Global Illumination. To do that, we need to access our Lighting window and enter some values.
Still inside the Lighting window's settings, we can lower some of the Lightmapping Settings values so that the map isn't as detailed and is also quicker to generate light on slower systems.
We will be presented with the following output in the Game window:
Further Information
We can (if we want to) check the indirect lighting that we have created from our current Lighting settings by selecting the Shaded button below the Scene tab and selecting Indirect from the dropdown (don't forget to change it back to Shaded once you're done).
Our shop scene looks overly bright red and has drowned the scene out. However, we nearly have what we want. Now, we can turn on some fog from the Lighting window to create a dark alley with the red emission bleeding through.
Our Game window will now have faded darkness in the background of our shop scene, as shown in the following screenshot:
With that, we have successfully removed the default lighting from our shop scene and applied Realtime Global Illumination from the emission material and added darkness (fog) to our shop scene's background.
In the next section, we will be discussing and implementing a small section of our level3 scene so that we can start adding art assets with reflections.
In this section, we are going to introduce the final art asset for our game. This asset will reflect the environment in the scene, as shown by the two-sphere statues in the following screenshot:
You can imagine how useful it would be to have a material that reflects its surroundings like a mirror. We are going to add the shinySphere art asset to our level3 scene and calibrate its property values to get a decent result without affecting our system's resources.
So, let's start by loading up the level3 scene:
Now, we are going to place our shinySphere asset in the scene.
The shinySphere game object should now be in a location next to the cargo blocks at the end of the level, as shown in the following screenshot:
Before we add the second shinySphere game object, let's add a Reflection Probe component to this game object, as follows:
Our shinySphere game object will now update its reflection on every frame.
Further Information
Box Projection will help improve the accuracy of the reflections given in the environment. If you would like to know more, check out the following link: https://docs.unity3d.com/Manual/AdvancedRefProbe.html.
The reflection probe can create performance issues if it's not used carefully, depending on the platform the game is being pointed toward. For example, with the previous settings, a higher resolution will show a clearer reflection but will obviously require more resources.
Further Information
For more information about reflection probes and their performance, check out the following link: https://docs.unity3d.com/Manual/RefProbePerformance.html.
To duplicate the shinySphere game object in our level3 scene, we need to do the following:
The following screenshot shows the two shinySphere game objects reflecting the environment:
If, in any other future Unity Projects, you are required to create a shiny surface, marble floor, a brand-new shiny car, and so on, making use of a reflection probe would cover these requirements.
With that, we have reached the point where our game is complete, and we've covered everything specified in the Game Design Document. Now would be a good time to build our standalone version of the game and see how well it runs. Are there any bugs? How are we going to test our game? Let's move on and see how we can tackle such issues.
We have reached the point where we can build and run our game instead of just testing our game's scenes in the Unity Editor. This section will be about not only building the game, as we did earlier for the Android version of the game, but also to see whether we have any bugs with our final build. We will also look for any potential issues along the way by using performance spikes in the profiler.
Let's start building our game and see how well it runs before we do any tests.
To build our game for a PC, we need to do the following:
Next, we need to add the aspect ratios that this game is intended for in the Player Settings... window:
Deselect the aspect ratios shown in the following screenshot:
The following screenshot shows these references highlighted:
Now, let's fix any potential issues that may arise in our game.
Imagine we have sent our game off to be bug-tested and get a response from several bug testers questioning bugs, the game's UI, and the performance of the game.
The following sections contain four reports that I want you to read and think about. We will go through the answers near the end of this chapter.
Let's start with the first bug report.
It has been reported that when our bug tester plays the PC version of the game, they can't watch an advert in the shop scene.
How can we resolve this issue?
The following screenshot shows the AD button in the shop scene:
Hint: Do we need the AD button in the standalone version of the shop scene? Is it supported by Unity?
A second report has been given to us, suggesting that when the game is completed, the player's lives don't reset.
Why is this happening and how do we fix this issue?
The following screenshot shows the player's lives counter on level 1:
Hint: Does this happen when you quit the game through the pause screen? Do the player's lives reset when all their lives are lost?
When the Android version is played on slower systems, it has been reported that level 3 runs slower than levels 1 and 2.
What amendments can be made, if any, to fix this problem?
The following screenshot shows where the game slows down:
Hint: What changes can be made that won't upset standalone or more powerful performing Android devices?
Some bug testers have reported that, when starting a game, it ends earlier than intended, with the player ship animating out of the screen.
Why is this happening and how can this be amended?
The following screenshot shows the tail end of the player ship's thrusters as it leaves the level too soon:
Hint: Which level does this happen on? Does it happen in the Unity Editor? Does it happen all the time? If not, what are you doing and what's different?
You may be able to solve some of these questions by Googling key problems. Others are more specific, and you may need to add Debug.Log() to parts of your code holding variable names so that you can see what's changed after a certain point in the game. For example, does GameManager.playerLives debug a different value than it should at certain points in the game? If you're using Microsoft Visual Studio as your IDE, you may want to start adding breakpoints and step through your code to see what changes. If you don't know what breakpoints are, I suggest that you check out the following link: https://docs.microsoft.com/en-us/visualstudio/debugger/using-breakpoints?view=vs-2019.
To potentially help with these performance issues, we are going to check out the Profiler tool and see how it can help us with checking the performance of our game.
In this section, we will be checking out one of the Unity Editor's tools – Profiler. This handy tool will show us where our game may spike in demands for system resources and/or show where our game is using too many resources at once.
Let's open the Profiler window and see its default layout before going into any more detail about it:
The Profiler window behaves like any other new window in Unity. Typically, Profiler should run well in fullscreen mode on a second screen. Otherwise, dock Profiler down with Console, as shown in the following screenshot:
The Profiler window will come alive, showing a graph and a table of information. This will be split into four sections, as shown in the following screenshot:
Let's take a look at these sections in more depth:
Let's take a further look at the profiler and how to diagnose a performance spike.
The following screenshot shows a highlighted spike (denoted by i), along with the indicator. We can uncheck each of the properties within the "GPU Usage" module (denoted by ii) to see what is causing the spike in the Frame Chart. Also, update the Module Details Panel (denoted by iii) list to show what's causing the performance spike.
In the Module Details Panel window (the "Hierarchy" section), at the top of the list, we have the culprit of what's causing the spike, UpdateDepthTexture.
With a quick Google search, we can see why this is happening and whether there is anything we can do to fix the issue. According to a topic on the Unity forums, this issue is caused by the profiler itself, which is fine, as we won't be requiring the profiler in the final build. Try to reproduce spikes, cross-compare, and check more than one answer to verify as much as possible that your spike relates. It's likely you will find a way to minimize/remove the issue.
Another way of checking this, and one that will possibly solve the issue, is to run our game outside of the Unity Editor to remove any resources being used up. One way of tackling this is to build and run (denoted by iii) our game as a standalone (denoted by i) Development Build and auto-connect it to the Profiler window (denoted by ii), as shown in the following screenshots:
As you can see, we no longer have this resource issue showing up in our Development Build (denoted by * in the preceding screenshot).
Further Information
If you would like to know more about the Profiler window, check out the following link: https://docs.unity3d.com/Manual/Profiler.html.
As we have seen, the Profiler window is a helpful tool that helps us rectify any issues with memory leaks, garbage collection, and any other possible issues.
Now, we'll look at our last Unity tool, which we can use to see how the graphics pipeline is being used to display our game.
Frame Debugger can be used to show how each frame is created for our game in the Unity Editor. This can help us with any potential shader issues regarding how a piece of art is displayed. However, this is also a healthy reminder of how a scene is brought together and challenges potentially any unnecessary effects/materials used.
To access the Frame Debugger tool, do the following:
Our Frame Debug window will appear.
The following screenshots show the Frame Debug window with the Enable button highlighted, along with three steps (4, 8, and 26):
Note that step 4 shows the image that has been applied to the Bloom texture to create the shiny glow in step 26.
After going through each of these steps and seeing all the maps, render targets, and all the other necessary steps to make a frame, it's also possible to select draw calls (a call to the graphics card) from the Frame Debug window, which will highlight the game object it's referring to.
In the following screenshots, we have the shop scene with a total of 47 steps, as shown at the top of Frame Debug. If one of the draw calls is selected within the Frame Debug window (the middle highlighted rectangle), it will ping which game object it is referring to in the Hierarchy window, as shown on the left-hand side:
Further Information
If you would like to find out more about Frame Debugger and its capabilities, check out the following link: https://docs.unity3d.com/Manual/FrameDebugger.html.
Hopefully, you will be able to make great use of Frame Debugger and debug any graphical issues and understand the graphical pipeline more with Unity.
Before we summarize this chapter, we are going to go through each of the four bug reports from our game's bug testers.
As programmers, we need to, for example, follow a value through a series of steps to see whether it's the reason why code is not doing what it's supposed to do. However, there are also different methods for carrying out testing, and it's also good to think about checking your or someone else's code after an update has been applied to the project's code.
As a programmer, you will likely hear of different types of methodology that are carried out and how much of a project's code should be tested.
Here are the more popular types of tests you will carry out on your own and other projects:
Testing often helps you to keep track of a project overall and not be solely focused on one part of it. This is also why it's important to have some kind of plan; for example, we have our Game Design Brief. We could also be even more technical and have a UML diagram to help us see the connections between our scripts. So, we shouldn't think any differently about coding. Now that we have our code, we can hopefully improve it, make it more efficient, and remind ourselves of the SOLID principles.
Speaking of bug-testing, have you thought of any solutions to the four bug reports that were made for our game back in the Tackling bugs section? Hopefully, you have, as we are going to go through each of them now.
As you may recall, we have our shop scene, which features an AD button. When pressed, the player will watch an advert and receive shop credits as a reward. This works fine in the mobile version of the game, but it had been reported that this button does not work on the standalone version.
The short answer to this is that Unity doesn't support adverts for standalone builds. This leaves us either looking for a solution to have an advert in our game or turning off the AD button game object, either through scripting or manually through the Hierarchy window. Either way, this is a simple quick fix, as removing the AD button will automatically make the Start button resize, thanks to Vertical Layout Group. Some redesign would need to be implemented to solve this issue, rather than it being solely a programmer problem:
To make the player's lives reset correctly, we need to apply a fix in the TitleComponent script so that when our game restarts back at the beginning from either quitting or the player losing all of their lives, GameManager.playerLives is reset back to 3.
In the TitleComponent script, add the following code to reset the player's lives back to 3:
void Start()
{
if (GameManager.playerLives <= 2)
GameManager.playerLives = 3;
}
Save the TitleComponent script.
The benefit of having multiple devices to run a series of tests is vital. If your game supports a low spec device, then you are also appealing to a wider audience. Reports for our game are coming in stating that the device struggles with lower-powered devices. To fix this, you need to ensure the following:
It has been reported that levels finish earlier than they should do, so instead of a level lasting 25 seconds, it has been reported to last only 5–10 seconds.
This is happening because the BeginGame method in the ScenesManager script is not resetting the gameTimer variable back to zero. Follow these steps to fix this bug:
gameTimer = 0;
Test your code, keep revisiting it, and keep polishing it. Continue to look at other ways of improving your script. Accept that the first few lines of code aren't going to be your best and that it's okay to revisit and keep optimizing your code.
Further Information
If you would like to continue looking into how to improve the code for the game you've created, check out the following link from Unity: https://learn.unity.com/tutorial/fixing-performance-problems#5c7f8528edbc2a002053b595.
That brings us to the end of this section, where we built our standalone version of our game and looked at potential issues that we needed to overcome, which we picked up with our bug testers. After that, we looked at the Profiler window, which we can use to monitor the performance of our game, and Frame Debugger, which shows what steps are followed to make a frame. We then discussed how and when to test our game, before looking at the bugs we were issued and how to correct them.
Now, let's discuss this chapter as a whole.
This chapter was about taking the game we have been developing throughout this book and putting it together as it reached its end. We spoke about how we could push our game further by adding physics collisions other than bullets or buttons. We set up collisions that got us more involved with tweaking the Rigidbody component to make our game objects behave in different ways. We did this by adding drag and affecting our scene's gravity.
We then moved on and discussed how we could improve our game's screen ratio by updating its Canvas Scaler and how it would make our UI look more stable under different ratios. We also made our game-playing area more flexible under the different resolutions using different Unity functions, such as WorldToViewportPoint.
At this point, our mobile version was ready to be built and tested so that we could see how well it ran with updated touchscreen controls. We also looked into its optimization in terms of textures and compressed them to decrease the size of our game and make it run better overall.
After the mobile build, we looked at the PC version and made some more changes to improve the look of the game. We did this because the standalone machine was likely going to have a more powerful CPU, graphics card, memory, and so on. Then, we added effects such as post-processing to change the look and feel of our game to make it more polished. We continued adding more polish to our game by adding global illumination and fogging effects from our Lighting settings window. This made our materials shine red and bleed through the foggy darkness to give them more of a futuristic feel. We also added reflective statues to our end level. These made use of the reflection probe component. After that, we discussed how to optimize it in terms of the size of its reflective texture.
Finally, we looked into building and testing our standalone version and also introduced some bug-testing scenarios, where our bug testers found issues with things not working the way they should. We reviewed and addressed them together.
Making a game isn't easy, and there are many ways in which a game can be made. Someone will always have a better way than you and likely pick holes in it. However, as mentioned in this chapter, a game can be made in sweeps and improved at each sweep; the worst thing to do is to try and make a perfect game the first time around. If you think like that, you'll end up with no game at all.
Are you ready for the full mock test? You should be; see what you've remembered by reading (and hopefully recreating) this book. If there are any problems with the questions that you can't answer, there are reference numbers next to each question (example: CH1, meaning Chapter 1) to help jog your memory. This will all be explained thoroughly in the next chapter – enjoy it.