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 > Scrolling background

Scrolling background

In this section, we will examine a program that continuously scrolls the main window background using an image the size of the main window area and moves a player up, down, left, right, left up, right up, left down, and right down with the arrow keys.

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

  1. The following global variables are defined at program startup:
    • unsigned int last_frame_time: Last frame time
    • int move_size: Move size variable
    • float x_left: x coordinate variable
    • int bg_width: Background width variable
    • bool scrolling: Background scrolling control variable
    • A variable named ball is defined from the following structure data type:
      struct players {
        float x;
        float y;
        float width;
        float height;
        float vel_x;
        float vel_y;
        int frame;
      } player;
      
  2. init_vars() function assigns initial values ​​to global variables.
  3. In load_img() function, a texture is created for the player and background image with the IMG_LoadTexture() function.
  4. In process_event() function,
    • When any of the arrow keys are pressed, the player movement is increased or decreased depending on the arrow direction. When the key is released, the movement in the same direction is set to 0 again.
    • When the S key is pressed, the background scrolling is enabled or disabled.
  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 player.vel_x and the loop iteration time (time_delta) is assigned to the move_x variable.
    • The player.x value is incremented by the move_x value.
    • If player.x is less than 0 or the sum of player.x and player.width is greater than the width of the window, player.x returns to its previous value.
    • If there is movement and the player x coordinate value is a multiple of 12,
      • If the left arrow key is pressed, if the player.frame value is equal to or less than 15, it is 8, otherwise it is increased by itself.
      • If the right arrow key is pressed, if the player.frame value is equal to or less than 7, it is 0, otherwise it is increased by itself.
    • The product of player.vel_y and the loop repeat time (time_delta) is assigned to the move_y variable.
    • The player.y value is increased by the move_y value.
    • If player.y is less than 0 or the sum of player.y and player.height is greater than the height of the window, player.y returns to its previous value.
    • If the scrolling variable is true, the x_left variable value is decreased by 1. If the resulting value is less than the negative value of bg_width, x_left takes the value 0.
    • The coordinates of the ball and the x_left and x_left + bg_width values ​​are shown in the main window title.
  6. In draw_screen() function,
    • With SDL_RenderTexture() function, the background texture is transferred to the x_left, 0 coordinates of the main window.
    • With SDL_RenderTexture() function, the background texture is transferred to the x_left + bg_width, 0 coordinates of the main window.
    • With SDL_RenderTexture() function, the image read from the texture_player value according to the player.frame variable value is copied to the main window according to the player.x and player.y variable coordinates.
    • With SDL_RenderPresent() function, the drawings and loadings made are shown on the screen.

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_player = NULL; // Texture variable
SDL_Texture *texture_bg = NULL;     // Texture variable
SDL_Renderer *renderer = NULL;      // Renderer variable

unsigned int last_frame_time;       // Last frame time
int move_size;                      // Move size variable
float x_left;                       // x coordinate variable
int bg_width;                       // Background width variable
bool scrolling;                     // Background scrolling control variable

// Struct declaration
struct players {
  float x;
  float y;
  float width;
  float height;
  float vel_x;
  float vel_y;
  int frame;
} player;

// 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;
    x_left = 0;
    bg_width = 640;
    scrolling = true;

    player.x = 0;
    player.y = 250;
    player.width = 88;
    player.height = 110;
    player.vel_x = 0;
    player.vel_y = 0;
    player.frame = 0;
}

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

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

    // Loading background image to texture
	texture_bg = IMG_LoadTexture(renderer, "bg_scroll.png");

	if(texture_bg == 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: // Bir tuşa basıldığını gösterir.
               if(event.key.repeat == 0) {
                  switch(event.key.key) {
                      case SDLK_UP: player.vel_y -= move_size; break;
                      case SDLK_DOWN: player.vel_y += move_size; break;
                      case SDLK_LEFT: player.vel_x -= move_size; break;
                      case SDLK_RIGHT: player.vel_x += move_size; break;
                      case SDLK_S: scrolling = !scrolling; break;
                      case SDLK_ESCAPE: is_running = false; break;
                  }
               }
               break;

          case SDL_EVENT_KEY_UP: // Bir tuşun bırakıldığını gösterir.
               if(event.key.repeat == 0) {
                  switch(event.key.key) {
                      case SDLK_UP: player.vel_y += move_size; break;
                      case SDLK_DOWN: player.vel_y -= move_size; break;
                      case SDLK_LEFT: player.vel_x += move_size; break;
                      case SDLK_RIGHT: player.vel_x -= move_size; 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
    // player.vel_x assigned to move_x variable.
    float move_x = player.vel_x * time_delta;

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

    const bool *kb_state = SDL_GetKeyboardState(NULL);

    // If player moves
    if(player.x>0 && ((int)player.x%12==0)) {
       if(kb_state[SDL_SCANCODE_LEFT]) {
          player.frame = (player.frame==15 || player.frame<8) ? 8 : player.frame+1;
       }
       if(kb_state[SDL_SCANCODE_RIGHT]) {
          player.frame = (player.frame==7 || player.frame>7) ? 0 : player.frame+1;
       }
    }

    // player.vel_y assigned to move_y variable.
    float move_y = player.vel_y * time_delta;

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

    if(scrolling) {
       // Scroll background
       x_left = x_left - (100 * time_delta);

       if(x_left < -bg_width) {
          x_left = 0;
       }
    }

    char cdizi[100];
    sprintf(cdizi, "player.x: %.2f player.y: %.2f x_left: %.2f x_left + WINDOW_WIDTH: %.2f",
                    player.x, player.y, x_left, x_left + WINDOW_WIDTH);
    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.

    // Load background texture to main window x_left, 0 coordinates
    SDL_FRect frect_dst = { x_left, 0, bg_width, WINDOW_HEIGHT };
    SDL_RenderTexture(renderer, texture_bg, NULL, &frect_dst);

    // Load background texture to main window x_left + bg_width, 0 coordinates
    frect_dst.x = x_left + bg_width;
    SDL_RenderTexture(renderer, texture_bg, NULL, &frect_dst);

    SDL_FRect frect_src = { player.frame*player.width, 0, player.width, player.height };
    frect_dst.x = (int) player.x;
    frect_dst.y = (int) player.y;
    frect_dst.w = (int) player.width/2;
    frect_dst.h = (int) player.height/2;

    SDL_RenderTexture(renderer, texture_player, &frect_src, &frect_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_player);
    SDL_DestroyTexture(texture_bg);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}