Finish Line & Display
This content is not available in your language yet.
Adding in some of your heads up display
Section titled Adding in some of your heads up displayIn order for us to know if we are at the finish line, we will need a display for the racer. Ideally, this should include a lap counter, time, a finished screen and a crashed screen.
Setting Up
Section titled Setting Up-
Add a new scene, and add in a Control node. Name it “HUD”.
Under the Layout > Anchors Preset in the Inpsector, set this to Full Rect. This means the HUD will be the full size of the game screen.
Drag this
hud.tscn
scene into thecar.tscn
scenes’ top node (which should be VehicleBody3D). -
In the HUD scene, add a Panel node. This will be the top display bar, showing the timer, laps, and if the car is reversing.
Change the sizing of this panel using the red dot anchors to be sitting on the top of the screen with full width. So we can see behind this panel: in CanvasItem > Visibility > Modulate, change the alpha (how transparent the object is) to halfway. And change the colour to your favourite colour!
-
Accessing our future labels
Add a global group called
labels
, so that we can change each of the lap, timer and reverse labels in the scripts.
Adding laps with a finish line
Section titled Adding laps with a finish line-
In the
hud.tscn
scene: Add a Label into the Panel node.- Rename it to
Laps
. - Set the text to
Laps: 1/3
. - Change the scale to
2
, to make it more visible.
- Rename it to
-
Move it to the top left corner of the screen.
-
Add the
labels
group to theLaps
label, you should see a rectangle with a circle inside it pop up next to its name in the scene bar. -
In the
track_1.tscn
scene: Add a script on the Node3D. -
Making the finish line
Section titled Making the finish lineCreate these nodes under the Node3D (the root).
- Area3D (named “FinishLine”)
-
CollisionShape3D (with a shape of BoxShape3D)
-
MeshInstance3D (with its mesh being BoxMesh and having a size of
x=8.5, y=1, z=2
)- Add a new StandardMaterial3D with a finish line picture like this (using the Albedo texture).
You will need to play around with the UV1’s scale. The numbers I ended up with was
x=3, y=2.02, z=0
.
-
Add a signal to “FinishLine” Area3D check whether the car has exited the area (so we will be able to check if the car has done a lap). To do this, you need to go in Node > Signals tab on the right, press
body_exited(body: Node3D)
, and clickConnect
down the bottom. - Area3D (named “FinishLine”)
-
Inside our newly created script, add this code to update the laps each time. Make sure to read and understand the code!
track_1.gd # Variables to manage lapsvar maxLaps = 3 # Total laps to complete the racevar currentLap = 1 # Current lap the vehicle is on# Variable to store references to all labels in the "labels" groupvar labels# Called when the node enters the scene tree for the first timefunc _ready() -> void:# Get all nodes in the "labels" grouplabels = get_tree().get_nodes_in_group("labels")# Called when the vehicle exits the finish line area (signal from finish line Area3D)func _on_finish_line_body_exited(body: Node3D) -> void:if body is VehicleBody3D: # Check if the body that exited is the vehicle# Check if the forward camera is active (not reversing into finish line)if body.camera_3d.current == true:# Check if the car hasn't finishedif currentLap != maxLaps:# Increase lap count, reset checkpoint flag, and update the lap labelcurrentLap += 1var labelLaps = labels.filter(func(label): return label.name == "Laps")[0]labelLaps.text = "Laps: " + str(currentLap) + "/" + str(maxLaps) -
You will now be able to test it out, and each time you cross the finish line, the laps will increase!
Checkpoints
Section titled CheckpointsWe will need more precise lap counting than just checking if the car is facing forward. Checkpoints across the track will help keep count of the actual laps the car has done.
-
Add the same nodes (apart from the MeshInstance3D) and a signal like the finish line’s.
Name the Area3D “Checkpoint”.
These checkpoints will be invisible. You will need to put them around the track. For the sake of time, I have only added one ‘halfway’ checkpoint, but it is recommended to do more.
-
Then add this code to the connected function from the signal you made.
track_1.gd var passed_checkpoint = false # Flag to ensure vehicle passes a checkpoint before completing a lap# (exisiting code here)func _on_finish_line_body_exited(body: Node3D) -> void:if body is VehicleBody3D:# Check if the forward camera is active (no reversing into finish line) AND checkpoint has been passedif body.camera_3d.current == true && passed_checkpoint:# Check if the car hasn't finishedif currentLap != maxLaps:# (exisiting code here)passed_checkpoint = false # Reset checkpoint flag for the next lap# Called when the vehicle exits a checkpoint area (signal from checkpoint Area3D)func _on_checkpoint_body_exited(body: Node3D) -> void:# Check if the vehicle is the one triggering the checkpoint and the main camera is activeif body is VehicleBody3D && body.camera_3d.current == true:passed_checkpoint = true # Mark checkpoint as passed, allowing lap completion -
Now, if you turn around before passing this checkpoint and go through the finish line, the lap counter won’t increase!
Adding a timer
Section titled Adding a timer- To make a race car timer, we will need milliseconds, seconds, and minutes.
- In the
hud.tscn
scene: Copy and paste the ‘Laps’ label. Rename it toTime
and move it to the top right hand corner of the screen. Set the text toTime: 00:00.000
. - Add this code to your existing code.
If you understand the comments, feel free to delete them to reduce script clutter.
track_1.gd # Variables for timervar time = 0.0 # Total time elapsed since the start of the race in secondsvar minutes = 0 # Minutes component of the time displayvar seconds = 0 # Seconds component of the time displayvar msec = 0 # Milliseconds component of the time display# Called every frame, where 'delta' is the elapsed time since the previous framefunc _process(delta: float) -> void:# Update the total time by adding delta (time since last frame)time += delta# Calculate milliseconds, seconds, and minutes for time displaymsec = fmod(time, 1) * 100seconds = fmod(time, 60)minutes = fmod(time, 3600) / 60# Format the time as a string (MM:SS.mmm) for displayvar timeString = "%02d:%02d.%03d" % [minutes, seconds, msec]# Find the label displaying the time (assumes a label named "Time" is in the labels group)var labelTime = labels.filter(func(label): return label.name == "Time")[0]labelTime.text = timeString - Well done! You now have a working timer for your racing. Now you can see who the fastest race car driver is!
Adding in a reverse state (optional)
Section titled Adding in a reverse state (optional)- If our car is still quite symmetrical, it would be easier to add in a reversing label so we can see if we are reversing or not.
- In the
hud.tscn
scene: Copy and paste the ‘Laps’ label. Rename it toReversing
and move it next to the Laps label. Set the text toReversing...
. Set the colour to red. Uncheck the Visible (or the eye icon), as we only want it to show up when we reverse. - In our
car.gd
script, we will be able to put this label inside the script using our global grouplabels
.car.gd var labelsvar reverse_label# (existing variables)func _ready() -> void:camera_look_at = global_position# Find our labels group, and filter them until we've found our reverse labellabels = get_tree().get_nodes_in_group("labels")reverse_label = labels.filter(func(label): return label.name == "Reversing")[0]# (existing functions)func _check_camera_switch():if linear_velocity.dot(transform.basis.z) > -1:camera_3d.current = truereverse_label.hide() # We're driving forwardelse:reverse_camera.current = truereverse_label.show() # We're reversing!
Making the finished state
Section titled Making the finished state-
Once we’ve finished the race, we celebrate with a finish screen!
-
In the
hud.tscn
scene: Add a ColorRect node under the main Control node.- Rename it to
FinishedLevel
and change it’s size to the whole screen. - Change the colour to transparent(ish) green.
- Uncheck the Visible (or the eye icon), as we only want it to show up when we finish the track.
- Add it to the
labels
global group (in Node > Signals). - Add a Label as a child of ColorRect and set the text to
You've finished the track!
. Set the scale to 3.
- Rename it to
-
Inside our
track_1.gd
, make some changes to our_on_finish_line_body_exited
function, and add in astop
function.track_1.gd func _on_finish_line_body_exited(body: Node3D) -> void:if body is VehicleBody3D: # Check if the body that exited is the vehicle# Check if the main camera is active and checkpoint has been passedif body.camera_3d.current == true && passed_checkpoint:# Check if the current lap is the last oneif currentLap == maxLaps:# Display "Finished Level" label to indicate race completionvar finishedLevel = labels.filter(func(label): return label.name == "FinishedLevel")[0]finishedLevel.show()stop() # Stop processing further lapselse:# Increase lap count, reset checkpoint flag, and update the lap labelcurrentLap += 1var labelLaps = labels.filter(func(label): return label.name == "Laps")[0]labelLaps.text = "Laps: " + str(currentLap) + "/" + str(maxLaps)passed_checkpoint = false # Reset checkpoint flag for the next lap# Function to stop the timer and prevent further processingfunc stop() -> void:set_process(false)
Making the crashed state
Section titled Making the crashed state-
In the
hud.tscn
scene: Add a ColorRect node under the main Control node.- Rename it to
CrashedScreen
and change it’s size to the whole screen. - Change the colour to transparent(ish) red.
- Uncheck the Visible (or the eye icon), as we only want it to show up when we crash.
- Add it to the
labels
global group (in Node > Signals). - Add a Label as a child of ColorRect and set the text to
You crashed! Restart the game.
. Set the scale to 3.
- Rename it to
-
Inside our
car.gd
, make some changes to our_physics_process
and_ready
functions.car.gd var crash_timer = 0.0var crash_threshold = 3.0 # Time in seconds before considering it crashedvar crash_screenfunc _ready() -> void:# (exisiting code)crash_screen = labels.filter(func(label): return label.name == "CrashedScreen")[0]func _physics_process(delta: float):# (exisiting code)# if the car has crashed: Check if the vehicle is upside downif transform.basis.y.dot(Vector3.UP) < 0:crash_timer += deltaif crash_timer >= crash_threshold:crash_screen.show()else:crash_timer = 0.0 # Reset timer if not upside down
Current File Structures
Section titled Current File StructuresThe heads up display file structure:
The track 1 file structure: