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 > Texture tiling

Texture tiling

In this section, we will examine a program that allows you to move a 20-pixel ball up, down, left, right, left up, right up, left down and right down in a map that is 4 times the size of the main window area and is created using different images in a texture, according to values ​​read from a file, by moving it with the arrow keys in the up, down, left, right, left up, right up, left down and right down directions, transferring the main window-sized part of the map to the main window depending on the coordinates of the ball, and allows you to move around the map, and the ball cannot move in the squares drawn in dark colors.

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

  1. The global variables shown below are first defined at the beginning of the program:
    • SDL_Texture *texture_ball: Texture variable for the ball
    • SDL_Texture *texture_tile: Texture for the tile variable
    • unsigned int last_frame_time: Last frame time
    • int move_size: Variable for movement size
    • SDL_FRect camera: Rectangle variable for camera
    • int map_width: Variable for map width
    • int map_height: Variable for map height
    • int tiles[192]: Tile array
    • int tile_width: Width of tile
    • int tile_height: Tile height
    • int total_tiles: Total number of tiles
    • int tile_active: Active tile
    • 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 the init_vars() function,
    • Global variables are assigned initial values.
    • The tiles.bin file is read with the get_set_tiles() function, and the values ​​obtained are assigned to the tiles array.
  3. In the load_img() function, a texture is created for the top image and the tile with the IMG_LoadTexture() function.
  4. In the process_event() function,
    • When any of the arrow keys are pressed, the ball movement is increased or decreased depending on the arrow direction. When the key is released, the movement in the same direction becomes 0 again.
    • When one of the 1, 2, 3, and 4 keys is pressed, the active tile value that the mouse is on is changed corresponding to the number pressed.
    • As the mouse is moved, the active square index value that the mouse is on is calculated and assigned to the tile_active variable.
  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 ball.x value is less than 0 or if its sum with ball.width is greater than the map width or if it intersects with one of the sections forming the obstacle, the ball.x variable takes its previous value.
    • 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 incremented by the move_y value.
    • If the ball.y value is less than 0 or if its sum with ball.width is greater than the map height or if it intersects with one of the sections forming the obstacle, the ball.y variable takes its previous value.
    • The new coordinates of the ball and camera and the differences of their coordinates are shown in the main window title.
  6. In the draw_screen() function,
    • With the SDL_RenderTexture() function, the squares located in the part of the map area determined by the camera are drawn and transferred to the main window.
    • If the drawn square is active because the mouse is on it, a different colored frame is drawn in the area where the square is. In this case, when the user presses one of the 1, 2, 3 and 4 keys, the active color and disabled status of the square change.
    • With the SDL_RenderTexture() function, all tile values ​​are copied to the upper right corner of the main window with a size of 10 pixels and a gray rectangular frame is drawn around it.
    • With the SDL_RenderRect() function, a red frame showing the area taken by the camera and a red square frame of 3 pixels showing the position of the ball are drawn in the small window showing the map area.
    • With the SDL_RenderTexture() function, the moving ball is loaded onto the screen with its new coordinates.
    • With the SDL_RenderPresent() function, the drawings and loadings are shown on the screen.

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

#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_ball = NULL; // Texture variable
SDL_Texture *texture_tile = NULL; // Texture variable
SDL_Renderer *renderer = NULL;    // Renderer variable

unsigned int last_frame_time;     // Last frame time
int move_size;                    // Movement size
SDL_FRect camera;                 // Camera
int map_width;                    // Map width
int map_height;                   // Map height
int tiles[192];                   // Tiles array
int tile_width;                   // Tile width
int tile_height;                  // Tile height
int total_tiles;                  // Total number of tiles
int tile_active;                  // Active tile

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

bool get_set_tiles(int mode);
bool intersect_wall(void);

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;
}

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

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

    // Loading tile image to texture
	texture_tile = IMG_LoadTexture(renderer, "tiles.png");

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

// Initialization function that runs only once at the beginning of the program
void init_vars(void)
{
    last_frame_time = 0;
    move_size = 300;
    camera.x = 0.00;
    camera.y = 0.00;
    camera.w = WINDOW_WIDTH;
    camera.h = WINDOW_HEIGHT;
    map_width = 1280;
    map_height = 960;
    tile_width = 80;
    tile_height = 80;
    total_tiles = 192;
    tile_active = -1;

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

	// Load tile map
	if(!get_set_tiles(1)) {
       SDL_Log("Failed to load tile set!\n");
	}
}

