Warning: Undefined array key "HTTP_ACCEPT_LANGUAGE" in /var/www/vhosts/bilgigunlugum.net/httpdocs/index.php on line 43
SDL Game Programming

SDL3 Oyun Programlama sayfalarımız yayında...

Ana sayfa > Oyun programlama > SDL3 programming > Continuous circular collision detection

Continuous circular collision detection

In this section, we will examine the program that automatically moves a 20-pixel ball up, down, left, right, left up, right up, left down and right down by accepting the edges of the main window, the edges of a rectangle with a width of 200 pixels and a height of 300 pixels located in the middle of the main window and four circles with a width of 20 pixels located at different coordinates of the main window as the borders, reverses the movement by detecting collisions in the border areas, changes the obstacle status of the rectangle with the R key, changes the obstacle status of the circles with the 1-4 keys, enables or disables the speed change in the horizontal and vertical directions with the X and Y keys, and changes the speed with the up and down arrow keys.

When the program runs, the following operations are performed in addition to the basic operations:

  1. At the beginning of the program, the global variables shown below are first defined:
    • unsigned int last_frame_time: Last frame time
    • SDL_FRect frect_col: Rectangle variable
    • bool rect_enabled: Rectangle state variable
    • bool circle_enabled: Rectangle state variable
    • int width_ball: Ball width
    • int height_ball: Ball height
    • bool x_vel, y_vel: Speed ​​change enable variables
    • The following structure is of data type ball and Two variables named ball2 are defined:
      struct objects {
        float x;
        float y;
        float width;
        float height;
        float vel_x;
        float vel_y;
        bool enabled;
      } ball, ball2, ball3, ball4, ball5;
      
    • A variable named circle_check is defined of the following structure data type:
      struct {
        int x, y;
        int r;
      } circle_check;
      
  2. init_vars() function assigns initial values ​​to global variables.
  3. In load_img() function, an image file for the ball and circle is loaded into the texture variable with the IMG_LoadTexture() function.
  4. In process_event() function,
    • When the up and down arrow keys are pressed, if the x_vel variable for the x direction and the y_vel variable for the y direction are true, the speed of the ball is increased or decreased.
    • When the X key is pressed, the x_vel variable and when the Y key is pressed, the y_vel variable are reversed, thus activating or deactivating the speed change in the relevant direction.
    • When the 1, 2, 3 and 4 keys are pressed, the obstacle feature of the circles is changed.
    • When the R key is pressed, the obstacle feature of the rectangular area is changed is changed.
  5. In update_screen() function,
    • The difference between the active time and the previous time of the loop is taken in seconds and assigned to the time_delta variable.
    • The active time is taken with the SDL_GetTicks() function and assigned to the last_frame_time variable to be used in the next iteration of the loop.
    • The product of ball.vel_x and the loop iteration time (time_delta) is assigned to the move_x variable
    • The ball.x value is increased by the move_x value.
    • If the difference between the new x coordinate position of the ball and the circle_check.r variable value is less than 0 or if their sum is greater than the window size or if it is within the coordinates of the rectangle in the middle of the window in the control made with the ball_in_rect() function or if the coordinates of the moving ball are within the fixed circle in the control made with the ball_in_circle() function, the x coordinate position of the ball is reset and the speed direction is reversed.
    • The product of ball.vel_y and the loop repetition time (time_delta) is assigned to the move_y variable
    • The ball.y value is increased by the move_y value.
    • If the difference between the new y coordinate position of the ball and the circle_check.r variable value is less than 0 or if their sum is greater than the window height or if it is within the coordinates of the rectangle in the middle of the window in the check made with the ball_in_rect() function or if the coordinates of the first moving ball are within the fixed second ball in the check made with the ball_in_circle() function, the y coordinate position of the ball is restored and the speed direction is reversed.
    • The ball's coordinates and speed values ​​and the obstacle status of the rectangle and circle are written in the main window title.
  6. In draw_screen() function,
    • With the SDL_RenderRect() function, the rectangle is drawn to the main window in red if the obstacle status is active and in green if it is not.
    • With the SDL_RenderTexture() function, the circles are drawn in the main window, red if the obstacle condition is active, and green if not.
    • With the SDL_RenderTexture() function, the ball is displayed in its position in the main window.

