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

Unreal Engine Oyun Programlama sayfalarımız yayında...

Ana sayfa > Oyun programlama > SDL3 projects > SDL3 water animation

SDL3 water animation

In this project, we will create a water animation using C Programming Language and SDL3 library. The animation achieves a realistic water surface effect by loading a water image read from the same file into two textures, one in the background and one in the foreground, scrolling in different directions and speeds, and changing the transparency (alpha) settings. The user can control various aspects of this animation using the keyboard keys.

Variable declarations and purposes

Global variables defined at the beginning of the code are important components that can be accessed by all functions in the program and manage the state of the animation.

  • is_running: A bool variable that controls whether the program's main loop is running. As long as it's true, the program continues to run.
  • window: A pointer of type SDL_Window* representing the program's main window.
  • texture_background and texture_foreground: Both are pointers of type SDL_Texture* used to load the same "water.png" image. They represent the background and foreground textures. These two textures can be moved independently, creating a sense of depth and flow.
  • renderer: A pointer to SDL_Renderer* used to draw on the window.
  • last_frame_time: An unsigned int variable that holds the time of the previous frame's drawing in milliseconds. It is used to calculate the time difference between frames for a smooth animation.
  • x_left, y_top: These are float-type coordinate variables that determine the position of the textures on the screen. These values are updated each frame to create the scrolling animation.
  • scrolling: This is a bool variable that determines whether the scrolling animation is active.
  • scroll_speed: This is an int variable that specifies how fast the textures will scroll.
  • scroll_direction: This is a bool variable that specifies the scrolling direction.
  • level_alpha: This is an int variable that specifies the transparency level of the foreground texture (between 0 and 255).

Functions' operating logic

  • init_window(): Initializes the SDL video system and creates a renderer with a window of the specified dimensions. Returns true on success and false on error.
  • init_vars(): Initializes variables at program startup. For example, last_frame_time is reset, scrolling is enabled, and default speed/direction values are set.
  • load_img(): Loads the image file "assets/images/water.png" using the SDL_image library and creates the textures (texture_background and texture_foreground). This forms the visual basis of the animation.
  • process_event(): Listens for keyboard and window events. This function is triggered when the user presses a key or closes the window.
    • "S" key on keyboard: Stops or restarts scrolling.
    • "D" key on keyboard: Changes the scrolling direction.
    • "PageUp" and "PageDown" keys on keyboard: Increases or decreases the scrolling speed.
    • "Up" and "Down" arrow keys on keyboard: Increases or decreases the transparency level (level_alpha) of the foreground texture.
    • "A, B, M, L, N" keys on keyboard: Changes the appearance of the water animation by activating different blend modes (ADD, BLEND, MOD, MUL, NONE).
    • "ESC" key on keyboard: Ends the main loop of the program.
  • update_screen(): Updates the state of objects on the screen.
    • Frame rate calculation: Calculates the time elapsed since the previous frame (time_delta) using the SDL_GetTicks() function. This ensures that the animation runs at the same speed on different systems.
    • Position update: If scrolling is enabled, it updates the x_left and y_top coordinates based on the scroll_direction variable. It resets the position when the texture goes off-screen for an infinite scroll effect.
    • Window title update: Displays the current state of the animation (mode, alpha, speed, etc.) in the window title using the sprintf function.
  • draw_screen(): Draws the screen based on updated data.
    • Clear screen: Clears the screen to black with SDL_RenderClear().
    • Background texture: Draws the background texture twice, side by side, for a continuous scrolling effect. The position of the first texture is determined by x_left, and the second texture is placed right next to the first (x_left - WINDOW_WIDTH or x_left + WINDOW_WIDTH).
    • Foreground texture: Similarly, draws the foreground texture twice. The transparency (level_alpha) for this texture is set. The movement of these two textures at different speeds and directions creates a layer of depth.
    • Refreshing the screen: Allows the drawings to appear on the screen with SDL_RenderPresent().
  • destroy_window(): Releases all allocated resources when the program terminates. The textures, window, and drawing tool are destroyed, respectively, and the SDL library is terminated.

Code flow

  • The main function creates a window and a renderer with init_window().
  • Variables are initialized with init_vars().
  • The textures required for the animation are loaded into memory with load_img().
  • A while loop (as long as is_running is true) begins.
  • Within the loop, the process_event(), update_screen(), and draw_screen() functions are called, respectively.
    • process_event(): User input is processed.
    • update_screen(): The animation state (position, speed, etc.) is updated.
    • draw_screen(): The screen is redrawn according to the new state.
  • When the user presses the ESC key or closes the window, is_running becomes false and the loop terminates.
  • After the loop is complete, destroy_window() is called to release the resources and terminate the program.

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

unsigned int last_frame_time;            // Last frame time
float x_left, y_top;                     // x and y coordinate variable
bool scrolling;                          // Background scrolling control variable
int scroll_speed;                        // Scrolling speed
bool scroll_direction;                   // Scrolling direction
int level_alpha;                         // Alpha level variable

// 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;
    x_left = 0;
    y_top = 0;
    level_alpha = 180;
    scrolling = true;
    scroll_speed = 20;
    scroll_direction = true;
}

