Adding SDL keyboard input to WebAssembly
SDL allows us to poll for keyboard input. Whenever the user presses a key, a call to SDL_PollEvent( &event ) will return us an SDK_KEYDOWN SDL_Event. When a key is released, it will return an SDK_KEYUP event. We can look into the values in such a case to figure out which key has been pressed or released. We can use this information to set flags in our game to let us know when to move our spaceship, and in what direction. Later, we can add code that detects a space bar press that will fire our ship's weapons.
For now, we are going to go back to using the default Emscripten shell. For the rest of this section, we will be able to do everything from within the WebAssembly C code. I will walk you through creating a new keyboard.c file from scratch, which will handle keyboard events and print to the textarea in our default shell.
Start by creating a new keyboard.c file, and add the following #include directives at the top of the file:
#include <SDL2/SDL.h>
#include <emscripten.h>
#include <stdio.h>
#include <stdbool.h>
After that, we need to add our global SDL objects. The first two, SDL_Window and SDL_Renderer, should look familiar by now. The third one, SDL_Event, is new. We will be populating this event object using a call to SDL_PollEvent later in our code:
SDL_Window *window;
SDL_Renderer *renderer;
SDL_Event event;
Like the JavaScript version of this code, we will be using global variables to keep track of which arrow keys we are currently pressing. These will all be Boolean variables, as shown in the following code:
bool left_key_press = false;
bool right_key_press = false;
bool up_key_press = false;
bool down_key_press = false;
The first function we are going to define is input_loop, but before we can define that function, we need to declare two functions that input_loop will be calling, as follows:
void key_press();
void key_release();
This will allow us to define the input_loop function before actually defining what happens when input_loop calls those functions. The input_loop function will call SDL_PollEvent to get an event object. We can then look at the type of event, and, if it is an SDL_KEYDOWN or SDL_KEYUP event, we can call the appropriate function to handle those events, as follows:
void input_loop() {
if( SDL_PollEvent( &event ) ){
if( event.type == SDL_KEYDOWN ){
key_press();
}
else if( event.type == SDL_KEYUP ) {
key_release();
}
}
}
The first of these functions that we will define will be the key_press() function. Inside this function, we will look at the keyboard event in a switch and compare the value to the different arrow key SDLK events. If the key had been previously up, it prints out a message that lets us know the key the user pressed. Then we should set the keypress flag to true. The following example shows the key_press() function in its entirety:
void key_press() {
switch( event.key.keysym.sym ){
case SDLK_LEFT:
if( !left_key_press ) {
printf("left arrow key press\n");
}
left_key_press = true;
break;
case SDLK_RIGHT:
if( !right_key_press ) {
printf("right arrow key press\n");
}
right_key_press = true;
break;
case SDLK_UP:
if( !up_key_press ) {
printf("up arrow key press\n");
}
up_key_press = true;
break;
case SDLK_DOWN:
if( !down_key_press ) {
printf("down arrow key press\n");
}
down_key_press = true;
break;
default:
printf("unknown key press\n");
break;
}
}
The first line inside the key_press function is a switch statement, switch(event.key.keysym.sym). These are structures within structures. Inside the input_loop function, we called SDL_PollEvent, passing a reference to an SDL_Event structure. This structure contains event data for any possible event that may be returned to us, as well as a type that tells us what kind of event this is. If the type is SDL_KEYDOWN or SDL_KEYUP, that means the internal key structure, which is a structure of type SDL_KeyboardEvent, is populated. If you would like to see the full definition of the SDL_Event structure, you can find it on the SDL website, at: https://wiki.libsdl.org/SDL_Event. Looking at the key variable inside of SDL_Event, you will notice it is a structure of type SDL_KeyboardEvent. This structure has a lot of data in it that we will not be using yet. It includes information such as timestamp, whether this key is a repeat press, or whether this key is being pressed or released; but what we are looking at in our switch is they keysym variable, which is a structure of type SDL_Keysym. For more information on the SDL_KeyboardEvent, you can find its definition on the SDL website, at: https://wiki.libsdl.org/SDL_KeyboardEvent. The keysym variable in the SDL_KeyboardEvent structure is where you will find the SDL_Keycode in the sym variable. This keycode is what we must look at to determine which key the player pressed. That is why we have the switch statement built around switch( event.key.keysym.sym ). A link to all of the possible values for the SDL keycodes is available at: https://wiki.libsdl.org/SDL_Keycode.
All of the case statements inside our switch look pretty similar: if a given SDLK keycode is pressed, we check to see if that key was pressed in the previous cycle, and we only print out the value if it has not. Then we set the keypress flag to true. The following example shows the code where we detect the press of the left arrow key:
case SDLK_LEFT:
if( !left_key_press ) {
printf("left arrow key press\n");
}
left_key_press = true;
break;
Our application calls the key_release function when the event type is SDL_KEYUP . That is very similar to the key_down function. The primary difference is that it is looking to see if the user pressed the key, and only prints out a message when the state changes to unpressed. The following example shows that function in its entirety:
void key_release() {
switch( event.key.keysym.sym ){
case SDLK_LEFT:
if( left_key_press ) {
printf("left arrow key release\n");
}
left_key_press = false;
break;
case SDLK_RIGHT:
if( right_key_press ) {
printf("right arrow key release\n");
}
right_key_press = false;
break;
case SDLK_UP:
if( up_key_press ) {
printf("up arrow key release\n");
}
up_key_press = false;
break;
case SDLK_DOWN:
if( down_key_press ) {
printf("down arrow key release\n");
}
down_key_press = false;
break;
default:
printf("unknown key release\n");
break;
}
}
Our last function is a new version of the main function, called when our Module is loaded. We still need to use emscripten_set_main_loop to prevent our code from tying up the JavaScript engine. We have created an input_loop which we defined earlier. It uses SDL to poll for keyboard events. But, before that, we still need to do our SDL initialization. We are using the Emscripten default shell, so the call to SDL_CreateWindowAndRenderer will set the width and height of our canvas element. We will not be rendering to the canvas element inside our input_loop, but we still want to have it initialized here because, in the next section, we will be adapting this code to render a spaceship image to the canvas and to move it around with key presses. The following code shows what the new version of our main function will look like:
int main() {
SDL_Init( SDL_INIT_VIDEO );
SDL_CreateWindowAndRenderer( 320, 200, 0, &window, &renderer );
SDL_SetRenderDrawColor( renderer, 0, 0, 0, 255 );
SDL_RenderClear( renderer );
SDL_RenderPresent( renderer );
emscripten_set_main_loop(input_loop, 0, 0);
return 1;
}
Now that we have all the code inside our keyboard.c file, we can compile our keyboard.c file with the following emcc command:
emcc keyboard.c -o keyboard.html -s USE_SDL=2
When you run keyboard.html in the browser, you will notice that pressing the arrow keys results in a message printed to the Emscripten default shell's textarea.
Consider the following screenshot:
In the next section, we will learn how to use this keyboard input to move a sprite around our canvas.