Unity 3D Speedometers

It would be useful if we had a speedometer for our car. We'll add one that looks like this:

A 2d image of a speedometer

Before making a start, download these two images:

Drag and drop them into your project. Select both of them and then take a look at the Inspector on the right. You need to change the Texture Type at the top to Sprite (2D and UI):

Now click the Apply button at the bottom. The two files in your project area will then look like this:

Now right-click on a blank space in the Hierarchy. From the menu that appears, select UI > Image. The image will appear under a Canvas. Click on the Canvas item to select it. In the Inspector, change the UI Scale Mode to Scale with screen Size:

Select the Image again under Canvas. Rename it to Gauge. Go into 2D mode by clicking the 2D icon just under the Scene tab:

Click the Move icon just under the Unity menu and your Hierarchy and Scene should look something like this:

Move your white square to wherever you want your gauge to appear on screen (drag the red arrow to move it across and the green arrow to move it up).

Now with your gauge item still selected, have a look at the Inspector on the right. Click the tiny circle to the right of Source Image:

Clicking the tiny circle brings up the select Sprite dialog box. Double-click your gauge sprite:

The Inspector and Scene will then look like this:

Now for the pointer.

Select your gauge item in the Hierarchy. Right-click and select Duplicate from the menu. Rename it to PointerHolder. Drag and drop it onto Gauge, so that it's a child of Gauge:

Now select PointerHolder. In the Inspector on the right, remove the Image and the Canvas Renderer:

Now click the dots for the Rect Transform and select Reset from the menu:

Change the Width and Height to 0 for both. Your Inspector should look like this:

We're doing this because we only want the Rect Transform. This will be used to rotate the gauge pointer. Now we need to add the pointer itself.

Right-click on PointerHolder in the Hierarchy. From the menu, select UI > Image. Rename the image to Pointer. Make sure that Pointer is a child of PointerHolder. Your Hierarchy should look like this:

In the Inspector, click the tiny circle to the right of Source Image again. This time, select your pointer sprite:

Change the Width and Height to these values:

Width: 38
Height: 9.5

Change Pos X and Pos Y values to these

Pos X: 19
Pos Y: 0.8

Your Inspector should look like this, for the Pointer:

Your gauge should look like this:

Now let's add some text where the empty black area is.

So, right-click on the Gauge item in the Hierarchy. From the menu that appears, select UI > Text - TextMeshPro. You'll get a dialog box popping up. Import the essentials then close the dialog box down (you don't need the extras). Rename the item to SpeedText. The Hierarchy should look like this (note that SpeedText should be a child of Gauge, not a child of PointerHolder):

In Scene view, your new text will look like this:

Delete the default New Text and type 0 instead. Change the font size to 20 and click the center alignment button.

Notice the yellow rectangle holding your text. It has white squares around it. Hold your mouse down on a white square. Keep it held down and drag to resize. Use the arrows to move your text into position. You should be looking at something like this, when you're done:

Now right-click the SpeedText item and duplicate this. Rename it to KPH. Your Hierarchy will then look like this:

In the Inspector, change the text to km/h and the font size to 14. In the Scene, move your text down a bit so that it's under the zero:

Now let's do the coding to get the needle moving and display the speed.

 

Unity Speedometer Coding

Create a new C# script (you should know how to do this by now). Call it Speedometer.

Inside the curly brackets of the class, set up the following variables:

public Rigidbody theCar;
public float maxSpeed = 0.0f;
public float minSpeedPointerAngle;
public float maxSpeedPointerAngle;
public RectTransform pointerHolder;
public TMPro.TMP_Text speedLabel;

In the Update method (you can delete the Start method), add this code:

float speed = theCar.velocity.magnitude * 3.6f;
speedLabel.text = (int)speed + "";
speedLabel.alignment = TMPro.TextAlignmentOptions.Center;
pointerHolder.localEulerAngles = new Vector3(0, 0, Mathf.Lerp(minSpeedPointerAngle, maxSpeedPointerAngle, speed / maxSpeed));

Your code should look like this:

C# Unity coding for a speedometer

Notice that we've spread the last line over two lines. (It starts with pointerHolder and ends with the semicolon.) You can do this in your own code. This saves you having to scroll across to see all the code.

