Continuando o tutorial sobre SDL publicado originalmente no site lazyfoo, iremos ver agora como tratar a entrada de dados do usuário pelo teclado.
Fechar a janela através do botão X é somente um dos eventos que o SDL é capaz de tratar. Outro tipo de entrada utilizada bastante em jogos é o teclado. Nesse tutorial iremos fazer diferentes imagens serem exibidas dependendo da tecla que for pressionada.
//Key press surfaces constants enum KeyPressSurfaces { KEY_PRESS_SURFACE_DEFAULT, KEY_PRESS_SURFACE_UP, KEY_PRESS_SURFACE_DOWN, KEY_PRESS_SURFACE_LEFT, KEY_PRESS_SURFACE_RIGHT, KEY_PRESS_SURFACE_TOTAL };
Na parte superior do código, declaramos uma enumeração de diferentes superfícies que teremos. Enumerações são um atalho para constantes simbólicas ao invés de termos de declarar const int KEY_PRESS_SURFACE_DEFAULT = 0; const int KEY_PRESS_SURFACE_UP = 1; const int KEY_PRESS_SURFACE_DOWN = 2; e assim em diante. Elas por padrão começam a contagem de 0 e vai-se somando 1 para cada enumeração declarada. Isso significa que KEY_PRESS_SURFACE_DEFAULT é 0, KEY_PRESS_SURFACE_UP é 1, KEY_PRESS_SURFACE_DOWN é 2, KEY_PRESS_SURFACE_LEFT é 3, KEY_PRESS_SURFACE_RIGHT é 4, e KEY_PRESS_SURFACE_TOTAL é 5. É possível associar a cada uma delas um valor inteiro explicitamente, mas não veremos isso aqui. Uma pesquisa rápida no Google sobre esse assunto deve ser suficiente.
Um hábito ruim de programadores iniciantes é usar números arbitrários ao invés de contantes simbólicas. Por exemplo, usar 1 para menu principal, 2 para opções, etc o que é serve para programas pequenos, mas quando é preciso lidar com milhares de linhas de código ter um linha “if(option == 1)” irá causar muita dor de cabeça, ao invés de usar algo como “if(option == MAIN_MENU)”.
//Starts up SDL and creates window bool init(); //Loads media bool loadMedia(); //Frees media and shuts down SDL void close(); //Loads individual image SDL_Surface* loadSurface( std::string path ); //The window we'll be rendering to SDL_Window* gWindow = NULL; //The surface contained by the window SDL_Surface* gScreenSurface = NULL; //The images that correspond to a keypress SDL_Surface* gKeyPressSurfaces[ KEY_PRESS_SURFACE_TOTAL ]; //Current displayed image SDL_Surface* gCurrentSurface = NULL;
Junto com nossos protótipos normais de funções, teremos uma nova função chamada loadSurface. Existe uma regra geral que diz se você copiar/colar código, irá fazer algo errado. Ao invés de copiar/colar o código de carregamento de imagens a cadas vez, iremos usar uma função que faz isso.
O que importa para nosso exemplo em particular é que teremos um array de ponteiros para superfícies SDL chamado gKeyPressSurfaces que irá conter todas as imagens que usaremos. Dependendo de que tecla o usuário pressionar, iremos ajustar o valor de gCurrentSurface (que será a imagem a ser inserida na tela) para uma dessas superfícies.
SDL_Surface* loadSurface( std::string path ) { //Load image at specified path SDL_Surface* loadedSurface = SDL_LoadBMP( path.c_str() ); if( loadedSurface == NULL ) { printf( "Unable to load image %s! SDL Error: %s\n", path.c_str(), SDL_GetError() ); } return loadedSurface; }
Aqui temos a função loadSurface que carrega as imagens e reporta um erro se algo der errado. É basicamente o mesmo que vimos em artigos anteriores, mas colocar o carregamento da imagem e o tratamento de erros em uma função torna mais fácil adicionar e depurar esse processo.
Antes que algum programador C++ questione isso, cabe ressaltar que essa função não causa nenhum vazamento de memória. Ela aloca memória para carregar uma nova superfície SDL e retorna essa superfície sem liberar a memória, mas qual seria o ponto de alocar espaço para uma nova superfície e imediatamente desaloca-la? O que essa função faz é criar a superfície e retornar esta superfície recém criada de forma que quem chamou essa função pode desaloca-la depois de te-la usado. No exemplo, a superfície alocada é liberada na função close.
bool loadMedia() { //Loading success flag bool success = true; //Load default surface gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ] = loadSurface( "04_key_presses/press.bmp" ); if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ] == NULL ) { printf( "Failed to load default image!\n" ); success = false; } //Load up surface gKeyPressSurfaces[ KEY_PRESS_SURFACE_UP ] = loadSurface( "04_key_presses/up.bmp" ); if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_UP ] == NULL ) { printf( "Failed to load up image!\n" ); success = false; } //Load down surface gKeyPressSurfaces[ KEY_PRESS_SURFACE_DOWN ] = loadSurface( "04_key_presses/down.bmp" ); if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_DOWN ] == NULL ) { printf( "Failed to load down image!\n" ); success = false; } //Load left surface gKeyPressSurfaces[ KEY_PRESS_SURFACE_LEFT ] = loadSurface( "04_key_presses/left.bmp" ); if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_LEFT ] == NULL ) { printf( "Failed to load left image!\n" ); success = false; } //Load right surface gKeyPressSurfaces[ KEY_PRESS_SURFACE_RIGHT ] = loadSurface( "04_key_presses/right.bmp" ); if( gKeyPressSurfaces[ KEY_PRESS_SURFACE_RIGHT ] == NULL ) { printf( "Failed to load right image!\n" ); success = false; } return success; }
Aqui temos a função loadMedia que carrega todas as imagens que iremos renderizar na tela.
//Main loop flag bool quit = false; //Event handler SDL_Event e; //Set default current surface gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ]; //While application is running while( !quit ) {
Na função main antes de entrar no loop principal, nós definimos a superfície padrão a ser exibida.
//Handle events on queue while( SDL_PollEvent( &e ) != 0 ) { //User requests quit if( e.type == SDL_QUIT ) { quit = true; } //User presses a key else if( e.type == SDL_KEYDOWN ) { //Select surfaces based on key press switch( e.key.keysym.sym ) { case SDLK_UP: gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_UP ]; break; case SDLK_DOWN: gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_DOWN ]; break; case SDLK_LEFT: gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_LEFT ]; break; case SDLK_RIGHT: gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_RIGHT ]; break; default: gCurrentSurface = gKeyPressSurfaces[ KEY_PRESS_SURFACE_DEFAULT ]; break; } } }
Aqui temos o loop de eventos. Como você pode ver, tratamos o evento de fechamento da janela como fizemos no artigo anterior, e também lidamos com o evento SDL_KEYDOWN. Esse evento é disparado quando você pressiona uma tecla do teclado.
Dentro de SDL_Event temos um evento SDL_Keyboard que contém a informação do evento de pressionamento de tecla. Dentro desse evento temos um SDL_Keysym que contém a informação sobre a tecla que foi pressionada. O Keysym contém um Keycode que identifica a tecla que foi pressionada.
Como você pode ver, o que esse código faz é definir a superfície com base na tecla que foi pressionada. Dê uma olhada na documentação do SDL se quiser ver quais os Keycodes das outras teclas.
//Apply the current image SDL_BlitSurface( gCurrentSurface, NULL, gScreenSurface, NULL ); //Update the surface SDL_UpdateWindowSurface( gWindow );
Depois que tratamos quais teclas foram pressionadas e a superfície foi selecionada, nós exibimos a superfície selecionada na tela.
Baixe os arquivos de imagem e o código fonte para esse artigo aqui.