In this chapter, we will be paying attention to our shop interface and how we can improve it visually, as well as its functionality. The current shop works well, but we could make it support multiple screen ratios. We could also introduce Unity's Event system and Button components, as well as a few other new functionalities.
The other area we will be visiting in this chapter is the in-game Heads-Up Display (HUD). This is fairly common in games where we have the game's information displayed at a particular location on the screen. We will be displaying our player's lives and score and a mini-map to show where our enemies are. This can be seen in the following screenshot:
The other half of this chapter will be about improving the 2D visuals of our shop scene so that there are choices in terms of the upgrades we can buy and so that we can also expand the size of the shop dynamically. Also, your shop scene will support any landscape ratio, unlike what it did previously. The following screenshot shows what our shop looks like in different ratio sizes:
In the previous screenshot, notice that the 3:2 screen ratio cuts off some of the screen (you will especially notice this from each screen's selection grid spacing) compared to our 1920 x 1080 (16:9) screen ratio. By the end of this chapter, our shop scene will look like the one shown in the following screenshot, no matter what landscape ratio our game is in:
In this chapter, we will cover the following topics:
Let's start by reviewing the core exam skills that will be covered in this chapter.
The following are the core exam skills that will be covered in this chapter:
The project content for this chapter can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/tree/main/Chapter_09
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, including a Complete folder that holds all of the work we'll carry out in this chapter.
Check out the following video to see the Code in Action: https://bit.ly/3LsxDWC.
With side-scrolling shooter games, it is common for us to have some form of recording of how many lives the player has, what their score is, a time limit, power-ups, and more. We are going to apply a typical HUD to show a similar set of information. Knowing about HUDs is a requirement of the Unity Programmer Exam.
By the end of this section, we will have created a HUD for our game that will consist of the following:
Before we add our HUD, we need to decide where it will sit on top of our game screen. As an example, we will pick a game so that we can briefly study how its HUD information is displayed.
We will be looking at a game called Super R-Type, which can be found at https://github.com/PacktPublishing/Unity-Certified-Programmer-Exam-Guide-Second-Edition/blob/main/Chapter_09/superRtype.jpg. Here, at the bottom of the screen, we can see that its HUD is made up of four parts, as follows:
Behind these details is a black background so that the scene doesn't interfere when it comes to reading the HUD.
So, in this section, we'll start by declaring the HUD space and giving it a dark background. To do this, follow these instructions:
From the previous chapter, we should know that a game object containing an Image component must be a child of Canvas.
So far, we have created a game object that holds an Image component.
Now, let's move on and scale this game object into its correct placeplace so that it can be used as a background for our HUD. Follow these steps:
Our background game object should be scaled to the same proportions and centered as a white bar at the bottom of the screen, as shown in the following screenshot:
Now, let's darken this background game object so that it blends in with our game.
The background game object has changed color from its default white to a dark, slightly transparent color.
The area for our HUD has been set. The following subsections will go through each segment of our HUD and explain how to create the following:
Now, we can begin filling the HUD with data that we have already made in our script, starting with the player's lives.
The player starts the game with three lives. The two typical ways of displaying the number of lives to the player are by displaying a number count or showing a little icon for each life they have. Let's go for the latter as we can use a couple of Unity components we haven't used before.
This section will also include some extra code that will be put into our GameManager script. This code will run a check to see how many lives the player has. With each life that's found, a game object will be created that holds an image.
All game object lives that will be created will be stored in a game object called lives. Let's continue working on the HUD and add the lives game object:
The last thing we need to do to the lives game object is to give it a Horizontal Layout Group component. This component will make it so that when we create an image to represent each life the player has, we'll display a spaceship image.
The Horizontal Layout Group component will put each spaceship image in a stacked order. To add this component, follow these steps:
Figure 9.8 shows the Horizontal Layout Group component when it's been added to the lives game object. We will need to alter some values of each life image so that they aren't too large.
So far, we have created a game object called lives that will store and automatically order each player's ship image.
In the next section, we are going to create a game object that will house each player's ship image. As an example of what's to come in the next two sections, the following screenshot demonstrates our lives game object holding each life game object:
Now, let's move on to making a game object called life that will store a spaceship icon.
In this section, we are going to create a game object that will hold an Image component that will be a symbol of the player ship. We will also be sizing it specifically so that it's uniform with the other lives it sits with.
Let's start by creating a game object that holds an Image component:
Our ship icon should look like the one shown in the following screenshot. If it doesn't, it may have a Default texture type and will need to be changed to a Sprite. We covered how to change this in Chapter 5, Creating a Shop Scene for Our Game:
I'm going to change the color of the icon slightly as it's possibly a bit too distracting for the player.
That's our life game object created. The final thing we need to do to it is to turn it into a prefab. As a reminder, the benefits of a prefab are that we have a game object with its components, preferences, and settings all stored. The prefab will allow us to create as many clones as required.
To turn this life game object into a prefab, do the following:
We can now delete the life game object in the Hierarchy window as we will be creating this game object with code in the next section.
In this section, we are going to revisit the GameManager script, taking the information about the player's life count and displaying it in the form of our UI system.
The following screenshot shows a section of the Hierarchy window that holds the level1 scene's Canvas game object. Within Canvas is the HUD background game object at the top, followed by the lives game object. Finally, with our code (which we will write shortly), we have created three life game objects within our lives game object:
To instantiate the life game objects so that they show the same amount as our player's lives, do the following:
The GameManager script already has an Awake() function, which is the first thing the script tries to activate once the script becomes active. What we don't currently have is a Start() function that gets called after Awake().
We can create a Start() function in GameManager and make it call a method that we are going to make shortly, called SetLivesDisplay, and send it our playerLives variable, which is the count of the player's lives.
Like any function, we can place it anywhere within the class, as long as it's not inside another method/function. I typically keep my Awake() and Start() functions near the top of the GameManager class. To call the custom method in the Start() function, do the following.
Now, we'll fill in the content for the SetLivesDisplay method.
I have put my SetLivesDisplay near the bottom of the GameManager script, but like the Start and Awake functions, you can put them wherever you wish in the GameManager script.
public void SetLivesDisplay(int players)
{
This method is set to public because our ScenesManager script will need to access it to load whatever level the player is on. We have our SetLivesDisplay method set to void as we aren't returning anything while in the method. As I mentioned previously, we take in the playerLives integer but we will refer to it as players while in the method.
Let's continue by adding some code inside the SetLivesDisplay method. This is where we will be checking, adding, and visually removing lives if the player dies.
if (GameObject.Find("lives"))
{
GameObject lives = GameObject.Find("lives");
if (lives.transform.childCount < 1)
{
for (int i = 0; i < 5; i++)
{
GameObject life = GameObject.Instantiate(Resources
.Load ("life")) as GameObject;
life.transform.SetParent(lives.transform);
}
}
In the previous code, we ran a check to find a game object called lives. If we find the game object, we store its reference in a game object called lives. We then ran a check to see if our lives game object is holding any game objects. If lives isn't holding any game objects, we are assuming this is the beginning of the level and that we need to create some lives. Inside the if statement, we ran a for loop with a limit of 5 counts. Inside this for loop, we instantiate our life prefab and let it sit inside the lives game object.
//set visual lives
for (int i = 0; i < lives.transform.childCount; i++)
{
lives.transform.GetChild(i).localScale = new Vector3(1,1,1);
}
//remove visual lives
for (int i = 0; i < (lives.transform.childCount - players); i++)
{
lives.transform.GetChild(lives.transform.childCount - i
-1).localScale = Vector3.zero;
}
}
}
There are two main parts to the code we just wrote. The first for loop is set by the count of how many game objects sit under the lives game object. Each game object under lives gets scaled to 1.
The second for loop takes the count of game objects under lives and subtracts it against the player's int variable that is brought into the parameters of this method. Inside this second for loop – depending on how big the player's int variable is – each life prefab is shrunk to 0. Scaling the life prefab to 0 doesn't affect the Horizontal Layout Group spacing, allowing the lives counter to not fluctuate based on the number of lives shown.
GameManager is now capable of creating a life meter at the bottom of the level1 scene. We now need to add some functionality so that ScenesManager loads the number of lives when the level is loaded.
To get the ScenesManager script to load the player's lives when a level starts or when the player dies, do the following:
void Start()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
Still in the ScenesManager script, we will add the Unity-recognized function, which will automatically take Scene and LoadSceneMode types, even if we aren't going to do anything with them.
Inside the function, we are calling the GameManager script's SetLivesDisplay, along with the number of lives the player has.
private void OnSceneLoaded(Scene aScene, LoadSceneMode aMode)
{
GetComponent<GameManager>().SetLivesDisplay(GameManager.playerLives);
}
Let's check what we have made:
The following screenshot shows the game being played, alongside the player's lives in the bottom left:
In this section, we have hooked up the player's lives so that they can be displayed in the bottom-left corner of the HUD. We have applied components such as Horizontal Layout Group and Layout Element to set the player's lives images in uniform order and size. We also made the code apply and update the player's lives whenever the scene loads up.
Next, we will focus on the other side of the HUD and display the player's score.
In this section, we will be applying the player's score on the right-hand side of the HUD, which we are currently filling up with information about the player.
We will continue to work in the Canvas game object and add another game object called score. Here, we will add a Text component and update a small section of the ScenesManager code to load the score display. Let's get started:
With the score game object renamed and located inside the Canvas game object, the next thing we need to do is to size and move the score game object into position.
With the score game object in the correct position and scale, we can now customize its Text component settings.
With the score game object still selected, make the following changes to its Text component in the Inspector window:
The last property to change is the Color property of the text. We will set this to the same color as our player's lives.
The following screenshot shows what our Text component properties have been set to:
If we check the Game window, we should see that the score in the right-hand corner is a good size, as shown in the following screenshot:
The final phase for our score game object is to update our ScenesManager script by adding an if statement to check whether the score game object is in the scene.
To update the ScenesManager script so that it supports our new score game object, do the following:
if (GameObject.Find("score"))
{
GameObject.Find("score").GetComponent<Text>().text =
GetComponent<ScoreManager>().PlayersScore.ToString();
}
As briefly mentioned in the newly added piece of code, we are checking whether the score game object is in the scene. If score is present in the scene, then we grab its Text component and apply the player's score integer to it from the ScoreManager script. Also make sure you have the using UnityEngine.UI at the top of the script to access the Text component.
Speaking of the ScoreManager script, we need to load this script back up so that its ResetMethod resets the score UI at the start/end of each game. Follow these steps to do so:
Inside the script, we need to bring in the UnityEngine.UI library so that we can make changes to our game's visual score.
using UnityEngine.UI;
public void ResetScore()
{
playerScore = 00000000;
UpdateScore();
}
public void SetScore(int incomingScore)
{
playerScore += incomingScore;
UpdateScore();
}
void UpdateScore()
{
if (GameObject.Find("score"))
{ GameObject.Find("score").GetComponent<Text>().text = playerScore.ToString();
}
}
Our score game object will now update when we destroy the enemies, as shown in the following screenshot:
In this section, we took the existing ScoreManager code that was originally displaying the player's score and made it into a Console window in the Editor. Now, it sends the score variable to the new HUD score in the level1 scene, which will update when an enemy is destroyed.
The final piece we need to create for the HUD is the mini-map, which will give us a visual of the enemies in our level.
In this section, we are going to fit a mini-map inside the HUD display to show a larger scope of the level. This will display the player, along with the enemies nearby, in a radar style. The following screenshot shows a radar in the middle of the HUD that represents the player, along with the enemies around them and other enemies that are due to enter the player's screen:
We will break down the mini-map into three sections:
Let's start by creating an extra layer so that we can expose certain game objects to our radar camera.
In this section, we will add an extra game object to our player and enemy game objects so that our second camera will only see the attached sprites. These will look like blips on a radar.
To add our radar blips to the game objects, do the following:
Now, we can add a radar point to the player and enemies. Let's make a start by bringing the player into the scene and updating its prefab so that it will be recognized by the radar camera. To do this, follow these instructions:
With the Transform properties set, we can now drop the radar dot sprite into the Sprite field and change its color:
We have now set the player ship so that it's ready to be detected by the radar camera.
The next thing to do is repeat the same methodology for the enemies, which are located in Assets/Prefab/Enemies.
Tip
In the Inspector window, click on Overrides | Apply All once you have finished making changes to your prefab.
Once we have finished making our changes and applied them to the prefab, we no longer need the enemy_wave game object as we have saved its details in the prefab.
We have effectively created a tracker (radarPoint) and attached it to the player and enemies for our level.
The next step is to add a Render Texture, which will work with a second camera in our scene. The feed from the second camera will be fed into a Render Texture. This Render Texture will then be placed at the bottom middle of the screen and display our player and enemy location.
Render Texture is typically used to hold moving images while in Play Mode (at runtime). We are going to use this Render Texture to hold the second camera's feed. This will work like a little TV screen in the center of our HUD.
To create and customize Render Texture, we will do the following:
Tip
If you don't have an open space to right-click, as step 2 suggests, you can change the size of the icons to gain space.
Change your icon size with the slider in the bottom-right corner of the Project window.
The last part of setting up Render Texture is to place it into the HUD. Follow these steps:
The radar game object will work as housing for anything related to the game object.
Moving and resizing the radar game object will give us a letterbox window for Render Texture to sit inside, as shown in the following screenshot:
We can now add another game object that will be a child of the radar game object we've just made. This game object will store Render Texture:
Next, we need to apply radar Render Texture to the Raw Image Texture field:
That's our Render Texture made and set. Now, we can pass this into the second camera. But before we do that, we need to add the camera!
In this section, we will be adding a second camera so that we can only see the radarPoint game objects.
Let's start by setting up a second camera in our level1 scene:
Still in the Inspector window and with our RadarCam selected, we need to change its Camera component settings to the following:
Now, if we click Play in the Unity Editor, we will see the radar in the HUD with its red tint showing red dots for enemies and a neon blue for the player, as shown in the following screenshot:
This mini-map was created without any code and made use of two new components: Render Texture, which will hold the second camera's feed, and a Raw Image component, which will display the final output.
In this section, we created a functioning HUD that has three main segments: the player's lives, a mini-map, and the player's score. We used the two fundamental UI tools that Unity offers to create a UI display. However, we also introduced three new components, as follows:
The following screenshot shows the final HUD:
Because we have updated our level1 scene, we need to update level2 and level3. The quickest way to do this would be to delete level2 and level3 and duplicate level1, as we did before, which leaves us with updating the level number in the Text component. We did this in the previous chapter, right at the end, so please check that if you need some guidance.
Now, we will move on and improve the existing shop scene by removing the pre-made polygons for UI components. This will also introduce us to using UI event triggers and making our code smaller and more efficient.
In this section, we are going to take our current shop scene and make it compatible with various screen ratios. Currently, our shop visuals are made out of polygons, which look fine, but, for example, our selection grid of buttons at the bottom of the screen has the risk of being clipped off at the edges. We can also change the way we select our buttons by using Unity's Button component, which works within the Canvas.
Because of these UI changes, this will cut our code down and make it more efficient as we will be relying on click events. We will cover these later in this section.
Let's make a start by replacing the selection grid at the bottom of our shop scene.
In this section, we are going to remove all of the shop buttons and replace them with a Horizontal Layout Group set of buttons to add the player's lives to the HUD. Each of the new selection buttons will contain a Unity pre-made script called button that has its own raycast system. This raycast system will give us an easier way of adding and customizing our buttons when it comes to adding or extracting buttons to/from the selection grid.
In the next section, we will support this change by removing our 3D assets so that we can replace them with Unity's own 2D buttons.
Let's start by removing the old selection grid at the bottom and our BUY ? button as that follows the same suit from our shop scene:
Press Delete on the keyboard if you are presented with a window to open the prefab. Open it and repeat the process. Once deleted, press the back button in the top left corner of the Hierarchy window. We haven't lost any of our sprite images, scripts, or any other type of information. We are simply removing polygons, 3D materials, and colliders (physics-based components). We are now going to move the same information we have into Canvas.
To create a Canvas with its own background, do the following:
The following screenshot shows the backGround game object positioned and scaled with a red tint:
We can now move on to the next section, where we will add three game objects that will control the position and scale of the button game objects.
In this section, we will add game objects that will support the spacing of the buttons we add to the grid. The benefit of this is that we can control the properties of each section of the buttons, as shown in the following figure:
Next, we will make an empty game object and add a Horizontal Layout Group to it, which will keep our top row buttons in order:
Now that our gridTop is positioned correctly, we can add a Horizontal Layout Group to it:
gridTop will now automatically order the top row of upgrade buttons.
We now need to repeat the process for the bottom row, without repeating the entire procedure. Follow the same steps for gridTop but make the following changes:
Our new, reworked selection grid now supports the creation of multiple self-scaling buttons. In the next section, we will demonstrate how to create multiple buttons that scale themselves to fit in the selection grid.
In this section, we are going to create a button that won't need any sizing changes to be made to it as the layout groups we placed in the previous section will take care of this.
To create a UI button for our new selection grid, right-click the gridTop game object in the Hierarchy window and do the following:
We will get a button that will be stretched and out of place, but don't worry – this is normal. Later, when we add more buttons to this and the other rows, the buttons will snap into place and scale in size automatically.
By default, the button comes with an Image component with rounded-off edges. For cosmetic purposes, this doesn't suit our scene. We can remove this by doing the following:.
The button no longer has any color.
Next, we are going to fill this game object with five game objects. In brief, their names and properties are as follows:
The following screenshot shows all of these game objects combined to create our new shop button:
Tip
The other way of changing a button's condition is by using Unity's Button component states. For more information about this and the Button script, check out https://docs.unity3d.com/2017.3/Documentation/Manual/script-Button.html.
Let's start by adding an outline game object for our new shop button:
The shop button will now have a colored outline. Now, let's move on and look at the button's backPanel.
Let's add backPanel to the 00 game object:
With the backPanel game object still selected, we can change the Image component's Color properties in the Inspector window. By clicking the Color field, we can change the backPanel game object's settings to R: 40, G: 39, B: 36, A: 255. That's the second game object that we've applied that gives us our default color.
We'll add the selection game object to the 00 game object next.
To create the selection button, follow the same steps provided in the previous section. However, note that there are three differences:
Information
We covered creating and applying tags back in Chapter 2, Adding and Manipulating Objects.
The following screenshot shows our selection game object's Tag and Rect Transform property values:
That's the third game object that we've applied to our 00 game object. Our buttons will light up and stay red until a purchase is made or a different button is pressed. We'll add the powerUpImage game object to our 00 game object next.
To create the powerUpImage button, follow the same steps provided in the previous section, but make three changes:
That's our fourth game object that displays each button's icon.
We'll add the itemText game object to the 00 game object next.
To add the itemText game object to our 00 game object, do the following:
That's the fifth and final game object we need to add to our weapon upgrade button.
In the Hierarchy window, our 00 game object should be in the order shown in the following screenshot. If the order isn't the same, simply click and drag either one into position:
In this section, we stripped out the old shop scene setup where we were selecting items in the shop with a raycast system. We replaced the old selection grid with a 2D interface with Button components. These buttons were grouped with Unity's Horizontal and Vertical Layout Group components. The benefit of these two groups is that if we add more or fewer buttons to the grid, the buttons will reorganize their position and scale automatically.
We need to make a slight modification to the ShopPiece script that was originally attached to each game object button.
Once we have applied and modified the script, we will check what the buttons look like in the new selection grid.
Let's briefly recall the purpose of the ShopPiece script. Each button in the selection grid will be given information from a scriptable object that will customize the button's name, description, value, and image. Because the buttons have changed from being 3D assets to 2D ones, we need to alter and add some more code to make this work.
To modify ShopPiece so that it's compatible with our new 2D button, do the following:
The first line of code will allow our new code support to grab references from the Text component on the 00 game object.
using UnityEngine.UI;
The second modification to make will be to replace the content of the Awake function. The original code accessed SpriteRenderer, which was used for accessing the sprite on each polygon button. The other piece of code we are replacing applied changes to the TextMesh component, which displays 3D text.
void Awake()
{
}
We can now enter the first if statement, which applies our scriptable object icon image to our button's image.
if (transform.GetChild(3).GetComponent<Image>() != null)
{
transform.GetChild(3).GetComponent<Image>(). sprite=shopSelection.icon;
}
The if statement grabs a reference from the second child in the 00 button and checks to see whether it has an Image component. If it does (and it should), we apply the scriptable object icon to it.
if(transform.Find("itemText"))
{
GetComponentInChildren<Text>().text = shopSelection.cost.ToString();
}
The if statement makes sure the 00 button has itemText (it should). When the itemText game object is found, its Text component receives the scriptable object price of the weapon.
The following screenshot shows the ShopPiece script with a scriptable object applied to it:
We are now in a position to check what our button looks like with the four game objects we've applied and with its modified ShopPiece script.
In the next few sections, we are going to duplicate a series of the new shop buttons. These shop buttons will automatically fit in the allocated game object space we have put them in. Then, we will clear up any of the old UI and replace it with our new interface. Finally, we will comment out the old raycast system from our code and add our new interface code.
In this section, we will be reviewing the new 00 button in the gridTop game object. The button is too big and spreads across the majority of the Canvas, as shown in the following screenshot:
But if I select the 00 game object in the Hierarchy window and press Ctrl (command on Mac) and D to duplicate the game object a few times, the button will divide equally, as shown in the following screenshot:
The button divides well and can be duplicated several times (not in Play Mode) to fill the top and bottom grids. To fill up and name the grids, do the following:
Now, we need to fill up the bottom row with buttons. To do that, follow these steps:
The following screenshot shows the top and bottom rows filled up:
Because we don't have any more items to sell in our shop, the bottom three buttons look odd, so let's replace these with some sold-out signs. This can easily be achieved with our scriptable object assets.
To create a sold-out sign for our bottom row in our shop, we need to do the following:
Lastly, apply the SoldOut file to game objects 03, 04, and 05 in the Hierarchy window in the Shop Piece component field's Shop Selection in the Inspector window.
Now, we need to repeat a similar process for our advert and start buttons.
To recreate the advert button, select either one of the buttons we duplicated in the Hierarchy window and do the following:
The following screenshot shows the Hierarchy window of the gridOther game object and its content, including the two buttons:
Now that our selection grid visuals are completed, we can move on to the description panel and partially convert it from 3D into 2D.
To add the 2D BUY? button to the description panel, do the following:
Now that the BUY? button is in place and scaled correctly, we can alter the aesthetics for the Image and Button components. In the Image component, select the remote button for the Source Image field and select None from the list to remove the curved edges for the button.
Next, we will make it so that the BUY? button changes colors when it's highlighted and pressed in the Button component. Follow these steps to do so:
Finally, for the BUY? button, we need to alter its Text component, as follows:
The following screenshot shows our BUY ? button positioned and styled:
In this section, we applied Unity's different state settings for our button without adding any extra code. Next, we will add a simple rectangle image to replace the polygon quad.
In the previous section, we changed our BUY ? button so that it is 2D and part of the Canvas, which also means the BUY ? button will now be moved, scaled, and adjusted to the ratio of the screen instead of remaining static. Because of this, we have the risk of our BUY ? button moving outside of the static textBoxPanel it sits in, as shown in the following screenshot:
Also, the PlayerShipBuild script has a reference to textBoxPanel, so we can't delete the game object without altering our code. To fix this dilemma, we can remove the 3D components of textBoxPanel, leaving it as an empty game object to house other game objects within it.
To remove the components from the textBoxPanel game object, do the following:
The following screenshot shows the locations of both three-dot icons:
Now, we can create the 2D panel game object to replace the textBoxPanel game object's visuals, as follows:
We can change the color of our panel game object by clicking its Color field within its Image component in the Inspector window and giving it the values highlighted in the following screenshot:
Finally, we can amend our textBoxPanel and bank balance fonts so that they fit in with the shop buttons.
To amend our bank balance, we need to do the following:
To change our textBoxPanel, we need to do something similar. Here, we will select its two child game objects in the Hierarchy window, name and desc, and update their Text Mesh components to the following:
officer:
will you need any
upgrades before
launch?
The following screenshot shows a section of the Game window and its updated font:
Now, all of our shop's visuals have been amended and will support various screen ratios. By doing this, we also introduced Unity's own Button component.
We have now reached the point where we can open a template script of PlayerShipBuild from our chapter's project files folder. This script will be a replica of the current PlayerShipBuild script we have been making but with highlighted code we will add to the project to support our shop scene's functionality.
In this section, we are going to replace the current PlayerShipBuild script with the one from this chapter's project files folder. The replacement script will contain the same code as your current script but with code to show what we will be adding and removing step by step.
Let's rename our current PlayerShipBuild script to something else before we begin working on our new replacement script. To rename the current PlayerShipBuild script, do the following:
Now, we need to disconnect the PlayerShipBuild_OLD script from the shop game object.
With that, we have renamed and detached the script from the scene. Now, we can bring in the new replica PlayerShipBuild script from this chapter's project files folder.
To hook up the new replica PlayerShipBuild script from our project files folder, do the following:
We can now apply this replica script to the shop game object in the scene. Let's get started:
Our new PlayerShipBuild script is now in place. This means we can now open the script and check through and reveal new sections of the code while explaining the fundamental parts of the old code's removal.
Each of the following Removing the old… subsections will do the following:
In this section, we are going to go through the newly installed PlayerShipBuild script and review parts of the code I have commented out so that it won't be acknowledged when it's compiled and executed in Unity.
We will be turning off the ability to raycast a 3D object, which we coded in Chapter 5, Creating a Shop Scene for Our Game. Because we have swapped the interactable game objects from 3D to 2D, we are no longer required to shoot and identify game objects as Unity will take care of this with its own Button component.
To review the code we have commented out, go to the Project window and double-click the PlayerShipBuild script located where we left it (Assets/Script).
Information
Commenting, Comments, and UnComment are words that refer to when a piece of code has two forward slashes in front of it. These will be ignored when our code is read by the compiler (when we run our code).
We are going to review each piece of code in separate sections so that it's clear when the changes we are going to make in PlayerShipBuild will be applied.
Each main chunk of code begins with //REMOVED, followed by a number. Here are the reasons why we have removed the particular piece of code for //REMOVED 01:
Let's continue scrolling down the PlayerShipBuild script until we get to //REMOVED 02.
In this section, we are going to review what we have commented out in //REMOVED 02:
Let's continue scrolling down the PlayerShipBuild script until we get to //REMOVED 03.
In this section, we are going to review what we have commented out in //REMOVED 03:
Let's continue scrolling down the PlayerShipBuild script until we get to //REMOVED 04.
In this section, we are going to review what we have commented out in //REMOVED 04:
Let's continue scrolling down the PlayerShipBuild script until we get to //REMOVED 05.
In this section, we are going to review what we have commented out in //REMOVED 05:
In the previous sections, we reviewed and amended the way we interacted with the old shop scene's raycast system.
The next phase is to apply methods that can be called directly via an event when a button is pressed in the shop scene.
In this section, we are going to build two main parts so that we can set up our script for 2D UI selection. Thankfully, we have done most of the work for this chapter and all that remains is to make parts of the script public so that our code can be accessed from other sources, that is, our event trigger (OnClick()).
The second thing we are going to do is make our AttemptSelection method receive the game object button so that it will replace the previous target game object.
To confirm this, the target game object was originally used to store ray hits from our raycast system. If you would like to know more about raycast systems, check out Chapter 5, Creating a Shop Scene for Our Game, if this sounds hazy.
Let's start by making the PlayerShipBuild script's methods accessible to other classes:
Information
By default, the accessibility levels for our methods/functions and classes are set to private unless stated otherwise. For more information about accessibility levels, check out the following link: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/accessibility-levels.
These methods are now open to other scripts and the Unity Editor via the Inspector. We will cover this in the next section, but before we do that, we need to amend our AttemptSelection method.
AttemptSelection will be given the same treatment with regard to being a public method, but it will now also take a game object in parameters, which will be the button our script is attached to.
public void AttemptSelection(GameObject buttonName)
{
Inside this AttemptSelection method, we check buttonName instead of what we did before by checking target. We then follow the same procedure of turning off any buttons highlighted, then apply the buttonName game object reference to another game object called tmpSelection, which was originally set in the Select method.
if (buttonName)
{
TurnOffSelectionHighlights();
tmpSelection = buttonName;
Continuing with the next line of code inside our method, we set the button's child selection game object to active (switch it on). The following screenshot shows the child number of the selection game object in the Hierarchy window:
tmpSelection.transform.GetChild(1).gameObject.SetActive(true);
UpdateDescriptionBox();
//not sold
if (buttonName.GetComponentInChildren<Text>().text != "SOLD")
{
//can afford
Affordable();
//can not afford
LackOfCredits();
}
else if (buttonName.GetComponentInChildren<Text>().text == "SOLD")
{
SoldOut();
}
}
}
Remember that if you get stuck with this part, you can always check the Complete folder for this chapter, where you'll have access to the completed files.
So far, we have removed multiple chunks of code and replaced them with a minimal amount that now supports the event triggers from the Unity Editor. This will help with performance and improve the readability of our code. In the next section, we are going to let each of the UI buttons know what methods to run when a selection is made.
In this final section, we are going to make it so that when the player presses a button in the shop, they will get access to it immediately, instead of our script shooting a ray and checking whether and what collider it has made contact with to get access to its method. We will be doing this using Unity's Event system to run methods directly.
To make a button run a method directly, follow these steps:
The On Click () panel has been updated with the 00 game object. Now, we need to direct what function from 00 it should load.
We will call the AttemptSelection method by making a request when we tap/click one of the shop scene's buttons.
To make our 00 button load the AttemptSelection method, do the following:
The last field to add within the On Click () panel is the button we want to put through AttemptSelection.
So, when the player presses the 00 button, our On Click () event will run the PlayerShipBuild script from the shop game object. Then, it will run the AttemptSelection method, taking the 00 game object as a reference in parameters.
Things are slightly different for our START and AD game object buttons (sat in the Hierarchy window).
To make our AD and START game object buttons work in the game, do the following:
The very last button to change is the BUY? button. Follow the same principles that we used before and select the BUY? game object button in the Hierarchy window:
Note that we don't apply event listeners to our bottom row of buttons (Sold Out) as there is no reason to press these buttons.
Our shop scene is now ready to test. Save the scene and press Play in the Unity Editor to try out our new shop buttons. It would also be worth testing different landscape views in the Game window to see the UI buttons pop into shape when a landscape ratio is selected.
The following screenshot shows the steps you have to follow to change the ratio. Do this by clicking the Game tab in the Unity Editor, followed by making a selection from two fairly common ratios:
In this section, we reevaluated our code and took out the old raycast system, which involved selecting 3D game objects to run methods. We replaced this with Unity's Event System, complete with Button components that were dynamically organized with the Horizontal and Vertical Layout Group components.
Now, the UI is more robust since it supports different screen ratios. This will make our game more compatible with a variety of mobile and tablet screens that are old and current, as well as portable devices that haven't been released yet. This helps future-proof our application without any embarrassing ratio issues occurring.
In this chapter, we looked into two different parts of our game: the in-game HUD and rebuilding our shop scene. Both of these used Unity's UI components but in different ways.
In the in-game HUD section, we read up about what a HUD is and how we can incorporate one into our game. By doing this, we learned how to use Horizontal Layout Group to order images correctly, Render Texture to take a feed from a second camera, and Raw Image to display a feed from Render Texture.
Most importantly, as required by the Unity Programmer Exam, you need to understand what a HUD is and how to build elements into it, such as a mini-map.
In the second part of this chapter, we reviewed our current shop scene's interface and code. We took it apart and rebuilt its interface as a Unity Event system that ran methods directly instead of casting a ray to call a method. We also made the interface support multiple ratios.
With the skills covered in this chapter, you should feel more confident in reviewing and understanding code that could be made more efficient.
In the next chapter, we will continue working on our in-game level so that we can pause the game, add and change the volume of our music and sound effects manually, and more.