Ease of Building UI Elements In Unity
Today we are going to create UI in Unity. UI stands for User Interface and it’s one of the best parts about Unity. It has never been easier to create full fledged UI for both video games as well as enterprise applications.
We are going to create a lives system and other functionality later on, but for now we are going to begin by creating a system to keep score. To get started we need to create a UI canvas. To do this we simply right click in the Hierarchy tab, scroll down to UI and select text mesh pro.
Starting with Unity 2021, TextMeshPro is the default text system used in Unity. There is not a simple text option anymore.
If you look at the drop down menu under UI, you can see that Unity has just about everything you could need when developing a UI. You can add buttons, sliders, dropdowns, input fields and many more. This system is very user friendly.
When you create the UI you are going to get two things. 1) you get the canvas which contains all of the elements you build into your interface. 2) you get the event system. The event system is what allows us to hover over things like sliders and buttons.
If the user is going to be interacting with the UI, then you must have an event system, and Unity provides it for us. We can also use the canvas to organize our UI in a similar way that we used the spawn manager to organize our enemies.
We are going to name our text Score_text because it is going to keep score.
The next thing we want to do is adjust our text inside the UI. we need to adjust the size, color, scale and position, so that it can be read during operation, but does not obstruct the gameplay.
when positioning UI elements we have this thing called Anchors. Anchors allow us to specify a corner that we want to attach UI elements to, within the UI canvas.
If we click the box, it opens up the anchor selections.
For this text we want it to always be in the top right corner. By using the anchor we can set the position and it will move and scale with the screen size. This way if we port the game to mobile, the element will maintain proper position.
We can use the scene view to position the text box in an approximate location.
Then we can go to the inspector and adjust the position using the X, Y, Z Axis.
You can see that as we scale the screen down, the text stays in the same location.
This is great, but what we really want is for the text size to scale with the screen size in addition to maintaining correct position. This is another setting we must adjust in Unity.
To do this we need to select our canvas. In the inspector tab, we have a canvas component and a canvas scaler component. On the canvas scaler component you will see UI Scale Mode. It is currently set to constant pixel size, which means that as the screen scales, the text will maintain a constant size.
We want the text to scale, so select the dropdown box and set the UI Scale Mode to scale with screen size.
Always use “Scale With Screen Size” when creating UI canvases. That way it will look clean and polished on any platform.
Next, we need to develop and implement the system to update the score. To start, we are going to develop a system where every time the player destroys an enemy, 10 points is added to the score.
We will begin in the Player Script. First we need a variable to track the score. we’ll call it ‘_score’ and int will be of type integer. We will use SerializeField so that we can see and manipulate it in the inspector.
Next we need a method to update our score. We’ll call it AddScore() and when this method is called, it will add 10 to our score variable.
To call this method we need to go to the place where we track the collision of the laser with the enemy, since we only want to add to the score when the player destroys the enemy.
We will find that in the Enemy script, in the OnTriggerEnter() method. To update the score we need to access the player script. But we cannot do that directly. The way we do that is to create a handle to the game object that we want to access and then use get component to get the correct component from the Player object.
Now this satisfies our need, but this is an expensive operation. If we destroy 50 enemies at once, then we will be making this call 50 times. That takes a lot of time and memory. The best practice in these situations is to cache this operation in global spot like in the start method, so it can be used when needed.
What we’ve done here is create a global handle for the player that can be accessed by all methods in this class. We then assigned our handle to the Player object using the ‘get component’ method. Now the Player is only called once on start, and cached in the ‘_player’ variable. Because we created a global variable, we can use it freely throughout our entire program.
To make use of it, I simply want to check if the player is alive, using a null check in an if statement. If the ‘_player’ is not null, meaning that it still exists and has not been destroyed, the we want to access the ‘_player’ and call the AddScore() method.
This operation is performed in the OnTriggerEnter method of the Enemy script. By calling that method, it will automatically add 10 to our score.
Now we have a functioning score system. If you look at the inspector tab of the Player object you will see the score variable updates as we destroy enemies.
The next step is to update the UI. Right now we have 10 points hard coded into our AddScore() method. If we wanted to add different types of enemies or other ways to get points, everything would be limited to 10 points.
So, While we are updating the UI, let’s also take a look at how to modularize the score system.
If we have different enemy types, worth different point values, then we need the enemy to tell the player how many points it was worth. To do that we can pass a parameter into the AddScore() method and use that value to update the score.
What this allows us to do is pass a different value into the method every we call it. This means that we could create a variable in the enemy script for different enemies and potentially pass a different value each time.
the last part is to communicate with the UI to update the score. We will be doing this frequently, so, again we will be using a global handle and we will cache the component we need for later use.
We created the UI manager handle above. and in the start method we used ‘get component’ and assigned the canvas UI manager component to our handle. To make sure we did this correctly, we can use a null check to see if the UI manager is NULL.
In the UI manager script we can access the score by using _scoreText.text and assign it a value of zero at the start. Then we can create a public method called UpdateScore() and append the score message with value we pass into the method.
Back in our Player script, we access the UI manager and update score method. We pass the current value of our _score variable into the method, which will update the UI on screen.
Now we have a fully functioning score system that updates on the UI and can be modularized in the future for more complex gameplay.
Now that we have our score text updating properly, let’s take a look at the logic for our lives system.
We need a system that will display the sprite on the UI and swap out the image based on how many lives we have.
To Start, we need to ad an image component to our canvas and anchor it to the top left corner. right click on the canvas, scroll down to UI, select image.
Rename it to Lives_Display_Img and drag the “Three Lives” sprite into the Source Image box.
Use the Scene view and anchor functions in the inspector view to place the image component in the top left corner.
You may notice that the image looks stretched. To avoid this we need to preserve the aspect ratio. Click on the Lives Display object and find the check box labeled ‘Preserve Aspect’.
Now we need to create the program to swap out the images. That’s where the logic and problem solving comes in.
In our UI manager, we need to maintain a reference to each of the sprites so they can be called at the appropriate time. instead of having a variable for each of those sprites, let’s create an array and place all four sprites in the array.
If we serialize the field then we can manipulate it in the inspector.
What we can do here is associate each element in the array with the number of lives our player has. In the player script we can update the lives is the UI manager using the handle we created earlier.
We send the current value of lives to the UI manager.
That value is passed into the UpdateLives() method. We use that method to call the sprite from our array for the correct number of lives. Now that we have a reference to the sprite that we want to use, we need to a reference to the Image in the UI so that we can update it on screen.
We can create a handle in our UI manager to reference the actual image and serialize the field. this will allow us to assign it in the inspector.
Now we can click on our canvas, and assign the lives display to the handle as seen below. Now we nee do swap out the sprites based on the current player lives.
The way we do that is pretty easy. We already have a method for updating the score. Let’s create a new method for updating the lives. We will pass in the current value of the player lives, and access the display lives sprite and set it based on the lives value.
Since the default value is set to three, we don’t need to call the method on start. It will appear with three lives at the beginning of the game. Instead, we will call the update lives method whenever our lives change.
This happens in the player script, inside the Damage method.
The end result is a functioning lives system.