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.
Functions' operating logic
Code flow
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();
}