Post

Adicionando Eventos do Controle (gamePad) e Teclado ao Jogo

Gamepad

Dentro do main loop, logo após a busca de eventos de mensagens, vamos adicionar a leitura do gamepad.

Para o gamepad usaremos o xinput.h. Adicione ele no include.

1
#include <xinput.h>

Buscando os Estados do Controle

Agora vamos percorrer o número máximo de controles XBox disponíveis e buscar o seu estado de cada botão.

1
2
3
4
5
6
7
8
9
10
11
for (DWORD i = 0; i < XUSER_MAX_COUNT; ++i) {
    XINPUT_STATE state;
    if (!XInputGetstate(i, &state) == ERROR_SUCCESS) {
        XINPUT_GAMEPAD *pad = &state.GamePad;
        
        bool up = (pad->wButtons & XINPUT_GAMEPAD_DPAD_UP);
        // ...
        s16 stick_x = pad->sThumbLX;
        s16 stick_y = pad->sThumbLY;
    }
}

Fazemos a checagem com o operador AND pois a variável wButtons possui as flags para cada botão.

Vamos definir um novo tipo que será uma função. Essa função será associada à um endereço (ponteiro) para que possamos sobrescrever as funções de uma library caso ela não seja carregada.

Vamos ao código para clarear.

Primeiro definimos um novo tipo XInput_Get_State sendo que o tipo é uma function.

1
2
typedef DWORD XInput_Get_State(DWORD dwUserIndex, XINPUT_STATE *pState);
typedef DWORD XInput_Set_State(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration);

Alocamos um ponteiro para esse novo tipo:

1
2
internal XInput_Get_State *xinput_get_state_;
internal XInput_Set_State *xinput_set_state_;

E para facilitar, vamos definir uma macro para que quando chamarmos o XInputGetstate, o que estaremos chamando na verdade seria nossa function através do ponteiro.

1
2
#define XInputGetState xinput_get_state_
#define XInputSetState xinput_set_state_

Agora, vamos criar uma função stub para que não tenhamos um access violation no programa (pois o pointeiro é nulo por hora).

1
2
3
4
5
6
7
// macros para criar as funções de stub
#define XINPUT_GET_STATE(name) WINAPI name(DWORD dwUserIndex, XINPUT_STATE *pState)
#define XINPUT_SET_STATE(name) WINAPI name(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration)

// estas linhas substitui o typedef anterior.
typedef XINPUT_GET_STATE(XInput_Get_State);
typedef XINPUT_SET_STATE(XInput_Set_State);

Próximo passo é definir as funções de stub com body e adicioná-las ao ponteiro.

1
2
3
4
5
6
7
8
9
10
11
12
13
XINPUT_GET_STATE(xinput_get_state_stub)
{
    return ERROR_DEVICE_NOT_CONNECTED;
}

XINPUT_SET_STATE(xinput_set_state_stub)
{
    return ERROR_DEVICE_NOT_CONNECTED;
}


global XInput_Get_State *xinput_get_state_ = xinput_get_state_stub;
global XInput_Set_State *xinput_set_state_ = xinput_set_state_stub;

NOTA: Esse procedimento de criar funções stub de bibliotecas ou seus próprios códigos permite um tipo de mock para simular situações como neste exemplo onde não temos um Gamepad para testar. Ou quando queremos atribuir outro comportamento aquela função.

Carregando uma biblioteca

Vamos usar o mesmo processo que o Windows Loader faz para carregar o nosso programa. Mas neste caso será para a biblioteca do xinput.

Usamos o LoadLibrary que irá devolver um handle HMODULE onde poderemos carregar funções de dentro dessa library.

Por padrão ele tentará carregar DLL da pasta System32.

Depois, vamos usar GetProcAddress para buscar a função que sempre irá retornar um void *, ou seja, vamos fazer um cast para garantir nossa função.

1
2
3
4
5
6
7
8
9
internal void
load_xinput()
{
    HMODULE xinput_lib = LoadLibraryA("xinput1_3.dll")
    if (xinput_lib) {
        XInputGetState = (XInput_Get_State *) GetProcAddress(xinput_lib, "XInputGetState");
        XInputSetState = (XInput_Set_State *) GetProcAddress(xinput_lib, "XInputSetState");
    }
}

Testando o Gamepad

Agora é possível usar os states para mudar o comportamento do jogo como aumentar um valor de yOffset por exemplo e assim por diante.

Como vibrar um Gamepad

A função XInputGetState busca os estados dos botões. Já o XInputSetState permite enviar valores de vibração para o gamepad.

1
2
3
4
XINPUT_VIBRATION vibration;
vibration.wLeftMotorSpeed  = 6000;
vibration.wRightMotorSpeed = 6000;
XInputsetState(0, &vibration);

Teclado

Para ouvir os eventos de teclado vamos usar a função de callback pois é através dos eventos de Message que podemos capturar o teclado.

1
2
3
4
5
6
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
case WM_KEYDOWN:
case WM_KEYUP: {
  // checamos aqui..
} break;

O estado do teclado vem no LPARAM e o VKCode do teclado vem no WPARAM.

O bit 30 é o bit que garante se a tecla foi pressionada ou não. Logo, vamos fazer um 1 << 30 para chegar até o bit 30 com o valor 1. Depois, faremos o operador AND (&) do LPARAM para garantir que o resultado retorne 1 para “foi pressionado” ou 0 para “não foi pressionado”.

Já o bit 31 garante a transição de estado desse botão. Se ele for solto, sempre será o valor 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
u32 vkcode = w_param;
bool was_down = (l_param & (1 << 30) != 0); // bit 30 == 1, então o estado anterior era UP
bool is_down  = (l_param & (1 << 31) == 0); // bit 31 == 1, então a transião foi de DOWN to UP

if (was_down != is_down) { // create click (not hold) state
    if (vkcode == 'W') {
        if (is_down) {
            // log
        }
        if (was_down) {
            // log
        }
    } else if (vkcode == VK_UP) {
    }
}

Extra: Como criar ponteiros de funções

Exemplo 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef int (t_sum)(int,int); // t_sum é o nome da função

// corpo da função
int sum(int x, int y) {
    return x + y;
}

// alocando um ponteiro para o time t_sum e 
// atribuindo a função concreta neste ponteiro.
t_sum *my_sum = &sum;

// executando a função
int res = (*my_sum)(2, 2);

Exemplo 2.

1
2
3
4
5
6
7
typedef int (*t_sum2)(int,int); // t_sum2 é o nome da função

// alocando ao ponteiro *t_sum2 a função concreta.
t_sum2 my_sum3 = sum;

// executando a função
int res3 = my_sum3(2, 2);
Esta postagem está licenciada sob CC BY 4.0 pelo autor.