Speedway Secrets: Racing Tutorial - Part 1

Welcome to the first installment of our game development series, "Speedway Secrets," where we dive into the thrilling world of racing game creation using Game Maker. Today, we'll explore two essential functions that form the backbone of racing mechanics: scr_build_pointlist and scr_get_point_ratio. These functions allow us to create dynamic race tracks and calculate positions efficiently, which are crucial for a fluid racing experience. Before we start, go ahead and create a new path asset and give it any shape you desire -- we will turn it into a racetrack.

Building the Track

In any racing game, the track is where the heart of the action lies. Our first function, scr_build_pointlist, is designed to construct the framework of our race track.

function scr_build_pointlist(pthname){
    global.track_points = [];
    var tt = 0;
    for(var j = 0; j<1; j+= 0.01){
        track_points[tt++] = [path_get_x( pthname, j ), path_get_y( pthname, j)];
    }
} 


Purpose: This function generates a detailed list of points along a path defined in Game Maker. Here's how it works:

  • Input: pthname — the name of the path you've created for your track in Game Maker.
  • Process: It iterates through the path, starting from 0 to 1 (the beginning to the end of the path), with a step of 0.01. For each step, it captures the X and Y coordinates on the path and stores them in the global.track_points array.
  • Outcome: A comprehensive array of coordinates that outlines your race track.

Why it's crucial: This method allows us to map checkpoints to the path.

Determining Position

Once we have our track laid out, it's vital to track the racers' positions accurately. That's where scr_get_point_ratio comes into play.

