Continuando nossa série de artigos traduzido do site lazyfoo, veremos agora com ler os dados originados de um controle de videogame, ao invés do teclado/mouse.
Assim como no caso do mouse e teclado, o SDL possui a habilidade de ler a entrada de um joystick. Nesse artigo, iremos fazer uma seta se mover baseado nos dados de entrada de um dispositivo desse tipo.
//Analog joystick dead zone const int JOYSTICK_DEAD_ZONE = 8000;
O jeito que o SDL usa para lidar com os dados analógicos de um joystick é converter a posição em um número entre -32768 e 32767. Isso significa que um toque de leve poderia reportar uma posição superior a 1000. Nós queremos ignorar toques de leve, assim queremos criar uma zona morta onde os dados de entrada do joystick são ignorados. Por isso definimos essa constante aqui e veremos como isso funciona mais tarde.
//Game Controller 1 handler SDL_Joystick* gGameController = NULL;
O tipo de dado para um joystick é o SDL_Joystick. Aqui declaramos o manipulador para o joystick que usaremos para interagir com ele no código.
bool init() { //Initialization flag bool success = true; //Initialize SDL if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK ) < 0 ) { printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() ); success = false; }
Isso é importante:
Até agora, só inicializamos vídeo para renderiza-lo na tela. Agora precisamos inicializar um subsistema para interagir com o joystick ou ler os dados vindo desse dispositivo não funcionará.
Até agora, só inicializamos vídeo para renderiza-lo na tela. Agora precisamos inicializar um subsistema para interagir com o joystick ou ler os dados vindo desse dispositivo não funcionará.
//Set texture filtering to linear if( !SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" ) ) { printf( "Warning: Linear texture filtering not enabled!" ); } //Check for joysticks if( SDL_NumJoysticks() < 1 ) { printf( "Warning: No joysticks connected!\n" ); } else { //Load joystick gGameController = SDL_JoystickOpen( 0 ); if( gGameController == NULL ) { printf( "Warning: Unable to open game controller! SDL Error: %s\n", SDL_GetError() ); } }
Após a inicialização desse subsistema, queremos abrir o joystick para uso. Primeiro, chamamos SDL_NumJoystick para verificar se temos pelo menos um joystick conectado. Se existir, chamamos SDL_JoystickOpen para abrir o joystick com indíce 0. Depois que o joystick é aberto, ele irá reportar eventos para a fila de eventos do SDL.
void close() { //Free loaded images gArrowTexture.free(); //Close game controller SDL_JoystickClose( gGameController ); gGameController = NULL; //Destroy window SDL_DestroyRenderer( gRenderer ); SDL_DestroyWindow( gWindow ); gWindow = NULL; gRenderer = NULL; //Quit SDL subsystems IMG_Quit(); SDL_Quit(); }
Quando tivermos finalizado o uso do joystick, precisamos fecha-lo com a função SDL_JoystickClose.
//Main loop flag bool quit = false; //Event handler SDL_Event e; //Normalized direction int xDir = 0; int yDir = 0;
No exemplo desse artigo, queremos rastrear a posição x e y do joystick. Se x for qual a -1, a posição x do joystick aponta para a esquerda. Se for +1, a posição x aponta para a direita. A posição y para o joystick é positiva quando aponta para cima e negativa quando aponta para baixo, de forma que y=+1 significa para cima e y=-1 para baixo. Se x ou y forem 0, significa que estamos da zona morta e no centro.
//Handle events on queue while( SDL_PollEvent( &e ) != 0 ) { //User requests quit if( e.type == SDL_QUIT ) { quit = true; } else if( e.type == SDL_JOYAXISMOTION ) { //Motion on controller 0 if( e.jaxis.which == 0 ) { //X axis motion if( e.jaxis.axis == 0 ) { //Left of dead zone if( e.jaxis.value < -JOYSTICK_DEAD_ZONE ) { xDir = -1; } //Right of dead zone else if( e.jaxis.value > JOYSTICK_DEAD_ZONE ) { xDir = 1; } else { xDir = 0; } }
Em nosso loop de eventos, nós iremos verificar se o joystick se moveu usando a função SDL_JoyAxisEvent. A variável which informa de qual joystick o movimento veio, e aqui verificamos que o evento veio do joystick 0.
Em seguida, queremos verificar se temos movimento na direção x ou na direção y, que é indicado pela variável axis. Tipicamente axis=0 representa o eixo x.
A variável value informa qual a posição analógica nos eixos. Se a posição x for menor do que a zona morta, a direção é configurada para negativa. Se for maior, a direção é configurada para positiva. Se estiver na zona morta, a direção é configurada para 0.
Em seguida, queremos verificar se temos movimento na direção x ou na direção y, que é indicado pela variável axis. Tipicamente axis=0 representa o eixo x.
A variável value informa qual a posição analógica nos eixos. Se a posição x for menor do que a zona morta, a direção é configurada para negativa. Se for maior, a direção é configurada para positiva. Se estiver na zona morta, a direção é configurada para 0.
//Y axis motion else if( e.jaxis.axis == 1 ) { //Below of dead zone if( e.jaxis.value < -JOYSTICK_DEAD_ZONE ) { yDir = -1; } //Above of dead zone else if( e.jaxis.value > JOYSTICK_DEAD_ZONE ) { yDir = 1; } else { yDir = 0; } } } } }
Fazemos a mesma coisa novamente para o eixo y, que é identificado com o id 1.
//Clear screen SDL_SetRenderDrawColor( gRenderer, 0xFF, 0xFF, 0xFF, 0xFF ); SDL_RenderClear( gRenderer ); //Calculate angle double joystickAngle = atan2( (double)yDir, (double)xDir ) * ( 180.0 / M_PI ); //Correct angle if( xDir == 0 && yDir == 0 ) { joystickAngle = 0; }
Antes de renderizarmos a seta que irá apontar para a direção que o direcional analógico do joystick foi pressionado, precisamos calcular o ângulo. Fazemos isso usando a função atan2 do cmath, que representa o arco tangente 2, ou tangente inversa 2.
Para quem está familiarizado com trigonometria, essa é basicamente a função tangente inversa com algum código adicional que leva em consideração em qual quadrante os valores estão.
Para quem está familiarizado apenas com geometria, apenas saiba que você fornece a posição y e x e irá obter o ângulo em radianos. O SDL necessita do ângulo de rotação em graus, então nós temos que converter de radianos para graus multiplicando por 180 dividido por Pi.
Quando tanto a posição x quanto y são 0, poderiamos obter lixo como ângulo, então precisamos corrigir o valor para 0.
Para quem está familiarizado com trigonometria, essa é basicamente a função tangente inversa com algum código adicional que leva em consideração em qual quadrante os valores estão.
Para quem está familiarizado apenas com geometria, apenas saiba que você fornece a posição y e x e irá obter o ângulo em radianos. O SDL necessita do ângulo de rotação em graus, então nós temos que converter de radianos para graus multiplicando por 180 dividido por Pi.
Quando tanto a posição x quanto y são 0, poderiamos obter lixo como ângulo, então precisamos corrigir o valor para 0.
//Render joystick 8 way angle gArrowTexture.render( ( SCREEN_WIDTH - gArrowTexture.getWidth() ) / 2, ( SCREEN_HEIGHT - gArrowTexture.getHeight() ) / 2, NULL, joystickAngle ); //Update screen SDL_RenderPresent( gRenderer );
Finalmente renderizamos a seta na tela.
Existem outros eventos para joysticks como button presses, pov hats, and pluggin in or removing a controller. Eles são bastantes simples e você deve ser capaz de utiliza-los somente dando uma olhada rápida na documentação e experimentando-os.
Existem outros eventos para joysticks como button presses, pov hats, and pluggin in or removing a controller. Eles são bastantes simples e você deve ser capaz de utiliza-los somente dando uma olhada rápida na documentação e experimentando-os.