// 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;
                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_1: tiles[tile_active] = 0; break;
                      case SDLK_2: tiles[tile_active] = 1; break;
                      case SDLK_3: tiles[tile_active] = 2; break;
                      case SDLK_4: tiles[tile_active] = 3; 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;

           case SDL_EVENT_MOUSE_MOTION:
                tile_active = (((int)(event.motion.y+camera.y)/80) * 16) + ((int)(event.motion.x+camera.x)/80);
                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;
	// If ball.x is lower than 0 or, sum of ball.x and ball.width is greater than map width
    // ball.x gets its previous value.
    if((ball.x<0) || (ball.x + ball.width > map_width) || intersect_wall()) {
	   ball.x -= move_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;
	// If ball.yx is lower than 0 or, sum of ball.y and ball.height is greater than map height
    // ball.y gets its previous value.
    if((ball.y < 0) || (ball.y + ball.height > map_height) || intersect_wall()) {
       ball.y -= move_y;
    }

    // Center camera on ball
    camera.x = (ball.x + ball.width/2) - WINDOW_WIDTH/2;
    camera.y = (ball.y + ball.height/2) - WINDOW_HEIGHT/2;

    // Protect camera limits
    if(camera.x < 0) {
       camera.x = 0;
    }
    if(camera.y < 0) {
       camera.y = 0;
    }
    if(camera.x > map_width - camera.w) {
       camera.x = map_width - camera.w;
    }
    if(camera.y > map_height - camera.h) {
       camera.y = map_height - camera.h;
    }

    char cdizi[100];
    sprintf(cdizi, "ball.x: %.2f ball.y: %.2f camera x: %.2f y: %.2f diff x: %.2f y: %.2f",
                    ball.x, ball.y, camera.x, camera.y, ball.x-camera.x, ball.y-camera.y);
    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.

    const SDL_Rect a = { camera.x, camera.y, camera.w, camera.h }; // SDL_FRect camera = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };

    // Drawing main window rectangles
    for(int i=0; i<total_tiles; i++) {
        const SDL_Rect b = { (i%16)*80, (i/16)*80, 80, 80 };
        const SDL_FRect frect_src = { (float) tiles[i]*80, (float) 0, (float) 80, (float) 80 };

        if(SDL_HasRectIntersection(&a, &b)) {
           const SDL_FRect frect_dst = { (float) b.x - camera.x, (float) b.y - camera.y, (float) 80, (float) 80 };
           SDL_RenderTexture(renderer, texture_tile, &frect_src, &frect_dst);

           if(i==tile_active) {
              SDL_SetRenderDrawColor(renderer, 255, 120, 0, 255);
              SDL_RenderRect(renderer, &frect_dst);
           }
        }
    }

    SDL_FRect rect_dst = { WINDOW_WIDTH-(WINDOW_WIDTH/4) - 30, 30, WINDOW_WIDTH/4, WINDOW_HEIGHT/4 };

    // Drawing view window rectangles
    for(int i=0; i<total_tiles; i++) {
        const SDL_FRect frect_src = { (float) tiles[i]*80, (float) 0, (float) 80, (float) 80 };
        const SDL_FRect frect_dst = { (float) rect_dst.x + ((i%16)*10), (float) rect_dst.y + ((i/16)*10), (float) 10, (float) 10 };
        SDL_RenderTexture(renderer, texture_tile, &frect_src, &frect_dst);
    }

    SDL_SetRenderDrawColor(renderer, 180, 180, 180, 255);
    SDL_RenderRect(renderer, &rect_dst);

    // View window camera rectangle
    float rate = map_width/(WINDOW_WIDTH/4);
    rect_dst.x = 450 + (camera.x/rate);
    rect_dst.y = 30 + (camera.y/rate);
    rect_dst.w = WINDOW_WIDTH/rate;
    rect_dst.h = WINDOW_HEIGHT/rate;
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
    SDL_RenderRect(renderer, &rect_dst);

    // View window camera ball
    rect_dst.x = 450 + (ball.x/rate);
    rect_dst.y = 30 + (ball.y/rate);
    rect_dst.w = 3;
    rect_dst.h = 3;
    SDL_RenderFillRect(renderer, &rect_dst);

    rect_dst.x = ball.x-camera.x;
    rect_dst.y = ball.y-camera.y;
    rect_dst.w = 20;
    rect_dst.h = 20;
	SDL_RenderTexture(renderer, texture_ball, 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)
{
    get_set_tiles(2);
    SDL_DestroyTexture(texture_ball);
    SDL_DestroyTexture(texture_tile);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

bool get_set_tiles(int mode)
{
    int rows = 12;
    int cols = 16;
    int id1, id2, id3;
    const char *filename = "tile.bin";

    // Memory allocation for 2D array
    int **array = (int **) malloc(rows * sizeof(int *));
    for (id1=0; id1<rows; id1++) {
         array[id1] = (int *) malloc(cols * sizeof(int));
    }

    FILE *file = fopen(filename, mode==1 ? "rb" : "wb");

    if(file == NULL) {
       SDL_Log("Failed to open tile set file!\n");
       for (id1=0; id1<rows; id1++) {
            free(array[id1]);
       }
       free(array);
       return false;
    }
    for (id1=0; id1<rows; id1++) {
         for (id2=0; id2<cols; id2++) {
              if(mode==1) {
                 if(fread(&array[id1][id2], sizeof(int), 1, file) != 1) {
                    SDL_Log("Failed to read tile set!\n");
                    fclose(file);
                    for (id3=0; id3<rows; id3++) {
                         free(array[id3]);
                    }
                    free(array);
                    return false;
                 }
                 // Initialize the tiles
                 tiles[(id1*cols)+id2] = array[id1][id2];
              }
              else {
                 // Set tiles
                 array[id1][id2] = tiles[(id1*cols)+id2];
                 if(fwrite(&array[id1][id2], sizeof(int), 1, file) != 1) {
                    SDL_Log("Failed to write tile set!\n");
                    fclose(file);
                    for (id3=0; id3<rows; id3++) {
                         free(array[id3]);
                    }
                    free(array);
                    return false;
                 }
              }
         }
    }
    fclose(file);

    // Free allocated memory
    for (int id1= 0; id1<rows; id1++) {
         free(array[id1]);
    }
    free(array);

    return true;
}

bool intersect_wall(void)
{
    const SDL_Rect a = { ball.x, ball.y, ball.width, ball.height };

    // Check all tiles
    for(int i = 0; i < total_tiles; i++) {
        // If the tile is a wall type
        if(tiles[i]==3) {
           // If the ball touches the wall tile
           const SDL_Rect b = { (i%16)*80, (i/16)*80, 80, 80 };

           if(SDL_HasRectIntersection(&a, &b)) {
              return true;
           }
        }
    }

    return false;
}