Lazy Foo' Productions

SDL Forums external link SDL Tutorials 🐰SDL3 Tutorials🥚 Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Key Presses and Key States

Key Presses and Key States screenshot

Last Updated: Oct 6th, 2024

In this tutorial we'll be handling keyboard input.
/* Global Variables */
//The window we'll be rendering to
SDL_Window* gWindow{ nullptr };

//The renderer used to draw to the window
SDL_Renderer* gRenderer{ nullptr };

//The directional images
LTexture gUpTexture, gDownTexture, gLeftTexture, gRightTexture;
Here are the directional textures we're going to render based on the user's input.
bool loadMedia()
{
    //File loading flag
    bool success{ true };

    //Load directional images
    if( success &= gUpTexture.loadFromFile( "03-key-presses-and-key-states/up.png" ); !success )
    {
        SDL_Log( "Unable to load up image!\n");
    }
    if( success &= gDownTexture.loadFromFile( "03-key-presses-and-key-states/down.png" ); !success )
    {
        SDL_Log( "Unable to load down image!\n");
    }
    if( success &= gLeftTexture.loadFromFile( "03-key-presses-and-key-states/left.png" ); !success )
    {
        SDL_Log( "Unable to load left image!\n");
    }
    if( success &= gRightTexture.loadFromFile( "03-key-presses-and-key-states/right.png" ); !success )
    {
        SDL_Log( "Unable to load right image!\n");
    }

    return success;
}
Here we are loading the textures.

If you're wondering what the &= does it's the "logical and and assign" operator. It basically means that this:
success &= gDownTexture.loadFromFile( "03-key-presses-and-key-states/down.png" )

Is the same as this:
success = success && gDownTexture.loadFromFile( "03-key-presses-and-key-states/down.png" )

So if the loading of an image fails, it will skip the rest because the success flag is false.
            //The quit flag
            bool quit{ false };

            //The event data
            SDL_Event e;
            memset( &e, 0, sizeof( e ) );
            
            //The currently rendered texture
            LTexture* currentTexture = &gUpTexture;

            //Background color defaults to white
            SDL_Color bgColor = { 0xFF, 0xFF, 0xFF, 0xFF };
Before we enter the main loop, we want to default the texture to the up texture and the background color to white.
                //Get event data
                while( SDL_PollEvent( &e ) )
                {
                    //If event is quit type
                    if( e.type == SDL_EVENT_QUIT )
                    {
                        //End the main loop
                        quit = true;
                    }
                    //On keyboard key press
                    else if( e.type == SDL_EVENT_KEY_DOWN )
                    {
                        //Set texture
                        if( e.key.key == SDLK_UP )
                        {
                            currentTexture = &gUpTexture;
                        }
                        else if( e.key.key == SDLK_DOWN )
                        {
                            currentTexture = &gDownTexture;
                        }
                        else if( e.key.key == SDLK_LEFT )
                        {
                            currentTexture = &gLeftTexture;
                        }
                        else if( e.key.key == SDLK_RIGHT )
                        {
                            currentTexture = &gRightTexture;
                        }
                    }
                }
When a keyboard key is pressed, we get an SDL_EventType of SDL_EVENT_KEY_DOWN and an event type of SDL_KeyboardEvent happens. The key member gives us the SDL_Keycode of the key press which is the virtual key which is based on the keyboard layout as opposed to the physical key.

Here we set the texture we want to eventually render.
                //Reset background color to white
                bgColor.r = 0xFF;
                bgColor.g = 0xFF;
                bgColor.b = 0xFF;

                //Set background color based on key state
                const Uint8* keyStates = SDL_GetKeyboardState( nullptr );
                if( keyStates[ SDL_SCANCODE_UP ] )
                {
                    //Red
                    bgColor.r = 0xFF;
                    bgColor.g = 0x00;
                    bgColor.b = 0x00;
                }
                else if( keyStates[ SDL_SCANCODE_DOWN ] )
                {
                    //Green
                    bgColor.r = 0x00;
                    bgColor.g = 0xFF;
                    bgColor.b = 0x00;
                }
                else if( keyStates[ SDL_SCANCODE_LEFT ] )
                {
                    //Yellow
                    bgColor.r = 0xFF;
                    bgColor.g = 0xFF;
                    bgColor.b = 0x00;
                }
                else if( keyStates[ SDL_SCANCODE_RIGHT ] )
                {
                    //Blue
                    bgColor.r = 0x00;
                    bgColor.g = 0x00;
                    bgColor.b = 0xFF;
                }
Before we start rendering we want to set the background color. Where the texture is based on the most recent key press event, the background color is based on the current state of the keyboard.

First we reset the background color to white so the background will be white unless the user is pressing a directional key. Then we get the current state of the keyboard keys with SDL_GetKeyboardState. We can then get the state of the keys using the SDL_Scancode which is based off the physical keyboard key independent of language and keyboard mapping.
                //Fill the background 
                SDL_SetRenderDrawColor( gRenderer, bgColor.r, bgColor.g, bgColor.b, 0xFF );
                SDL_RenderClear( gRenderer );
            
                //Render image on screen
                currentTexture->render( ( kScreenWidth - currentTexture->getWidth() ) / 2.f, ( kScreenHeight - currentTexture->getHeight() ) / 2.f );

                //Update screen
                SDL_RenderPresent( gRenderer );
Finally we render the background color the and key press texture.

If you're wondering what those calculations are in the image coordinates, it's how you center an image. Say for example you had:
  • screen width of 1000
  • image width of 200
This mean you need 400 pixels on the left and 400 pixels on the right for the image to be centered. When the x position of the image is on the left side, you want the image's x position to be 400. To calculate that based off the image width and screen width:
( 1000 - 200 ) / 2 = 400

Or expressed in variables:
( screenWidth - imageWidth ) / 2

Addendum: Division vs multiplication performance

One thing you learn in computer architecture is that floating point division is significantly slower than floating point multiplication. So an easy way to optimize this code:
currentTexture->render( ( kScreenWidth - currentTexture->getWidth() ) / 2.f, ( kScreenHeight - currentTexture->getHeight() ) / 2.f );

Is to replace the divison by multiplying by the inverse:
currentTexture->render( ( kScreenWidth - currentTexture->getWidth() ) * 0.5f, ( kScreenHeight - currentTexture->getHeight() ) * 0.5f );

But in my opinion the division code is easier to read so I chose ease of reading over performance. Besides, when optimizing with inverses, we care about optimizing things like physics simulations of tens or hundreds of thousands of rigid bodies, not a handful of images.