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 > File operations

File operations

In this section, we will examine a program that moves a 20-pixel-sized ball up, down, left, right, left up, right up, left down and right down using the arrow keys, taking the edges of the main window and the edges of a 200-pixel-wide and 300-pixel-high rectangle in the middle of the main window as the border, changes the ball's movement distance with the PageUp and PageDown keys, disables or activates the blocking feature of the rectangular area with the R key, and starts the ball's coordinates, movement distance and the blocking status of the rectangular area in its latest state when closed and reopened.

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

  1. At the beginning of the program, the following global variables are first defined:
    • unsigned int last_frame_time: Last frame time
    • SDL_FRect frect_col: Rectangle variable
    • int move_size: Move size variable
    • bool rect_enabled: Rectangle state variable
    • A variable named ball is defined from the following structure data type:
      struct objects {
        float x;
        float y;
        float width;
        float height;
        float vel_x;
        float vel_y;
      } ball;
      
  2. In init_vars() function,
    • Global variables are assigned initial values.
    • The file named options.dat is opened for reading with the SDL_IOFromFile() function.
    • If the file does not exist, the file named options.dat is created with the SDL_IOFromFile() function. The SDL_WriteIO() function writes the ball.x, ball.y, move_size and rect_enabled variable values ​​to the file.
    • If the file exists, the SDL_ReadIO() function assigns the values ​​read from the options.dat file to the ball.x, ball.y, move_size and rect_enabled variables.
  3. In load_img() function, an image file for the ball is loaded into the texture variable with the IMG_LoadTexture() function.
  4. In the process_event() function,
    • When the arrow keys are pressed, the move_size variable value in the direction indicated by the arrow is added to the ball.vel_x or ball.vel_y variable, which has a value of 0. Thus, movement is achieved.
    • When the arrow keys are released, the movement size variable value called move_size in the direction indicated by the arrow is subtracted from the ball.vel_x or ball.vel_y variables, and the value of these variables is equalized to 0. Thus, the movement ends.
    • When the R key is pressed, the obstruction status of the rectangle is changed.
    • The speed value of the ball is changed with the PageUp and PageDown keys.
  5. In update_screen() function,
    • A variable named rect_control is defined, which contains the latest coordinates and size of the ball.
    • A variable named rect_col is defined, which contains the coordinates and size of the rectangle.
    • 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 incremented by the move_x value.
    • The ball.x value is assigned to the rect_control.x variable.
    • If ball.x is less than 0 or the sum of ball.x and ball.width is greater than the width of the window or rect_enabled is true and the ball and the rectangle intersect in the control performed with the SDL_HasRectIntersection() function, the ball.x and rect_control.x variables take their previous values.
    • The product of ball.vel_y and the loop iteration time (time_delta) is assigned to the move_y variable.
    • The ball.y value is incremented by the move_y value.
    • The ball.y value is assigned to the rect_control.y variable.
    • If ball.y is less than 0 or the sum of ball.y and ball.height is greater than the height of the window or rect_enabled is true and If the ball and the rectangle intersect in the control made with the SDL_HasRectIntersection() function, the ball.y and rect_control.y variables take their previous values.
    • The coordinates and speed values ​​of the ball and the obstacle status of the rectangle are written to 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 not.
    • With the SDL_RenderTexture() function, the ball is displayed in its position in the main window.
  7. When the program is closed, the ball.x, ball.y, move_size and rect_enabled variable values ​​are written to the file with SDL_WriteIO() function.

main.c file content will be as follows:


#include <stdio.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
int move_size;                 // Move size
bool rect_enabled;             // Rectangle state variable

// Struct declaration
struct objects {
    float x;
    float y;
    float width;
    float height;
    float vel_x;
    float vel_y;
} ball;