// Load image to texture
void load_img(void)
{
    // Loading image to background texture
	texture_background = IMG_LoadTexture(renderer, "assets/images/water.png");
	if(texture_background == NULL) {
       SDL_Log("IMG_LoadTexture error: %s\n", SDL_GetError());
	}

    // Loading image to foreground texture
	texture_foreground = IMG_LoadTexture(renderer, "assets/images/water.png");
	if(texture_foreground == 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_KEY_DOWN: // Indicate that a key was pressed.
               switch (event.key.key) {
                  case SDLK_S: // Enable/Disable scrolling
                       scrolling = !scrolling;
                       break;
                  case SDLK_D: // Change scrolling direction
                       scroll_direction = !scroll_direction;
                       x_left = 0;
                       y_top = 0;
                       break;
                  case SDLK_PAGEUP: // Increase scrolling speed
                       if(scroll_speed<80) scroll_speed+=5;
                       break;
                  case SDLK_PAGEDOWN: // Decrease scrolling speed
                       if(scroll_speed>0) scroll_speed-=5;
                       break;
                  case SDLK_UP: // Increase alpha level
                       if(level_alpha<255) level_alpha+=5;
                       break;
                  case SDLK_DOWN: // Decrease alpha level
                       if(level_alpha>0) level_alpha-=5;
                       break;
                  // Change texture blend mode
                  case SDLK_A:
                       SDL_SetTextureBlendMode(texture_foreground, SDL_BLENDMODE_ADD);
                       break;
                  case SDLK_B:
                       SDL_SetTextureBlendMode(texture_foreground, SDL_BLENDMODE_BLEND);
                       break;
                  case SDLK_M:
                       SDL_SetTextureBlendMode(texture_foreground, SDL_BLENDMODE_MOD);
                       break;
                  case SDLK_L:
                       SDL_SetTextureBlendMode(texture_foreground, SDL_BLENDMODE_MUL);
                       break;
                  case SDLK_N:
                       SDL_SetTextureBlendMode(texture_foreground, SDL_BLENDMODE_NONE);
                       break;
                  case SDLK_ESCAPE:
                       is_running = false;
                       break;
               }
               break;

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

// 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();

    if (scrolling) {
        if (scroll_direction) {
            // When scroll_direction is TRUE:
            // Background: Visually left-to-right (x_left increases)
            x_left = x_left + (scroll_speed * time_delta);
            // If the first texture has scrolled completely off-screen to the right, reset its position
            if (x_left > WINDOW_WIDTH) {
                x_left = 0;
            }

            // Foreground: Visually bottom-to-top (y_top decreases)
            y_top = y_top - (scroll_speed * time_delta);
            // If the first texture has scrolled completely off-screen to the top, reset its position
            if (y_top < -WINDOW_HEIGHT) {
                y_top = 0;
            }
        }
        else {
            // When scroll_direction is FALSE:
            // Background: Visually right-to-left (x_left decreases)
            x_left = x_left - (scroll_speed * time_delta);
            // If the first texture has scrolled completely off-screen to the left, reset its position
            if (x_left < -WINDOW_WIDTH) {
                x_left = 0;
            }

            // Foreground: Visually top-to-bottom (y_top increases)
            y_top = y_top + (scroll_speed * time_delta);
            // If the first texture has scrolled completely off-screen to the bottom, reset its position
            if (y_top > WINDOW_HEIGHT) {
                y_top = 0;
            }
        }
    }

    char cdizi[200], mode[6];
    SDL_BlendMode blendMode;

    SDL_GetTextureBlendMode(texture_foreground, &blendMode);

    switch (blendMode) {
       case SDL_BLENDMODE_NONE: strcpy(mode, "None"); break;
       case SDL_BLENDMODE_BLEND: strcpy(mode, "Blend"); break;
       case SDL_BLENDMODE_ADD: strcpy(mode, "Add"); break;
       case SDL_BLENDMODE_MOD: strcpy(mode, "Mod"); break;
       case SDL_BLENDMODE_MUL: strcpy(mode, "Mul"); break;
       default: strcpy(mode, "Error"); break; // Should not happen
    }

    sprintf(cdizi, "Water animation, Mode: %s, Alpha: %d, x: %.2f, y: %.2f, Dir: %s, Speed: %d",
            mode, level_alpha, x_left, y_top, scroll_direction ? "L->R/B->T" : "R->L/T->B", scroll_speed);
    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, SDL_ALPHA_OPAQUE); // Set background clear color to black
    SDL_RenderClear(renderer); // Clear the current rendering target

    // Render background texture
    // Draw the first instance of the background texture
    SDL_FRect bg_rect_1 = {x_left, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
    SDL_RenderTexture(renderer, texture_background, NULL, &bg_rect_1);

    // Draw the second instance of the background texture for seamless scrolling
    SDL_FRect bg_rect_2;
    if (scroll_direction) { // If true, background scrolls visually left-to-right
        bg_rect_2 = (SDL_FRect){x_left - WINDOW_WIDTH, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
    }
    else { // If false, background scrolls visually right-to-left
        bg_rect_2 = (SDL_FRect){x_left + WINDOW_WIDTH, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
    }
    SDL_RenderTexture(renderer, texture_background, NULL, &bg_rect_2);

    // Render foreground texture
    SDL_SetTextureAlphaMod(texture_foreground, level_alpha); // Set transparency level
    // Draw the first instance of the foreground texture
    SDL_FRect fg_rect_1 = {0, y_top, WINDOW_WIDTH, WINDOW_HEIGHT};
    SDL_RenderTexture(renderer, texture_foreground, NULL, &fg_rect_1);

    // Draw the second instance of the foreground texture for seamless scrolling
    SDL_FRect fg_rect_2;
    if (scroll_direction) { // If true, foreground scrolls visually bottom-to-top
        fg_rect_2 = (SDL_FRect){0, y_top + WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT};
    }
    else { // If false, foreground scrolls visually top-to-bottom
        fg_rect_2 = (SDL_FRect){0, y_top - WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT};
    }
    SDL_RenderTexture(renderer, texture_foreground, NULL, &fg_rect_2);

    // Present the rendered content to the screen
    SDL_RenderPresent(renderer);
}

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