main.c file content will be as follows:


#include <stdio.h>
#include <stdlib.h>
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>

// Window width and height
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480

// Global variables
int is_running = false;        // Variable that controls the execution of the main loop of the program
SDL_Window *window = NULL;     // Main window variable
SDL_Texture *texture = NULL;   // Texture variable
SDL_Renderer *renderer = NULL; // Renderer variable

unsigned int last_frame_time;  // Last frame time
SDL_FRect frect_col;           // Rectangle variable
bool rect_enabled;             // Rectangle state variable
int width_ball = 20;           // Ball width
int height_ball = 20;          // Ball height
bool x_vel, y_vel;             // Velocity change enabled variables

// Struct declaration
struct objects {
  float x;
  float y;
  float width;
  float height;
  float vel_x;
  float vel_y;
  bool enabled;
} ball, ball2, ball3, ball4, ball5;

struct {
  int x, y;
  int r;
} circle_check;

// Function prototypes
int init_window(void);         // Create window and renderer
void init_vars(void);          // Initialize variables
void load_img(void);           // Load image to texture
void process_event(void);      // Process events
void update_screen();          // Update values
void draw_screen(void);        // Draw screen
void destroy_window(void);     // Destroy window

bool ball_in_rect(void);
bool ball_in_circle(void);
double distance_piksel(int x1, int y1, int x2, int y2);

int main(int argc, char* argv[])
{
    // Create window and renderer
    is_running = init_window();

    // Initialize variables
    init_vars();

    // Load .png file to texture
    load_img();

   // Main loop
    while (is_running) {
       process_event(); // Processing SDL events (Here keyboard inputs)
       update_screen(); // Updating variables
       draw_screen();   // Drawing objects on the window (Rendering)
    }

    // Destroy renderer and SDL window
    destroy_window();

    return 0;
}

// Create window and renderer
int init_window(void)
{
    // Initialize the SDL library.
    if(SDL_Init(SDL_INIT_VIDEO) == false) {
       SDL_Log("SDL init error: %s\n", SDL_GetError());
       return false;
    }

    // Create a window and a 2D rendering context for the window.
    if(!SDL_CreateWindowAndRenderer("SDL3 window", WINDOW_WIDTH, WINDOW_HEIGHT, 0, &window, &renderer)) {
       return false;
    }

    return true;
}

// Initialization function that runs only once at the beginning of the program
void init_vars(void)
{
    last_frame_time = 0;
    rect_enabled = true;
    x_vel = true;
    y_vel = true;
    frect_col.x = 220.00;
    frect_col.y = 90.00;
    frect_col.w = 200.00;
    frect_col.h = 300.00;

    ball.x = width_ball/2;
    ball.y = height_ball/2;
    ball.width = width_ball;
    ball.height = height_ball;
    ball.vel_x = 200;
    ball.vel_y = 200;

    ball2.x = 100;
    ball2.y = 150;
    ball2.width = width_ball;
    ball2.height = height_ball;
    ball2.vel_x = 0;
    ball2.vel_y = 0;
    ball2.enabled = true;

    ball3.x = 540;
    ball3.y = 150;
    ball3.width = width_ball;
    ball3.height = height_ball;
    ball3.vel_x = 0;
    ball3.vel_y = 0;
    ball3.enabled = true;

    ball4.x = 100;
    ball4.y = 330;
    ball4.width = width_ball;
    ball4.height = height_ball;
    ball4.vel_x = 0;
    ball4.vel_y = 0;
    ball4.enabled = true;

    ball5.x = 540;
    ball5.y = 330;
    ball5.width = width_ball;
    ball5.height = height_ball;
    ball5.vel_x = 0;
    ball5.vel_y = 0;
    ball5.enabled = true;

	circle_check.x = ball.x;
	circle_check.y = ball.y;
    circle_check.r = width_ball/2;
}

// Load image to texture
void load_img(void)
{
    // Loading image to texture
	texture = IMG_LoadTexture(renderer, "ball_group.png");

	if(texture == NULL) {
       SDL_Log("IMG_LoadTexture error: %s\n", SDL_GetError());
	}
}