// 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

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;
    move_size = 200;
    rect_enabled = true;
    frect_col.x = 220.00;
    frect_col.y = 90.00;
    frect_col.w = 200.00;
    frect_col.h = 300.00;

    ball.x = 0;
    ball.y = 0;
    ball.width = 20;
    ball.height = 20;
    ball.vel_x = 0;
    ball.vel_y = 0;

	// Open options file for reading
	SDL_IOStream* file = SDL_IOFromFile("options.dat", "r+b");

	// File does not exist
	if(file == NULL) {
       SDL_Log("Can not open options file! SDL Error: %s\n", SDL_GetError());

  	   // Create options file for writing options
		file = SDL_IOFromFile("options.dat", "w+b");
		if(file != NULL) {
           SDL_Log("Options file created!\n");

           SDL_WriteIO(file, &ball.x, sizeof(float));
           SDL_WriteIO(file, &ball.y, sizeof(float));
           SDL_WriteIO(file, &move_size, sizeof(int));
           SDL_WriteIO(file, &rect_enabled, sizeof(bool));

		   // Close file handler
           SDL_CloseIO(file);
		}
		else {
			SDL_Log("Can not create file! SDL Error: %s\n", SDL_GetError());
		}
	}
	// File exists
	else {
		// Load data
        SDL_ReadIO(file, &ball.x, sizeof(float));
        SDL_ReadIO(file, &ball.y, sizeof(float));
        SDL_ReadIO(file, &move_size, sizeof(int));
        SDL_ReadIO(file, &rect_enabled, sizeof(bool));

		// Close file handler
		SDL_CloseIO(file);
	}
}

// Load image to texture
void load_img(void)
{
    // Loading image to texture
	texture = IMG_LoadTexture(renderer, "ball.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.
               if(event.key.repeat == 0) {
                  switch(event.key.key) {
                     case SDLK_UP: ball.vel_y -= move_size; break;
                     case SDLK_DOWN: ball.vel_y += move_size; break;
                     case SDLK_LEFT: ball.vel_x -= move_size; break;
                     case SDLK_RIGHT: ball.vel_x += move_size; break;
                     case SDLK_R: rect_enabled = !rect_enabled; break;
                     case SDLK_PAGEUP: if(move_size<1000) move_size += 20; break;
                     case SDLK_PAGEDOWN: if(move_size>0) move_size -= 20; break;
                     case SDLK_ESCAPE: is_running = false; break;
                 }
               }
               break;

          case SDL_EVENT_KEY_UP: // Key released.
               if(event.key.repeat == 0) {
                  switch(event.key.key) {
                     case SDLK_UP: ball.vel_y += move_size; break;
                     case SDLK_DOWN: ball.vel_y -= move_size; break;
                     case SDLK_LEFT: ball.vel_x += move_size; break;
                     case SDLK_RIGHT: ball.vel_x -= move_size; break;
                  }
               }
               break;
       }
    }
}

// Updates objects in the main window
void update_screen(void)
{
    SDL_Rect rect_control = { ball.x, ball.y, 20, 20 };
    SDL_Rect rect_col = { 220, 90, 200, 300};

    // 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;
    rect_control.x = ball.x;
    // 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<0) || (ball.x + ball.width > WINDOW_WIDTH) || (rect_enabled && SDL_HasRectIntersection(&rect_control, &rect_col))) {
	    ball.x -= move_x;
	    rect_control.x = ball.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;
    rect_control.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 < 0) || (ball.y + ball.height > WINDOW_HEIGHT) || (rect_enabled && SDL_HasRectIntersection(&rect_control, &rect_col))) {
       ball.y -= move_y;
		rect_control.y = ball.y;
    }

    char cdizi[120];
    sprintf(cdizi, "ball.x: %.2f ball.y: %.2f ball.vel_x: %.2f ball.vel_y: %.2f - Rectangle: %s",
                 ball.x, ball.y, ball.vel_x, ball.vel_y, rect_enabled ? "Enabled" : "Disabled");
    SDL_SetWindowTitle(window, cdizi);
}

// 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.

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

    SDL_FRect rect_dst = { ball.x, ball.y, 20, 20 };
    // Load texture content to main window
    SDL_RenderTexture(renderer, texture, NULL, &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)
{
	// Open file for writing
	SDL_IOStream* file = SDL_IOFromFile("options.dat", "w+b");
	if(file != NULL) {
       SDL_WriteIO(file, &ball.x, sizeof(float));
       SDL_WriteIO(file, &ball.y, sizeof(float));
       SDL_WriteIO(file, &move_size, sizeof(int));
       SDL_WriteIO(file, &rect_enabled, sizeof(bool));

       // Close file handler
       SDL_CloseIO(file);
	}

    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}