function scr_get_point_ratio(ptx, pty){
    var outind = 0;
    var dist=100000;
    for(var j = 0; j<array_length(track_points); j+=1){
    var new_dist = point_distance(ptx,pty,track_points[j][0],track_points[j][1]);
    if(new_dist < dist){
        dist=new_dist;
        outind=j; 
    } 
    var ratio = outind / array_length(track_points);
    return ratio;
}

The scr_get_point_ratio function allows us to calculate the real time position of any point in the world relative to the path (returning a ratio between 0 and 1). We can use this function to test whether a racer passes through a checkpoint, or their placement relative to their peers.

Continuing with our Speedway Secrets series, let's delve into a crucial aspect of racing game development: managing the track and implementing checkpoints. 

OBJ Track Manager

Lets create an object that manages to track and in the create event. We will use the code snippet below for setting up the checkpoint system. 

// Create event for obj_track_manager
scr_build_pointlist(pth_test);
track_position = 0;
checkpoint_counter = 0;
checkpoint_count_max = 5;
lap_count = 0;
//======================= save all checkpoint positions ==================//
/// draw all checkpoints
checkpoint_lines = [];
checkpoint_half_width = 120;
/// generate points so we can draw checkpoints evenly distributed
for(var i = 0; i < checkpoint_count_max; i++){
    var ratio = (i+1)/checkpoint_count_max;
    var ratio2 = (i+0.99) / checkpoint_count_max;
    var pos = [path_get_x(pth_test,ratio),path_get_y(pth_test,ratio)];
    var pos2 = [path_get_x(pth_test,ratio2),path_get_y(pth_test,ratio2)];
    var dir = point_direction(pos2[0],pos2[1],pos[0],pos[1])+90;
    var dx = lengthdir_x(1, dir);
    var dy = lengthdir_y(1, dir);
    
    checkpoint_lines[i] = {
        range: checkpoint_half_width + sprite_get_width(spr_car)*0.5,
        x1: pos[0]-dx*checkpoint_half_width,
        y1: pos[1]-dy*checkpoint_half_width,
        x2: pos[0]+dx*checkpoint_half_width,
        y2: pos[1]+dy*checkpoint_half_width,
        xmid: pos[0],
        ymid: pos[1],
        width: i == checkpoint_count_max-1 ? 12 : 6,
        draw: function(_a=0.2){
            var _w = self.width;
            draw_set_alpha(_a);
            draw_set_color(c_ltgray);
            gpu_set_blendmode(bm_add);
            draw_line_width(x1,y1,x2,y2,_w);
            draw_set_alpha(1);
            draw_set_color(c_white);
            gpu_set_blendmode(bm_normal);
        },
        in_range: function(){
            return(point_distance(xmid,ymid,global.car.x,global.car.y) <= range);
        }
    };
}

This system not only marks progress along the track but also introduces a layer of strategy and challenge to your racing game. Here's a breakdown of what the code does and how it contributes to the gameplay.

scr_build_pointlist(pth_test);: Initializes the track by building a list of points along the predefined path pth_test. This list is crucial for tracking positions and setting up checkpoints.

  • Variables Initialization: Sets up the initial game state, including the player's position on the track (track_position), the number of checkpoints crossed (checkpoint_counter), the total number of checkpoints (checkpoint_count_max), and the lap count (lap_count).
  • checkpoint_lines Array: Stores information about each checkpoint.
  • checkpoint_half_width: Defines the visual and collision width of our checkpoints, making them detectable and visible.
  • Position and Orientation: Calculated to ensure checkpoints span across the track width.
  • Visuals (draw function): Defines how checkpoints are drawn on the screen, including their transparency, color, and width. This function uses Game Maker's drawing functions to render the checkpoints visually.
  • Collision Detection (in_range function): Determines if the player's vehicle is within the checkpoint's range. This is crucial for tracking progress and implementing game logic like lap counting.
  • In-Depth: The Checkpoint Object

    Each checkpoint is an object with properties like range, x1, y1, x2, y2 (defining the start and end points of the checkpoint line), and methods such as draw and in_range:

    • range: The detection range for the checkpoint, expanded by the car's width to ensure accurate collision detection.
    • draw Method: Handles the visual representation of the checkpoint. It's designed to be customizable, allowing for different visual effects based on the game state or player interactions.
    • in_range Method: A critical gameplay mechanic that checks whether the player has hit the checkpoint. This function can trigger events like updating the checkpoint counter, playing sound effects, or other game logic.

    Continuing our deep dive into the obj_track_manager's Step event, we'll explore how this event further drives the dynamics of our racing game, particularly focusing on checkpoint logic and lap counting. This segment of code plays a pivotal role in ensuring the game accurately tracks the player's progress and rewards them accordingly.

    Player Position Tracking

    Lets go into the step event of obj_track_manager and add the code below. 

    /// the position of the players car in the world, using mouse position temporarily for testing
    track_position = scr_get_point_ratio(mouse_x,mouse_y);
    /// checkpoints
    for(var i = 0; i < checkpoint_count_max; i++){
        var ratio = (i+1)/checkpoint_count_max;
        /// if the vehicle enters the position...
        if(checkpoint_lines[i].in_range()){
            if(track_position >= ratio && track_position <= ratio + 0.05){
                if(checkpoint_counter == i){
                    checkpoint_counter++;
                    // play checkpoint audio here
                }
            } else if(i >= checkpoint_count_max-1 && track_position < 0.05){
                if(checkpoint_counter == i){
                    checkpoint_counter++;
                    // play checkpoint audio here
                }
            }
        }
    }
    if(checkpoint_counter >= checkpoint_count_max){
        checkpoint_counter = 0;
        lap_count++;    
    } 

    In the step event we determine the player's position on the track using scr_get_point_ratio(mouse_x, mouse_y). This function call calculates the player's current position as a ratio along the path of the track, using the mouse's position for testing purposes. This ratio is essential for interacting with checkpoints and tracking overall progress.

    Then, we iterate over each check point and do the following:

    1. Checkpoint Proximity and Order: The loop checks each checkpoint to determine if the player's car is within its designated range (using in_range()). It then checks if the player's position corresponds with the next expected checkpoint (checkpoint_counter), ensuring the player is following the course correctly.
    2. Checkpoint Validation: The player's position ratio is compared against the checkpoint's ratio (ratio) to verify if the player has indeed crossed the checkpoint. A buffer of 0.05 is added to account for frame rate variations and ensure smooth gameplay.
    3. Advancing Checkpoints: If the player crosses a checkpoint in the correct sequence, checkpoint_counter is incremented. This progression logic ensures that players can't skip checkpoints and must follow the track's intended route.
    4. Sound Feedback: Upon successfully crossing a checkpoint, a placeholder comment indicates where audio feedback should be triggered. This immediate auditory cue is vital for enhancing player satisfaction and providing clear progress indicators.
    Lap Completion and Reset

    Finally, the event checks if the player has passed all checkpoints (checkpoint_counter >= checkpoint_count_max). If so, it resets the checkpoint_counter to 0 and increments lap_count, indicating the start of a new lap. This mechanism is crucial for games with multiple laps, as it tracks the completion of each circuit around the track.

    Drawing the Track

    draw_set_alpha(0.2);
    draw_path(pth_test, 0, 0, 1);
    draw_set_alpha(1);
    /// draw all checkpoints
    for(var i = 0; i < array_length(checkpoint_lines); i++){
        var _a = checkpoint_counter == i ? 0.75 : 0.1;
        checkpoint_lines[i].draw(_a);
    }
    var last = array_length(checkpoint_lines)-1;
    draw_text(checkpoint_lines[last].x1,checkpoint_lines[last].y1,"Start"); 

    Let's break down this Draw event code piece by piece to understand its functionality within obj_track_manager.

    Initially, the alpha level is set to 0.2 using draw_set_alpha(0.2). Alpha level determines the opacity of the drawing, with 1 being fully opaque and 0 fully transparent. Setting the alpha to 0.2 makes the checkpoints appear semi-transparent, giving it a lighter appearance on the screen.

    Next, draw_path(pth_test, 0, 0, 1) draws the path of the track. The pth_test parameter specifies the path resource to draw, which is the predefined path that represents our race track. The next two parameters, 0, 0, set the starting position for drawing the path, essentially placing it at the origin point of the room. The final parameter, 1, scales the path to its original size.

    After drawing the path, the alpha level is reset to 1 (draw_set_alpha(1)), ensuring that subsequent drawings are fully opaque unless otherwise specified.

    Drawing the Checkpoints

    The event then iterates over the checkpoint_lines array, drawing each checkpoint on the track. The loop uses a conditional alpha setting to highlight the current checkpoint:

    • If the checkpoint_counter equals the current index i (meaning it's the next checkpoint the player needs to pass), the alpha is set higher at 0.75, making the checkpoint more visible.
    • For all other checkpoints, the alpha is set to 0.1, making them much more faint. This visual cue helps players focus on their immediate objective while still being aware of the track layout.

    Each checkpoint's draw method is called with the determined alpha value (_a), which draws the checkpoint line with specified visibility. The draw function within the checkpoint object handles the actual rendering of the line on the screen, using draw_line_width and other drawing functions tailored to represent the checkpoint visually.

    Marking the Start Line

    Finally, the event marks the "Start" position on the track. It identifies the last checkpoint in the checkpoint_lines array as the start line (which could also serve as the finish line in a looped track scenario). The text "Start" is drawn at the coordinates of the first point of the last checkpoint (checkpoint_lines[last].x1, checkpoint_lines[last].y1), serving as a label for the starting line. This provides players with a clear indication of where the lap begins and ends.

    Conclusion

    In the first installment of "Speedway Secrets," we embarked on an insightful journey into the realm of racing game development with Game Maker. We dissected and explained foundational functions and code snippets essential for crafting a dynamic and engaging racing game. From constructing detailed race tracks with scr_build_pointlist, determining racers' positions via scr_get_point_ratio, to implementing the core gameplay mechanics in the obj_track_manager's Create and Step events, we've laid the groundwork for a compelling racing experience. Additionally, we explored the visual aspects, highlighting how checkpoints and the race path are drawn, enhancing the game's interactivity and visual appeal. This comprehensive guide serves not just as an introduction to racing game development but as a foundation upon which to build more complex and interactive game features.

    Stay tuned for Part 2 of "Speedway Secrets," where we'll delve deeper into adding complexity with AI opponents, power-ups, collisions, and refining player controls for an even more exhilarating racing game experience. Whether you're a budding game developer or a seasoned programmer looking to expand your portfolio, our series promises to equip you with the skills and insights needed to bring your racing game vision to life.

    Download

    Download NowName your own price

    Click download now to get access to the following files:

    racetrack_test.yyz 18 kB

    Leave a comment

    Log in with itch.io to leave a comment.