In a program made with SDL, if the image in the main window and being moved on is not larger than the main window size, all movements made according to user inputs can be followed on the main window. However, when the image being moved on is larger than the main window size, a system called camera is used.
A camera system, which usually allows navigation on a large image such as a large world map or a game scene, works according to the principles shown below:
In this section, we will examine the program that moves a 20 pixel ball up, down, left, right, left up, right up, left down and right down with the arrow keys on a map that is 4 times the size of the main window area, transferring the main window sized part of the map to the main window depending on the ball's coordinates.
When the program is run, 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 <stdlib.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_ball = NULL; // Texture variable
SDL_Texture *texture_bg = NULL; // Texture variable
SDL_Renderer *renderer = NULL; // Renderer variable
unsigned int last_frame_time; // Last frame time
SDL_FRect camera; // Camera rectangle variable
int move_size; // Move size variable
int map_width; // Map width variable
int map_height; // Map height variable
// 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();
// 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;
camera.x = 0.00;
camera.y = 0.00;
camera.w = WINDOW_WIDTH;
camera.h = WINDOW_HEIGHT;
move_size = 200;
map_width = 1280;
map_height = 960;
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_ball = IMG_LoadTexture(renderer, "ball.png");
if(texture_ball == NULL) {
SDL_Log("IMG_LoadTexture error: %s\n", SDL_GetError());
}
// Loading background image to texture
texture_bg = IMG_LoadTexture(renderer, "bg.png");
if(texture_bg == 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: // Bir tuşa basıldığını gösterir.
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_ESCAPE: is_running = false; break;
}
}
break;
case SDL_EVENT_KEY_UP: // Bir tuşun bırakıldığını gösterir.
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;
// 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)) {
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)) {
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.
// Render background
SDL_RenderTexture(renderer, texture_bg, &camera, NULL);
SDL_FRect rect_dst = { WINDOW_WIDTH-(WINDOW_WIDTH/4) - 30, 30, WINDOW_WIDTH/4, WINDOW_HEIGHT/4 };
SDL_RenderTexture(renderer, texture_bg, NULL, &rect_dst);
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 255);
SDL_RenderRect(renderer, &rect_dst);
// Render camera in small window
float rate = map_width/(WINDOW_WIDTH/4); // 8
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);
// Render ball in small window
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);
// Render ball
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)
{
SDL_DestroyTexture(texture_ball);
SDL_DestroyTexture(texture_bg);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}