Skip to content

2D Racing Game

This is a tutorial for a basic 2D racing game in Godot. This will follow on from previous learning we did in the 2D Platformer. We will be starting with a new project.

Please download the asset pack linked here (it is free so you don’t need to pay).

Making the Cars

  1. Make a CharacterBody2D, then give it 2 child nodes: Sprite2D and CollisionShape2D.

  2. On Sprite2D, drag and drop spritesheet_vehicles.png into the empty texture in the inspector.

  3. In the region drop down, enable the region, click on edit region, and then cover a single car using the red dots. Screenshot showing edited region

  4. For the Collisionshape2D, select new CapsuleShape2D and cover the car.

  5. In Project > Project Settings > Input Map, we will create other keys for the player to use. In the “add new action” box, type in something like Left1 and then click add.

  6. On the right, click the plus button and and then press a key to represent it. E.g. for left you could use the left arrow key. Repeat the steps for all 4 directions. Screenshot showing Input Map

  7. Now go into the car script and change the "ui_left", "ui_right" etc. to the inputs you made before.

  8. Attach a script to CharacterBody2D and replace the code with this:

    extends CharacterBody2D
    const ACC = 25.0
    const MAX = 750.0
    const DEC = 5.0
    var motion = 0.0
    func _physics_process(delta: float) -> void:
    # Add the gravity.
    # Get the input direction and handle the movement/deceleration.
    # As good practice, you should replace UI actions with custom gameplay actions.
    var direction := Input.get_axis("Left1", "Right1")
    if direction:
    rotation += 2 * direction * delta
    var moving := Input.get_axis("Forward1", "Backward1")
    if moving:
    motion += ACC * moving
    if motion > MAX:
    motion = MAX
    elif motion < -MAX:
    motion = -MAX
    velocity = lerp(motion * transform.y, Vector2.ZERO, 0.2)
    if velocity != Vector2.ZERO:
    if motion > 0:
    motion = max(motion - DEC, -ACC)
    elif motion < 0:
    motion = min(motion + DEC, ACC)
    move_and_slide()

    This will allow your car to turn, accelerate, and decelerate.

  9. To test this, create a new scene and give it a Node2D, give it a child node Camera2D and your car scene as a child node. If you now play the track scene, you should be able to drive around.

  10. To make the second car, all you need to do is duplicate your car scene and add more inputs to the Input Map so you can control both cars on one keyboard. Don’t forget to change the controls in the script.

Making the Track

  1. Give track1 a TileMapLayer and name it Foreground. This is where we’ll make the track.

  2. For the tileset, open the spritesheet_tiles.png. in the TileSet menu, set the texture region to 128 by 128 and in the inspector click on TileSet and change the tile size to 128 by 128. Screenshot showing TileSet size menu

  3. Now you can draw your own track.

  4. If you want to make a background, create a new TileMapLayer by duplicating the foreground, and draw using that as your base. Screenshot showing current track

Making the Menu

  1. Make a new scene with a Control node. This will be our main menu. Add a Label child node. In the inspector will be a text box - write Racing Game or whatever you want to call it. Attach a script to the menu node, we’ll use this later to change between menus. Add some Button child nodes to the Control node. I have 2, one for Start and one for Exit. For each of the buttons, connect the Pressed signal to the menu script. We’ll change the code later.

  2. In the control section of the inspector there is a dropdown for theme overrides. This is where you can get creative and change the colors, the fonts, outlines etc. Save this scene as main. Screenshot showing creative controls

  3. Make a new scene and give it a Button node. This will be our button to select tracks. Change the text the same way as the Label in the previous step. You can change the style a similar way to the text with theme overrides, but now there is an extra styles menu. You can customise the button to look however you like.

  4. Attach a script to the button. In the node menu, connect the Pressed signal to the script and change the code to this:

    extends Button
    @export var level : PackedScene
    func _on_pressed() -> void:
    get_tree().change_scene_to_packed(level)
  5. Now make a new scene with a Control node. This will be our track select menu. Add a Label child node and write “Track Select” in the text box.

  6. Now in your track select scene, you can add in the button for track one. In the inspector near the top you should see a box for Level. Right click on your track1.tscn and copy the path and paste it into that text box. Screenshot showing level variable

  7. Change the code in your main menu script to this:

    extends Control
    const TRACK_SELECT = preload("res://Scenes/track_select.tscn")
    func _on_start_pressed() -> void:
    get_tree().change_scene_to_packed(TRACK_SELECT)
    func _on_exit_pressed() -> void:
    get_tree().quit()
  8. Now when you run the main menu and click the start button, it should load the track select menu, and from here you can select which track you’d like to play. To add more levels, simply add another button and change the path in the Level text box.

Next Steps

  1. Add more levels and connect them to your menu
  2. Add items to pick up
  3. Make an end screen
  4. Make your menus more sophisticated - how could you add a settings menu?
  5. Make a character selection menu