In this section, we will examine a program that moves a ball in the main window with the arrow keys using a texture created from a .png image file and controls the ball's movement speed using the Fixed Frame Rate (FPS) method.
When the program runs, in addition to the basic operations, the following operations are performed:
struct objects { float x; float y; float width; float height; float vel_x; float vel_y; } ball;
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 time_start; // Program start time
unsigned int time_start_fps; // Program while loop time
int frame_no; // while loop iteration number (frame)
bool fps_fixed; // Controls whether Fixed FPS is enabled
int fps; // Frame Per Second (FPS) to fix
int move_size; // Movement size
// 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();
frame_no = 0;
time_start = SDL_GetTicks(); // Get program start time
// Main loop
while (is_running) {
time_start_fps = SDL_GetTicks(); // Get time for while loop iteration start
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)
{
time_start = 0;
time_start_fps = 0;
frame_no = 0;
fps = 30;
fps_fixed = false;
move_size = 10;
ball.x = 0;
ball.y = 0;
ball.width = 20;
ball.height = 20;
ball.vel_x = 0;
ball.vel_y = 0;
}
// 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_F: // Enable Fixed Frame Rate (FPS)
fps_fixed = !fps_fixed;
break;
case SDLK_PAGEUP: // Increase fixed fps limit by 1
if(fps<100) fps++;
break;
case SDLK_PAGEDOWN: // Decrease fixed fps limit by 1
if(fps>10) fps--;
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;
case SDLK_F:
case SDLK_PAGEUP:
case SDLK_PAGEDOWN:
frame_no = 0;
time_start = SDL_GetTicks();
break;
}
}
break;
}
}
}
// Updates objects in the main window
void update_screen(void)
{
// Calculate moving size
// ball.vel_x assigned to move_x variable.
float move_x = ball.vel_x;
// 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 windows width
// ball.x gets its previous value.
if((ball.x<0) || (ball.x + ball.width > WINDOW_WIDTH)) {
ball.x -= move_x;
}
// ball.vel_y assigned to move_y variable.
float move_y = ball.vel_y;
// 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 windows height
// ball.y gets its previous value.
if((ball.y<0) || (ball.y + ball.height > WINDOW_HEIGHT)) {
ball.y -= move_y;
}
}
// 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_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
// The time elapsed since the start of the program in milliseconds
// It is reset when the 0, 1, 2, 3, PageUp and PageDown keys are pressed.
unsigned int time_elapsed = SDL_GetTicks()-time_start;
int milliseconds = time_elapsed; // The elapsed time in milliseconds
int seconds = milliseconds / 1000; // The elapsed time in seconds
milliseconds %= 1000; // The millisecond part of the counter
int minutes = seconds / 60; // The elapsed time in minutes
seconds %= 60; // The second part of the counter
minutes %= 60; // The minute part of the counter
// Calculating FPS
// The number of times the while loop repeats is divided by the seconds value of the elapsed time,
// The number of times the loop repeats in a second (average number of frames) is found.
float avg_fps = frame_no / (time_elapsed / 1000.f);
// fpms = 16.66 (miliseconds allocated for 1 frame (one iteration of the while loop))
// time_start_fps value is reset at the beginning of each iteration of the while loop.
// The time elapsed since the beginning of the last while loop execution (miliseconds) - time_start_fps = SDL_GetTicks()
float fpms = (float) 1000 / fps;
int time_passed_ms = SDL_GetTicks() - time_start_fps;
if(fps_fixed) {
if(time_passed_ms < fpms) { // 16.66
// Wait until the elapsed time reaches 16.66
SDL_Delay(fpms - time_passed_ms);
}
}
char cdizi[200];
sprintf(cdizi, "%.2d:%.2d:%.4d - Frame: %d - FPS: %.2f - Fixed FPS: %s - x: %.2f y: %.2f",
minutes, seconds, milliseconds, frame_no, avg_fps, fps_fixed ? "On" : "Off",
ball.x, ball.y);
SDL_SetWindowTitle(window, cdizi);
frame_no++;
}
// Destroy Renderer and SDL window, exit from SDL3
void destroy_window(void)
{
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}