// Function to control SDL events and process keyboard inputs
void process_event(void)
{
    SDL_Event event;

    // Creating a loop to process user inputs
    while (SDL_PollEvent(&event)) {
       switch (event.type) {
          case SDL_EVENT_QUIT: // Logout action by the user (x button at the top right of the window)
               is_running = false; // Terminates the execution of the program main loop.
               break;

          case SDL_EVENT_KEY_DOWN: // Key pressed
               switch(event.key.key) {
                  case SDLK_UP:
                       if(abs(ball.vel_x)<1000.00 && x_vel) {
                          ball.vel_x += (ball.vel_x>0) ? 10.00 : -10.00;
                       }
                       if(abs(ball.vel_y)<1000.00 && y_vel) {
                          ball.vel_y += (ball.vel_y>0) ? 10.00 : -10.00;
                       }
                       break;
                  case SDLK_DOWN:
                       if(abs(ball.vel_x)>0.00 && x_vel) {
                          ball.vel_x += (ball.vel_x>0) ? -10.00 : 10.00;
                       }
                       if(abs(ball.vel_y)>0.00 && y_vel) {
                          ball.vel_y += (ball.vel_y>0) ? -10.00 : 10.00;
                       }
                       break;
                  case SDLK_X: x_vel = !x_vel; break;
                  case SDLK_Y: y_vel = !y_vel; break;
                  case SDLK_1: ball2.enabled = !ball2.enabled; break;
                  case SDLK_2: ball3.enabled = !ball3.enabled; break;
                  case SDLK_3: ball4.enabled = !ball4.enabled; break;
                  case SDLK_4: ball5.enabled = !ball5.enabled; break;
                  case SDLK_R: rect_enabled = !rect_enabled; break;
                  case SDLK_ESCAPE: is_running = false; break;
               }
               break;
       }
    }
}

// Updates objects in the main window
void update_screen(void)
{
    // Get the difference between the active time and the previous time of loop in seconds
    float time_delta = (SDL_GetTicks() - last_frame_time) / 1000.0;
    // Assign the active time to use in the next iteration of the loop
    last_frame_time = SDL_GetTicks();

    // Calculate moving size
    // Multiplication of ball.vel_x and the loop repeat time (delta time) assigned to move_x variable.
    float move_x = ball.vel_x * time_delta;

    // Ball x coordinate increased by move_x
    ball.x += move_x;
	circle_check.x = ball.x;
	circle_check.y = ball.y;
	// If ball.x is lower than 0 or, sum of ball.x and ball.width is greater than windows width
    // ball.x gets its previous value.
    if((ball.x-circle_check.r<0) || (ball.x + circle_check.r> WINDOW_WIDTH) || ball_in_rect() || ball_in_circle()) {
	   ball.x -= move_x;
	   circle_check.x = ball.x;
	   circle_check.y = ball.y;
	   ball.vel_x = -ball.vel_x;
    }

    // Multiplication of ball.vel_y and the loop repeat time (delta time), assigned to move_y variable.
    float move_y = ball.vel_y * time_delta;

    // Ball y coordinate increased by move_y
    ball.y += move_y;
	circle_check.x = ball.x;
	circle_check.y = ball.y;
	// If ball.yx is lower than 0 or, sum of ball.y and ball.height is greater than windows height
    // ball.y gets its previous value.
    if((ball.y-circle_check.r<0) || (ball.y + circle_check.r > WINDOW_HEIGHT) || ball_in_rect() || ball_in_circle()) {
       ball.y -= move_y;
	   circle_check.x = ball.x;
	   circle_check.y = ball.y;
	   ball.vel_y = -ball.vel_y;
    }

    char cdizi[150];
    sprintf(cdizi, "x: %.2f y: %.2f vel_x: %.2f vel_y: %.2f - Rect: %s - Circles: %s %s %s %s",
                 ball.x, ball.y, ball.vel_x, ball.vel_y, rect_enabled ? "On" : "Off",
                 ball2.enabled ? "On" : "Off", ball3.enabled ? "On" : "Off",
                 ball4.enabled ? "On" : "Off", ball5.enabled ? "On" : "Off");
    SDL_SetWindowTitle(window, cdizi);
}

