In this section, we will examine a program that moves a 20-pixel ball up, down, left, right, left up, right up, left down and right down using the arrow keys, changes the blocking status of the rectangle with the R key, changes the blocking status of the circle with the C key and changes the speed with the PageUp and PageDown keys.
When the program runs, the following operations are performed in addition to the basic operations:
struct objects { float x; float y; float width; float height; float vel_x; float vel_y; } ball, ball2;
struct { int x, y; int r; } circle_check;
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 last_frame_time; // Last frame time
SDL_FRect frect_col; // Rectangle variable
int move_size; // Move size
bool rect_enabled; // Rectangle state variable
bool circle_enabled; // Circle state variable
int width_ball; // Ball width
int height_ball; // Ball height
// Struct declaration
struct objects {
float x;
float y;
float width;
float height;
float vel_x;
float vel_y;
} ball, ball2;
struct {
int x, y;
int r;
} circle_check;
// 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 ball_in_rect(void); // Determine whether the ball in rectangle
bool ball_in_circle(void); // Determine whether the ball in circle
double distance_piksel(int x1, int y1, int x2, int y2); // Calculate distance between pixels
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;
rect_enabled = true;
circle_enabled = true;
width_ball = 20;
height_ball = 20;
frect_col.x = 220.00;
frect_col.y = 90.00;
frect_col.w = 200.00;
frect_col.h = 300.00;
ball.x = width_ball/2;
ball.y = height_ball/2;
ball.width = width_ball;
ball.height = height_ball;
ball.vel_x = 0;
ball.vel_y = 0;
ball2.x = 100;
ball2.y = 150;
ball2.width = width_ball;
ball2.height = height_ball;
ball2.vel_x = 0;
ball2.vel_y = 0;
circle_check.x = ball.x;
circle_check.y = ball.y;
circle_check.r = width_ball/2;
}
// Load image to texture
void load_img(void)
{
// Loading image to texture
texture = IMG_LoadTexture(renderer, "ball_group.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_R: rect_enabled = !rect_enabled; break;
case SDLK_C: circle_enabled = !circle_enabled; break;
case SDLK_PAGEUP: if(move_size<1000) move_size += 20; break;
case SDLK_PAGEDOWN: if(move_size>0) move_size -= 20; 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;
}
}
}
// 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;
circle_check.x = ball.x;
circle_check.y = ball.y;
// 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-circle_check.r<0) || (ball.x + circle_check.r> WINDOW_WIDTH) || ball_in_rect() || ball_in_circle()) {
ball.x -= move_x;
circle_check.x = ball.x;
circle_check.y = ball.y;
}
// 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;
circle_check.x = ball.x;
circle_check.y = ball.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-circle_check.r<0) || (ball.y + circle_check.r > WINDOW_HEIGHT) || ball_in_rect() || ball_in_circle()) {
ball.y -= move_y;
circle_check.x = ball.x;
circle_check.y = ball.y;
}
char cdizi[120];
sprintf(cdizi, "ball.x: %.2f ball.y: %.2f ball.vel_x: %.2f ball.vel_y: %.2f - Rect: %s - Circle: %s",
ball.x, ball.y, ball.vel_x, ball.vel_y, rect_enabled ? "On" : "Off", circle_enabled ? "On" : "Off");
SDL_SetWindowTitle(window, cdizi);
}
bool ball_in_rect(void)
{
// Closest point in collision rectangle
int x_close, y_close;
// Finding the nearest x-coordinate
if(circle_check.x < frect_col.x) {
x_close = frect_col.x;
}
else if(circle_check.x > frect_col.x + frect_col.w) {
x_close = frect_col.x + frect_col.w;
}
else {
x_close = circle_check.x;
}
// Finding the nearest y-coordinate
if(circle_check.y < frect_col.y) {
y_close = frect_col.y;
}
else if(circle_check.y > frect_col.y + frect_col.h) {
y_close = frect_col.y + frect_col.h;
}
else {
y_close = circle_check.y;
}
// If the closest point is inside the circle
if(rect_enabled && (distance_piksel(circle_check.x, circle_check.y, x_close, y_close ) < circle_check.r * circle_check.r)) {
return true;
}
return false;
}
bool ball_in_circle()
{
// Total number of pixels of the ball
int pixel_no = circle_check.r + circle_check.r;
pixel_no = pixel_no * pixel_no;
// If the distance between the centers of the circles is less than the sum of their radii
if(circle_enabled && (distance_piksel(circle_check.x, circle_check.y, ball2.x, ball2.y) < pixel_no)) {
return true;
}
return false;
}
double distance_piksel(int x1, int y1, int x2, int y2)
{
int dist_x = x2 - x1;
int dist_y = y2 - y1;
return (dist_x*dist_x) + (dist_y*dist_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.
// Collision rectangle
SDL_SetRenderDrawColor(renderer, rect_enabled ? 255 : 0, !rect_enabled ? 255 : 0, 0, 255);
SDL_RenderRect(renderer, &frect_col);
SDL_FRect rect_src = { 0, 0, 20, 20 };
SDL_FRect rect_dst = { ball.x-circle_check.r, ball.y-circle_check.r, 20, 20 };
SDL_RenderTexture(renderer, texture, &rect_src, &rect_dst);
// Collision circle
rect_src.y = circle_enabled ? 20 : 40;
rect_dst.x = ball2.x-circle_check.r;
rect_dst.y = ball2.y-circle_check.r;
SDL_RenderTexture(renderer, texture, &rect_src, &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)
{
SDL_DestroyTexture(texture);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}