It all looks a bit complicated, though, so let's go through it.

We first want the Rigidbody for the car. Next, we set up three float variables: maxSpeed, minSpeedPointerAngle, maxSpeedPointerAngle. These last two are for the pointer. Next, we have a RectTransform variable called pointerHolder. This is the one we need to rotate, rather than the pointer itself. The other variable is for the text.

Inside the Update method we first get the speed of the car:

float speed = theCar.velocity.magnitude * 3.6f;

We multiply the magnitude of the car by 3.6 in order to convert it to kilometers an hour.

Next, we can set the text:

speedLabel.text = (int)speed + "";
speedLabel.alignment = TMPro.TextAlignmentOptions.Center;

This converts the speed value to an integer. But, because speedLabel can only hold text, we need to convert it all to a string. You can do this by adding an empty string on the end. The other line just makes sure that we centre the text in the label.

Finally, there's this tricky line:

pointerHolder.localEulerAngles = new Vector3(0, 0, Mathf.Lerp(minSpeedPointerAngle, maxSpeedPointerAngle, speed / maxSpeed));

If you want to rotate a RectTransform then it's best to use localEulerAngles. We're using localEulerAngles after our pointerHolder variable. The pointer itself, remember, is a child of our PointerHolder item in the Hierarchy. If you rotate the PointerHolder, it rotates the pointer.

But localEulerAngles need a Vector3 (an X, Y and Z). The first two values are 0. The final one is this:

Mathf.Lerp(minSpeedPointerAngle, maxSpeedPointerAngle, speed / maxSpeed)

We're using Lerp again to get a nice smooth value for the rotation. Otherwise, the rotation might be all jerky. For Lerp, you need three values, a minimum value, a maximum value, and then a value between 0 and 1 that smooths out the curve. The minSpeedPointerAngle variable is going to hold the lowest value for our speedometer. The maxSpeedPointerAngle will hold the highest value. You'll set these soon. The way we get the final value for Lerp is to divide the speed by the maxSpeed. Again, you'll set maxSpeed soon.

So, save your code and go back to Unity. Drag and drop your script onto the Gauge item in the Hierarchy. With your Gauge item selected, the Inspector should show you this for the script:

The first item to fill is the Rigidbody for the car. Click the tiny circle to the right of None (Rigidbody). From the dialog box that appears, select your CAR-ROOT item:

The Max Speed value is on 0. The Max Speed here is not the speed your car is going. It's really how fast you want the needle to go from its lowest point to its highest point. Just type 160 in here. You can come back and change this value to see what effect it has.

The next two values to fill are the Min Speed Pointer Angle and the Max Speed Pointer Angle. It's a little bit tricky to fill these values. But, in the Hierarchy, select your PointerHolder item. Now locate the Rotation Z item in the Inspector. It's set to a value of 0:

Hold your left mouse button down on the Z, keep it held down and drag to the right. You should see your mouse pointer rotate to the left. Lift your finger off the left mouse button when the pointer is at the starting position for the speedometer. Now note the rotation value for Z. It's this value you need to enter for Min Speed Pointer Angle. Repeat the process, only this time drag to the left instead of to the right. Stop when you get to the end of the speedometer. Note the value. Use this value for your Max Speed Pointer Angle. If that makes no sense, watch the video below.

 

In the Inspector, reset the Z value back to 0 for PointerHolder. In the Hierarchy, select your Gauge again. Click the tiny circle to the right of Pointer Holder:

From the dialog box, select your PointerHolder item (not the Pointer):

Now do the same for the Speed Label: click the tiny circle to the right of None (TMP_Text). Select your text item, which is SpeedText:

The Inspector for your Gauge should look like this (you may have different values for the pointer angles):

At long last, you can try it out!

Play your game. Drive your car and watch your new gauge. You should see the pointer move and the speed going up. You might need to drive your car off the edge of your terrain before you see the pointer go to the max value. But that depends on what Max Torque you set for your car. Anyway, the short video below shows what you should have.

 

Change the Max Speed value and see what that does to the pointer when you play your game. Change the Max Torque and Mass for the car, as well, to see what happens.

We'll move on from speedometers, though, and take a look at minimaps in the next lesson below.

<--Back to the Unity 3D Course Contents Page