bool ball_in_rect(void)
{
    // Closest point in collision rectangle
    int x_close, y_close;

    // Finding the nearest x-coordinate
    if(circle_check.x < frect_col.x) {
       x_close = frect_col.x;
    }
    else if(circle_check.x > frect_col.x + frect_col.w) {
       x_close = frect_col.x + frect_col.w;
    }
    else {
       x_close = circle_check.x;
    }

    // Finding the nearest y-coordinate
    if(circle_check.y < frect_col.y) {
       y_close = frect_col.y;
    }
    else if(circle_check.y > frect_col.y + frect_col.h) {
       y_close = frect_col.y + frect_col.h;
    }
    else {
       y_close = circle_check.y;
    }

    // If the closest point is inside the circle
    if(rect_enabled && (distance_piksel(circle_check.x, circle_check.y, x_close, y_close ) < circle_check.r * circle_check.r)) {
       return true;
    }

    return false;
}

bool ball_in_circle()
{
	// Total number of pixels of the ball
	int pixel_no = circle_check.r + circle_check.r;
	pixel_no = pixel_no * pixel_no;

    // If the distance between the centers of the circles is less than the sum of their radii
    if((ball2.enabled && (distance_piksel(circle_check.x, circle_check.y, ball2.x, ball2.y) < pixel_no)) ||
       (ball3.enabled && (distance_piksel(circle_check.x, circle_check.y, ball3.x, ball3.y) < pixel_no)) ||
       (ball4.enabled && (distance_piksel(circle_check.x, circle_check.y, ball4.x, ball4.y) < pixel_no)) ||
       (ball5.enabled && (distance_piksel(circle_check.x, circle_check.y, ball5.x, ball5.y) < pixel_no))
    ) {
       return true;
    }

    return false;
}

double distance_piksel(int x1, int y1, int x2, int y2)
{
	 int dist_x = x2 - x1;
	 int dist_y = y2 - y1;
	 return (dist_x*dist_x) + (dist_y*dist_y);
}

// Render function used to draw game objects in the main window
void draw_screen(void)
{
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // Set the color used for drawing operations.
    SDL_RenderClear(renderer); // Clear the current rendering target with the drawing color.

    // Collision rectangle
    SDL_SetRenderDrawColor(renderer, rect_enabled ? 255 : 0, !rect_enabled ? 255 : 0, 0, 255);
    SDL_RenderRect(renderer, &frect_col);

    SDL_FRect rect_src = { 0, 0, 20, 20 };
    SDL_FRect rect_dst = { ball.x-circle_check.r, ball.y-circle_check.r, 20, 20 };
	SDL_RenderTexture(renderer, texture, &rect_src, &rect_dst);

    // Collision circles
    rect_src.y = (ball2.enabled) ? 20 : 40;
    rect_dst.x = ball2.x-circle_check.r;
    rect_dst.y = ball2.y-circle_check.r;
    SDL_RenderTexture(renderer, texture, &rect_src, &rect_dst);

    rect_src.y = (ball3.enabled) ? 20 : 40;
    rect_dst.x = ball3.x-circle_check.r;
    rect_dst.y = ball3.y-circle_check.r;
    SDL_RenderTexture(renderer, texture, &rect_src, &rect_dst);

    rect_src.y = (ball4.enabled) ? 20 : 40;
    rect_dst.x = ball4.x-circle_check.r;
    rect_dst.y = ball4.y-circle_check.r;
    SDL_RenderTexture(renderer, texture, &rect_src, &rect_dst);

    rect_src.y = (ball5.enabled) ? 20 : 40;
    rect_dst.x = ball5.x-circle_check.r;
    rect_dst.y = ball5.y-circle_check.r;
    SDL_RenderTexture(renderer, texture, &rect_src, &rect_dst);

    // Loading all objects drawn one by one to the back buffer to the front buffer at once and loading them onto the screen
    // This process prevents each drawn object from being displayed on the screen one by one.
    SDL_RenderPresent(renderer); // Update on screen all operations performed since the previous call
}

// Destroy Renderer and SDL window, exit from SDL3
void destroy_window(void)
{
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}