From cc63e9e33871471159a112d6d3aed79ced5f8e33 Mon Sep 17 00:00:00 2001 From: TheoryOfNekomata Date: Tue, 17 May 2022 09:37:51 +0800 Subject: [PATCH] Add gamepad/joystick support Implement event handling for gamepads and joysticks. In addition, the events are ordered according to SDL compatibility. --- src/packages/game/IZ_action.h | 21 ++++++- src/packages/game/IZ_app.c | 5 ++ src/packages/game/IZ_app.h | 7 +++ src/packages/game/config/IZ_config.c | 35 +++++++++-- src/packages/game/config/IZ_config.h | 63 ++++++++++++++----- src/packages/game/main.c | 92 +++++++++++++++++++++++----- 6 files changed, 186 insertions(+), 37 deletions(-) diff --git a/src/packages/game/IZ_action.h b/src/packages/game/IZ_action.h index 9030a78..e961cf7 100644 --- a/src/packages/game/IZ_action.h +++ b/src/packages/game/IZ_action.h @@ -6,10 +6,10 @@ typedef uint16_t IZ_Action; static const char* ACTION_NAMES[CONTROLS] = { + "Up", "Right", "Down", "Left", - "Up", "Affirm", "Negate", "Action0", @@ -24,4 +24,23 @@ static const char* ACTION_NAMES[CONTROLS] = { "Action9", }; +typedef enum { + IZ_ACTION_INDEX_UP, + IZ_ACTION_INDEX_RIGHT, + IZ_ACTION_INDEX_DOWN, + IZ_ACTION_INDEX_LEFT, + IZ_ACTION_INDEX_AFFIRM, + IZ_ACTION_INDEX_NEGATE, + IZ_ACTION_INDEX_ACTION0, + IZ_ACTION_INDEX_ACTION1, + IZ_ACTION_INDEX_ACTION2, + IZ_ACTION_INDEX_ACTION3, + IZ_ACTION_INDEX_ACTION4, + IZ_ACTION_INDEX_ACTION5, + IZ_ACTION_INDEX_ACTION6, + IZ_ACTION_INDEX_ACTION7, + IZ_ACTION_INDEX_ACTION8, + IZ_ACTION_INDEX_ACTION9, +} IZ_ActionIndex; + #endif diff --git a/src/packages/game/IZ_app.c b/src/packages/game/IZ_app.c index 8398bbb..bb1d47f 100644 --- a/src/packages/game/IZ_app.c +++ b/src/packages/game/IZ_app.c @@ -4,5 +4,10 @@ int IZ_InitializeApp(IZ_App* app) { IZ_LoadConfig(&app->config); IZ_SaveConfig(&app->config); + for (uint8_t p = 0; p < PLAYERS; p += 1) { + app->assigned_joysticks[p] = NULL; + app->actions[p] = 0; + } + return 0; } diff --git a/src/packages/game/IZ_app.h b/src/packages/game/IZ_app.h index f021189..49fef2c 100644 --- a/src/packages/game/IZ_app.h +++ b/src/packages/game/IZ_app.h @@ -1,10 +1,17 @@ #ifndef IZ_APP_H #define IZ_APP_H +#include + #include "config/IZ_config.h" +#include "IZ_action.h" typedef struct { IZ_Config config; + + IZ_Action actions[PLAYERS]; + + SDL_Joystick* assigned_joysticks[PLAYERS]; } IZ_App; int IZ_InitializeApp(IZ_App*); diff --git a/src/packages/game/config/IZ_config.c b/src/packages/game/config/IZ_config.c index c562cbd..0d92f66 100644 --- a/src/packages/game/config/IZ_config.c +++ b/src/packages/game/config/IZ_config.c @@ -22,6 +22,11 @@ void IZ_SaveConfig(IZ_Config* config) { fprintf_s(fp, "[Video]\n"); fprintf_s(fp, "Width=%u\n", config->video.width); fprintf_s(fp, "Height=%u\n", config->video.height); + fprintf_s(fp, "MaxFps=%u\n", config->video.max_fps); + fprintf_s(fp, "\n"); + + fprintf_s(fp, "[Input]\n"); + fprintf_s(fp, "GamepadAxisThreshold=%u\n", config->input.gamepad_axis_threshold); fprintf_s(fp, "\n"); for (uint8_t p = 0; p < PLAYERS; p += 1) { @@ -29,6 +34,12 @@ void IZ_SaveConfig(IZ_Config* config) { for (uint8_t i = 0; i < CONTROLS; i += 1) { fprintf_s(fp, "%s=%s\n", ACTION_NAMES[i], SDL_GetKeyName(config->controls[p].keyboard[i])); } + + fprintf_s(fp, "\n"); + fprintf_s(fp, "[Controls.%u.Joystick]\n", p); + for (uint8_t i = 4; i < CONTROLS; i += 1) { + fprintf_s(fp, "%s=%d\n", ACTION_NAMES[i], config->controls[p].gamepad[i]); + } } } @@ -37,11 +48,25 @@ void IZ_LoadConfig(IZ_Config* config) { IZ_GetConfigPath(config_path); config->video.width = ini_getl("Video", "Width", 640l, config_path); config->video.height = ini_getl("Video", "Height", 480l, config_path); + config->video.max_fps = ini_getl("Video", "MaxFps", 30, config_path); + config->input.gamepad_axis_threshold = ini_getl("Input", "GamepadAxisThreshold", 8000, config_path); char buffer[128]; - char section_name[20] = "Controls.0.Keyboard"; - for (uint8_t i = 0; i < CONTROLS; i += 1) { - section_name[9] = (char) (48 + i); - ini_gets(section_name, ACTION_NAMES[i], SDL_GetKeyName(IZ_DEFAULT_KEYBOARD_CONTROLS[i]), buffer, 128, config_path); - config->controls[0].keyboard[i] = SDL_GetKeyFromName(buffer); + char keyboard_section_name[20] = "Controls.0.Keyboard"; + for (uint8_t p = 0; p < PLAYERS; p += 1) { + keyboard_section_name[9] = (char) (48 + p); + for (uint8_t i = 0; i < CONTROLS; i += 1) { + ini_gets(keyboard_section_name, ACTION_NAMES[i], SDL_GetKeyName(IZ_DEFAULT_KEYBOARD_CONTROLS[p][i]), buffer, 128, + config_path); + config->controls[p].keyboard[i] = SDL_GetKeyFromName(buffer); + } + } + + char joystick_section_name[20] = "Controls.0.Joystick"; + for (uint8_t p = 0; p < PLAYERS; p += 1) { + joystick_section_name[9] = (char) (48 + p); + + for (uint8_t i = 4; i < CONTROLS; i += 1) { + config->controls[p].gamepad[i] = ini_getl(joystick_section_name, ACTION_NAMES[i], IZ_DEFAULT_JOYSTICK_CONTROLS[p][i], config_path); + } } } diff --git a/src/packages/game/config/IZ_config.h b/src/packages/game/config/IZ_config.h index 464c4cc..aa07b51 100644 --- a/src/packages/game/config/IZ_config.h +++ b/src/packages/game/config/IZ_config.h @@ -7,8 +7,13 @@ typedef struct { uint16_t width; uint16_t height; + uint8_t max_fps; } IZ_VideoConfig; +typedef struct { + uint16_t gamepad_axis_threshold; +} IZ_InputConfig; + typedef SDL_KeyCode IZ_KeyCode; typedef int IZ_PadButton; @@ -20,26 +25,50 @@ typedef struct { typedef struct { IZ_VideoConfig video; + IZ_InputConfig input; IZ_ControlsConfig controls[PLAYERS]; } IZ_Config; -static const IZ_KeyCode IZ_DEFAULT_KEYBOARD_CONTROLS[CONTROLS] = { - SDLK_RIGHT, - SDLK_DOWN, - SDLK_LEFT, - SDLK_UP, - SDLK_RETURN, // yes - SDLK_BACKSPACE, // no - SDLK_a, // action0 - SDLK_s, // action1 - SDLK_d, // action2 - SDLK_f, // action3 - SDLK_z, // action4 - SDLK_x, // action5 - SDLK_c, // action6 - SDLK_v, // action7 - SDLK_w, // action8 - SDLK_e, // action9 +static const IZ_KeyCode IZ_DEFAULT_KEYBOARD_CONTROLS[PLAYERS][CONTROLS] = { + { + SDLK_UP, + SDLK_RIGHT, + SDLK_DOWN, + SDLK_LEFT, + SDLK_RETURN, // yes + SDLK_BACKSPACE, // no + SDLK_a, // action0 + SDLK_s, // action1 + SDLK_d, // action2 + SDLK_f, // action3 + SDLK_z, // action4 + SDLK_x, // action5 + SDLK_c, // action6 + SDLK_v, // action7 + SDLK_w, // action8 + SDLK_e, // action9 + }, +}; + +static const uint8_t IZ_DEFAULT_JOYSTICK_CONTROLS[PLAYERS][CONTROLS] = { + { + 255, + 255, + 255, + 255, + 11, + 10, + 1, + 0, + 4, + 3, + 6, + 7, + 8, + 9, + 13, + 14, + }, }; void IZ_GetConfigPath(char* config_path); diff --git a/src/packages/game/main.c b/src/packages/game/main.c index 2173639..59c2316 100644 --- a/src/packages/game/main.c +++ b/src/packages/game/main.c @@ -39,7 +39,6 @@ int main(int argc, char* args[]) { SDL_Event e; screen_surface = SDL_GetWindowSurface(window); - IZ_Action action = 0; while (!quit) { SDL_FillRect(screen_surface, NULL, SDL_MapRGB(screen_surface->format, 0x00, 0x00, 0x00)); uint64_t ticks = SDL_GetTicks64(); @@ -63,24 +62,89 @@ int main(int argc, char* args[]) { quit = true; } - for (uint8_t i = 0; i < CONTROLS; i += 1) { - // TODO do same for gamepad - if (e.key.keysym.sym == app.config.controls[0].keyboard[i]) { - const uint16_t bitflag = (0x1 << i); - if (e.type == SDL_KEYDOWN) { - action |= bitflag; - } else if (e.type == SDL_KEYUP) { - action &= ~bitflag; + // Handle joystick events + for (uint8_t current_player = 0; current_player < PLAYERS; current_player += 1) { + if (e.type == SDL_JOYDEVICEADDED) { + if (SDL_NumJoysticks() <= PLAYERS && !app.assigned_joysticks[current_player]) { + app.assigned_joysticks[current_player] = SDL_JoystickOpen(e.jdevice.which); + } + } + + if (e.type == SDL_JOYDEVICEREMOVED) { + if ( + app.assigned_joysticks[current_player] + && SDL_JoystickInstanceID(app.assigned_joysticks[current_player]) == e.jdevice.which + ) { + app.assigned_joysticks[current_player] = NULL; + } + } + + if (e.type == SDL_JOYAXISMOTION) { + if (e.jaxis.axis == 0 || e.jaxis.axis == 3) { + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_RIGHT); + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_LEFT); + if (e.jaxis.value > app.config.input.gamepad_axis_threshold) { + app.actions[current_player] |= (0x1 << IZ_ACTION_INDEX_RIGHT); + } else if (e.jaxis.value <= -app.config.input.gamepad_axis_threshold) { + app.actions[current_player] |= (0x1 << IZ_ACTION_INDEX_LEFT); + } + } + if (e.jaxis.axis == 1 || e.jaxis.axis == 4) { + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_UP); + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_DOWN); + if (e.jaxis.value > app.config.input.gamepad_axis_threshold) { + app.actions[current_player] |= (0x1 << IZ_ACTION_INDEX_DOWN); + } else if (e.jaxis.value <= -app.config.input.gamepad_axis_threshold) { + app.actions[current_player] |= (0x1 << IZ_ACTION_INDEX_UP); + } + } + } + + if (e.type == SDL_JOYHATMOTION) { + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_UP); + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_RIGHT); + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_DOWN); + app.actions[current_player] &= ~(0x1 << IZ_ACTION_INDEX_LEFT); + if (e.jhat.value != 0) { + app.actions[current_player] |= e.jhat.value; } } } - for (unsigned char i = 0; i < CONTROLS; i += 1) { + // Handle keyboard events + for (uint8_t current_player = 0; current_player < PLAYERS; current_player += 1) { + for (uint8_t i = 0; i < CONTROLS; i += 1) { + if (e.key.keysym.sym == app.config.controls[current_player].keyboard[i]) { + const uint16_t bitflag = (0x1 << i); + if (e.type == SDL_KEYDOWN) { + app.actions[current_player] |= bitflag; + } else if (e.type == SDL_KEYUP) { + app.actions[current_player] &= ~bitflag; + } + } + + if (i >= 4) { + if (e.jbutton.button == app.config.controls[current_player].gamepad[i]) { + const uint16_t bitflag = (0x1 << i); + if (e.type == SDL_JOYBUTTONDOWN) { + app.actions[current_player] |= bitflag; + } else if (e.type == SDL_JOYBUTTONUP) { + app.actions[current_player] &= ~bitflag; + } + } + } + } + } + } + + // Update window + for (uint8_t current_player = 0; current_player < PLAYERS; current_player += 1) { + for (uint8_t i = 0; i < CONTROLS; i += 1) { const uint8_t column = i % 4; const uint8_t row = i / 4; - const IZ_Action bitflag = (0x1 << i); + const IZ_Action bitflag = (0x1 << i); const uint8_t size = 4; - if (action & bitflag) { + if (app.actions[current_player] & bitflag) { SDL_FillRect(screen_surface, &(SDL_Rect) { column * size, row * size, @@ -89,9 +153,9 @@ int main(int argc, char* args[]) { }, SDL_MapRGB(screen_surface->format, 0xff, 0xff, 0x00)); } } - - SDL_UpdateWindowSurface(window); } + + SDL_UpdateWindowSurface(window); } SDL_DestroyWindow(window); SDL_Quit();