Last active
January 5, 2023 23:01
-
-
Save xeekworx/8d92a55563c7b51378a34e98d027bd39 to your computer and use it in GitHub Desktop.
SDL Renderer & Keyboard State Example
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 <iostream> | |
#include <sstream> | |
#include <string> | |
#include <algorithm> | |
#include <format> | |
int main(int argc, char* argv[]) | |
{ | |
std::string app_title = "SDL Box Movement Example - Use the arrow keys!"; | |
int screen_width = 1024, screen_height = 768; | |
SDL_Window* main_window = NULL; | |
SDL_Renderer* renderer = NULL; | |
SDL_Event event = { 0 }; | |
bool should_quit = false; | |
SDL_FRect box_destination = { }; // Use SDL_FRect instead of SDL_Rect for subpixel rendering. | |
std::stringstream error; // Aid in creating error strings for output later. | |
std::stringstream update_title; // Aid in appending information to the window's title. | |
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. | |
try { | |
// INITIALIZATION: | |
// ------------------------------------------------------------------------------------------------------------ | |
// Initialize SDL and create a window and renderer | |
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { | |
error << "Failed to initialize SDL: " << SDL_GetError(); | |
throw(error.str()); | |
} | |
if ((main_window = SDL_CreateWindow( | |
app_title.c_str(), | |
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) { | |
error << "Failed to create a window: " << SDL_GetError(); | |
throw(error.str()); | |
} | |
// -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) { | |
error << "Failed to create the renderer: " << SDL_GetError(); | |
throw(error.str()); | |
} | |
// 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 = static_cast<double>(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: | |
update_title.str(""); | |
update_title << app_title << " (FPS: " << current_fps << ")"; | |
SDL_SetWindowTitle(main_window, update_title.str().c_str()); | |
} | |
// 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. | |
float move_amount = static_cast<float>(speed * delta_time); | |
const Uint8* state = SDL_GetKeyboardState(NULL); | |
if (state[SDL_SCANCODE_LEFT]) { | |
box_destination.x = std::max(box_destination.x - move_amount, 0.0f); | |
} | |
if (state[SDL_SCANCODE_RIGHT]) { | |
box_destination.x = std::min(box_destination.x + move_amount, screen_width - box_destination.w); | |
} | |
if (state[SDL_SCANCODE_UP]) { | |
box_destination.y = std::max(box_destination.y - move_amount, 0.0f); | |
} | |
if (state[SDL_SCANCODE_DOWN]) { | |
box_destination.y = std::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); | |
} | |
} | |
catch (std::string error_str) { | |
// 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, app_title.c_str(), error_str.c_str(), main_window); | |
// Output the error to the console, if you have one | |
std::cout << error_str << std::endl; | |
} | |
// CLEAN-UP & SHUTDOWN: | |
// ---------------------------------------------------------------------------------------------------------------- | |
if (renderer) SDL_DestroyRenderer(renderer); | |
if (main_window) SDL_DestroyWindow(main_window); | |
SDL_Quit(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I wrote this to help others, I consider this Public Domain.