Created
June 20, 2022 05:41
-
-
Save xeekworx/4ed45c039ea1676ddef1c2d9f921973d to your computer and use it in GitHub Desktop.
SDL Renderer & Keyboard State Example in C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <SDL.h> | |
#include <stdio.h> | |
// Macros used to keep the box within the screen's boundaries. In C++ std::min and std::max can be used. | |
#define MIN(a,b) (((a)<(b))?(a):(b)) | |
#define MAX(a,b) (((a)>(b))?(a):(b)) | |
// This is the forward declaration for shutdown so that main() knows it exists, check below main() for the function | |
// body. | |
int shutdown(SDL_Window* main_window, SDL_Renderer* renderer, const char* error_prefix, const char* error); | |
int main(int argc, char* argv[]) | |
{ | |
const char* app_title = "SDL Box Movement Example - Use the arrow keys!"; | |
const size_t max_app_title_ext = (size_t)strlen(app_title) + 128; | |
char* app_title_ext = (char*)malloc(max_app_title_ext); // needed when appending FPS to the title | |
int screen_width = 1024, screen_height = 768; | |
SDL_Window* main_window = NULL; | |
SDL_Renderer* renderer = NULL; | |
SDL_Event event = { 0 }; | |
SDL_bool should_quit = SDL_FALSE; | |
SDL_FRect box_destination = { 0 }; // Use SDL_FRect instead of SDL_Rect for subpixel rendering. | |
float update_fps_every_seconds = 1.F; // Every (this many) seconds, update the framerate in the title bar. | |
float seconds_since_last_fps_update = 0.F; // Keep up with how long it's been for framerate display. | |
float current_fps = 0.F; // Always set to the current calculated framerate. | |
// INITIALIZATION: | |
// ------------------------------------------------------------------------------------------------------------ | |
// Initialize SDL and create a window and renderer | |
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { | |
free(app_title_ext); | |
return shutdown(main_window, renderer, "Failed to initialize SDL", SDL_GetError()); | |
} | |
if ((main_window = SDL_CreateWindow( | |
app_title, | |
SDL_WINDOWPOS_CENTERED, // Use Centered Top Coordinate | |
SDL_WINDOWPOS_CENTERED, // Use Centered Left Coordinate | |
screen_width, screen_height, // Width & Height | |
0 // Flags (Window is implicitly shown if SDL_WINDOW_HIDDEN is not set) | |
)) == 0) { | |
free(app_title_ext); | |
return shutdown(main_window, renderer, "Failed to create a window", SDL_GetError()); | |
} | |
// -1 to use the first available render driver. | |
// 0 to default to SDL_RENDERER_ACCELERATED (hardware), and fallback or fallback to software if necessary. | |
if ((renderer = SDL_CreateRenderer( | |
main_window, // The window to associate with this renderer, multiple windows & renderers are possible | |
-1, // Index of render driver, -1 for default | |
0 // Flags, 0 to default to SDL_RENDERER_ACCELERATED (hardware), and fallback or fallback to | |
// software if necessary. | |
)) == 0) { | |
free(app_title_ext); | |
return shutdown(main_window, renderer, "Failed to create the renderer", SDL_GetError()); | |
} | |
// Comment this line out if you do not want VSync. VSync is when the rendering waits for the monitor to finish | |
// drawing the last frame so that you do not start drawing the current frame before it's done. Without VSync | |
// you could see just how fast your computer can render, but also it prevents seeing screen tearing (only | |
// partial frames being drawn). | |
SDL_RenderSetVSync(renderer, 1); // 1 for on, 0 for off. | |
// With this on you might expect a framerate of around 59 or 60 depending on your monitor. I have a gaming | |
// monitor with a refresh rate of 165, so I will see my fps as close to 165. | |
// CONFIGURE THE BOX / PLAYER, IT'S INITIAL LOCATION AND SIZE: | |
// ------------------------------------------------------------------------------------------------------------ | |
box_destination.w = 100.0f; | |
box_destination.h = 100.0f; | |
box_destination.x = screen_width / 2.F - box_destination.w / 2.F; // Center is half the screen width minus half the box width | |
box_destination.y = screen_height / 2.F - box_destination.h / 2.F; // Center is half the screen height minus half the box height | |
// Setup the initial beforeTime before the game loop starts up... | |
// Using double to avoid issues with high performance systems, like my own. | |
// SDL_GetPerformanceCounter is in a unit of measurement only meaningful to this computer. | |
// SDL_GetPerformanceFrequency is a value to divide by to convert the counter to seconds. | |
double before_time = (double)SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency(); | |
double speed = 50.0f; /* pixels per second of movement */ | |
// PRIMARY & EVENT LOOP: | |
while (!should_quit) { | |
while (SDL_PollEvent(&event)) { | |
switch (event.type) { | |
case SDL_QUIT: | |
should_quit = 1; | |
break; | |
case SDL_KEYDOWN: | |
switch (event.key.keysym.sym) { | |
case SDLK_ESCAPE: | |
should_quit = 1; | |
break; | |
} | |
break; | |
} | |
} | |
// CALCULATE DELTA TIME: | |
// -------------------------------------------------------------------------------------------------------- | |
// Get the current time by querying the performance counter and using the performance frequency to give it | |
// meaning | |
// currentTime is tick counter in seconds. | |
double current_time = (double)SDL_GetPerformanceCounter() / SDL_GetPerformanceFrequency(); | |
// How long it's been since the last frame in seconds. | |
double delta_time = current_time - before_time; | |
// Prime beforeTime for the next frame. | |
before_time = current_time; | |
// * Use deltaTime from here on for calculations that need to know how long it has been since the last | |
// frame. | |
// FRAME RATE (FPS) CALCULATION: | |
// -------------------------------------------------------------------------------------------------------- | |
// This actually calculates the current framerate in every frame. If you were to display this every frame | |
// expect it to jitter around since the system isn't going to finish every frame at a perfectly identical | |
// time to the last. Generally, you would consider adding this to a list of capped size and average the | |
// result for many frames to smooth the number out. | |
float current_fps = 1.0 / delta_time; | |
// In order to filter out some of that jitter more easily, we're just going to show the framerate in the | |
// window's title bar every so many seconds. | |
seconds_since_last_fps_update += delta_time; | |
if (seconds_since_last_fps_update >= update_fps_every_seconds) { | |
seconds_since_last_fps_update = 0; // Reset the time span since the last frame rate display | |
// We have to use stringstream magic to keep the original window's title, but tack on the current | |
// framerate: | |
snprintf(app_title_ext, max_app_title_ext, "%s (FPS: %.1f)", app_title, current_fps); | |
SDL_SetWindowTitle(main_window, app_title_ext); | |
} | |
// CHECK THE KEYBOARD STATE FOR MOVEMENT KEYS & MOVE THE BOX: | |
// -------------------------------------------------------------------------------------------------------- | |
// If you had used events for this instead (SDL_KEYDOWN/UP) you would have onlybeen able to act on one key | |
// press at a time. This also uses min() and max() to keep the box on the screen. The box should not go | |
// beyond the boundaries of the screen. | |
double move_amount = speed * delta_time; | |
const Uint8* state = SDL_GetKeyboardState(NULL); | |
if (state[SDL_SCANCODE_LEFT]) { | |
box_destination.x = MAX(box_destination.x - move_amount, 0.0f); | |
} | |
if (state[SDL_SCANCODE_RIGHT]) { | |
box_destination.x = MIN(box_destination.x + move_amount, screen_width - box_destination.w); | |
} | |
if (state[SDL_SCANCODE_UP]) { | |
box_destination.y = MAX(box_destination.y - move_amount, 0.0f); | |
} | |
if (state[SDL_SCANCODE_DOWN]) { | |
box_destination.y = MIN(box_destination.y + move_amount, screen_height - box_destination.h); | |
} | |
// RENDERING: | |
// -------------------------------------------------------------------------------------------------------- | |
// Clear the background: | |
// Always start fresh and clear, you're never guaranteed to have a blank canvas or the previous rendered | |
// canvas here. | |
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); | |
SDL_RenderClear(renderer); | |
// Render the box: | |
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); // Black with an opacity of 255 (totally visible) | |
SDL_RenderFillRectF(renderer, &box_destination); | |
// Show on screen: | |
SDL_RenderPresent(renderer); | |
} | |
// CLEAN-UP & SHUTDOWN: | |
// ---------------------------------------------------------------------------------------------------------------- | |
return shutdown(main_window, renderer, NULL, NULL); | |
} | |
int shutdown( | |
SDL_Window* main_window, | |
SDL_Renderer* renderer, | |
const char* error_prefix, | |
const char* error | |
) | |
{ | |
int result = 0; | |
if (error) { | |
// Return non-zero to indicate the application should exit with an error code. | |
result = -1; | |
// Output the error to the console, if you have one. | |
if (error_prefix) printf("%s: %s\n", error_prefix, error); | |
else printf("%s\n", error); | |
// This is a simple way to show a message box, if main_window failed to create this will still work | |
// since main_window will be NULL (the message box will just not have a parent): | |
SDL_ShowSimpleMessageBox( | |
SDL_MESSAGEBOX_ERROR, | |
"Application Execution Failed", | |
error, | |
main_window | |
); | |
} | |
if (renderer) SDL_DestroyRenderer(renderer); | |
if (main_window) SDL_DestroyWindow(main_window); | |
SDL_Quit(); | |
return result; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment