gamepad support on pc and web
This commit is contained in:
parent
1db0a870d9
commit
acb62ece0b
|
@ -96,7 +96,9 @@
|
|||
"input":
|
||||
{
|
||||
"suppress any key on mods": true,
|
||||
"any key ignore commands": ["left", "right", "up", "down", "pause"]
|
||||
"any key ignore commands": ["left", "right", "up", "down", "pause"],
|
||||
"gamepad pause button index": 9,
|
||||
"gamepad axis cooldown": 0.2
|
||||
},
|
||||
|
||||
"keys":
|
||||
|
@ -281,6 +283,7 @@
|
|||
"play texture": "resource/Play_Button.png",
|
||||
"play translation": [0.0, 0.0],
|
||||
"play scale": 0.6,
|
||||
"play scale ratio": 1.0,
|
||||
"name":
|
||||
{
|
||||
"arrow dimensions": [80.0, 40.0],
|
||||
|
@ -410,7 +413,7 @@
|
|||
"unlock buffalo": "✩ UNLOCKED BUFFALO BEEF CAKE ✩",
|
||||
"unlock jackpot": "✩ UNLOCKED GOLDEN CAKE ✩",
|
||||
"new best": "✩ NEW BEST TIME ✩",
|
||||
"thanks": "Thank you playtesters @carlosissurreal @snakesandrews @paotato @big__flan @8ude @emilyrakoonce @xed @markkleeb @sortasoft @toddwords @computerlunch @synodai @sleepin @artfail @wondervillenyc @gumbo_nyc",
|
||||
"thanks": " ",
|
||||
"thanks translation": [0.3, -0.78],
|
||||
"thanks scale": [1.58, 0.13],
|
||||
"thanks wrap": 1080
|
||||
|
|
2
lib/sb
2
lib/sb
|
@ -1 +1 @@
|
|||
Subproject commit 8184a62a54feda11ea9543a7e615c4289ee0e593
|
||||
Subproject commit 8e62f9e0a223deb5a48a67f401c6433257d6a936
|
Binary file not shown.
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 108 KiB |
135
src/Cakefoot.cpp
135
src/Cakefoot.cpp
|
@ -104,12 +104,19 @@ Cakefoot::Cakefoot()
|
|||
challenge_index = configuration()("progress", "current challenge");
|
||||
view_index = configuration()("progress", "current view");
|
||||
|
||||
/* Subscribe to command events */
|
||||
/* Subscribe to events */
|
||||
delegate().subscribe(&Cakefoot::respond, this);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEMOTION);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEBUTTONDOWN);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEBUTTONUP);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_MOUSEWHEEL);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYAXISMOTION);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYBUTTONDOWN);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_JOYBUTTONUP);
|
||||
delegate().subscribe(&Cakefoot::respond, this, SDL_KEYDOWN);
|
||||
|
||||
/* Open a game controller if any are available at the beginning of the program */
|
||||
open_game_controller();
|
||||
|
||||
/* Set up playing field, the plane that provides the background of the curve, character, and enemies */
|
||||
sb::Plane playing_field_plane;
|
||||
|
@ -180,13 +187,59 @@ Cakefoot::Cakefoot()
|
|||
|
||||
#if defined(EMSCRIPTEN)
|
||||
/* Pause the game when the browser tab is hidden */
|
||||
if (emscripten_set_visibilitychange_callback(nullptr, false, &respond_to_visibility_change) < 0)
|
||||
if (emscripten_set_visibilitychange_callback(this, false, &respond_to_visibility_change) < 0)
|
||||
{
|
||||
sb::Log::log("Failed to enable browser visibility change automatic pause feature", sb::Log::WARN);
|
||||
}
|
||||
|
||||
/* Open the game controller when it is connected */
|
||||
if (emscripten_set_gamepadconnected_callback(this, false, &respond_to_gamepad_connected) < 0)
|
||||
{
|
||||
sb::Log::log("Failed to listen for gamepad connections", sb::Log::WARN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Cakefoot::open_game_controller()
|
||||
{
|
||||
for (int ii = 0, jj = 0; ii < SDL_NumJoysticks(); ii++)
|
||||
{
|
||||
if (SDL_IsGameController(ii))
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Gamepad #" << ++jj << ": ";
|
||||
std::string name {SDL_GameControllerNameForIndex(ii)};
|
||||
if (name == "")
|
||||
{
|
||||
name = "[unnamed]";
|
||||
}
|
||||
message << name;
|
||||
sb::Log::log(message);
|
||||
if (controller.get() == nullptr)
|
||||
{
|
||||
controller = std::shared_ptr<SDL_GameController>(SDL_GameControllerOpen(ii), SDL_GameControllerClose);
|
||||
std::ostringstream message;
|
||||
if (controller.get() == nullptr)
|
||||
{
|
||||
message << "Could not open gamepad #" << jj;
|
||||
sb::Log::sdl_error(message.str());
|
||||
}
|
||||
else
|
||||
{
|
||||
message << "Opened gamepad #" << jj;
|
||||
sb::Log::log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ostringstream message;
|
||||
message << "Joystick #" << ii << " cannot be loaded by the game controller API";
|
||||
sb::Log::log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cakefoot::load_audio()
|
||||
{
|
||||
audio = {};
|
||||
|
@ -611,7 +664,8 @@ void Cakefoot::set_up_buttons()
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
sb::Plane play_plane;
|
||||
play_plane.texture(play_texture);
|
||||
button.at("play") = sb::Pad<>{play_plane, configuration()("button", "play translation"), configuration()("button", "play scale"), 1.0f};
|
||||
button.at("play") = sb::Pad<>{play_plane, configuration()("button", "play translation"), configuration()("button", "play scale"),
|
||||
configuration()("button", "play scale ratio")};
|
||||
button.at("play").state(original_state);
|
||||
button.at("play").on_state_change([&](bool state){
|
||||
load_level(0);
|
||||
|
@ -1614,6 +1668,53 @@ void Cakefoot::refresh_scoreboard()
|
|||
|
||||
void Cakefoot::respond(SDL_Event& event)
|
||||
{
|
||||
/* Translate gamepad input to commands */
|
||||
if (event.type == SDL_JOYBUTTONDOWN)
|
||||
{
|
||||
if (level_index > 0 && static_cast<std::size_t>(level_index) <= configuration()("levels").size() - 2 &&
|
||||
event.jbutton.button == configuration()("input", "gamepad pause button index"))
|
||||
{
|
||||
sb::Delegate::post("pause");
|
||||
}
|
||||
else if (!use_play_button || button.at("play").pressed())
|
||||
{
|
||||
sb::Delegate::post("any");
|
||||
}
|
||||
}
|
||||
else if (event.type == SDL_JOYBUTTONUP)
|
||||
{
|
||||
sb::Delegate::post("any", true);
|
||||
}
|
||||
else if (event.type == SDL_JOYAXISMOTION && !cooldown_animation.playing())
|
||||
{
|
||||
if (event.jaxis.axis == 1)
|
||||
{
|
||||
if (event.jaxis.value > 15000)
|
||||
{
|
||||
sb::Delegate::post("down");
|
||||
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
|
||||
}
|
||||
else if (event.jaxis.value < -15000)
|
||||
{
|
||||
sb::Delegate::post("up");
|
||||
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (event.jaxis.value > 15000)
|
||||
{
|
||||
sb::Delegate::post("right");
|
||||
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
|
||||
}
|
||||
else if (event.jaxis.value < -15000)
|
||||
{
|
||||
sb::Delegate::post("left");
|
||||
cooldown_animation.play_once(configuration()("input", "gamepad axis cooldown"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Get mouse button states */
|
||||
bool left_mouse_pressed = SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK;
|
||||
bool shift_pressed = SDL_GetModState() & KMOD_SHIFT;
|
||||
|
@ -2065,6 +2166,12 @@ void Cakefoot::respond(SDL_Event& event)
|
|||
hovering = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Any keyboard input causes the play button to press */
|
||||
else if (sb::Delegate::compare(event, "any"))
|
||||
{
|
||||
button.at("play").press();
|
||||
}
|
||||
}
|
||||
|
||||
/* Always collide with volume button and fullscreen if enabled */
|
||||
|
@ -2096,6 +2203,11 @@ void Cakefoot::respond(SDL_Event& event)
|
|||
}
|
||||
}
|
||||
|
||||
bool Cakefoot::paused() const
|
||||
{
|
||||
return !unpaused_timer;
|
||||
}
|
||||
|
||||
void Cakefoot::run()
|
||||
{
|
||||
/* Start timers precisely when the game update loop starts */
|
||||
|
@ -2125,6 +2237,7 @@ void Cakefoot::update(float timestamp)
|
|||
shift_hue_animation.update(timestamp);
|
||||
flash_animation.update(timestamp);
|
||||
blink_animation.update(timestamp);
|
||||
cooldown_animation.update(timestamp);
|
||||
|
||||
/* Transformation for looking at the center of the field of play from the camera position. */
|
||||
view = glm::lookAt(camera_position, {0.0f, 0.0f, 0.0f}, glm::vec3{0.0f, 1.0f, 0.0f});
|
||||
|
@ -2664,15 +2777,29 @@ void Cakefoot::update(float timestamp)
|
|||
sb::Log::gl_errors("at end of update");
|
||||
}
|
||||
|
||||
void Cakefoot::quit()
|
||||
{
|
||||
controller.reset();
|
||||
super::quit();
|
||||
}
|
||||
|
||||
#if defined(EMSCRIPTEN)
|
||||
EM_BOOL respond_to_visibility_change(int event_type, const EmscriptenVisibilityChangeEvent* visibility_change_event, void* user_data)
|
||||
{
|
||||
if (visibility_change_event->hidden)
|
||||
Cakefoot* game = reinterpret_cast<Cakefoot*>(user_data);
|
||||
if (visibility_change_event->hidden && !game->paused())
|
||||
{
|
||||
sb::Delegate::post("pause", false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
EM_BOOL respond_to_gamepad_connected(int event_type, const EmscriptenGamepadEvent* gamepad_event, void* user_data)
|
||||
{
|
||||
Cakefoot* game = reinterpret_cast<Cakefoot*>(user_data);
|
||||
game->open_game_controller();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
int main()
|
||||
|
|
|
@ -263,6 +263,7 @@ private:
|
|||
sb::Color rotating_hue {128, 0, 0, 0};
|
||||
std::vector<sb::Text> ending_messages;
|
||||
std::optional<std::string> selected;
|
||||
std::shared_ptr<SDL_GameController> controller = nullptr;
|
||||
|
||||
/*!
|
||||
* Load sound effects and music into objects that can be used by the SDL mixer library. Use chunk objects for background music instead of
|
||||
|
@ -451,6 +452,9 @@ private:
|
|||
/* Toggle visibility flag every interval */
|
||||
Animation blink_animation {std::bind(&Cakefoot::blink, this), configuration()("display", "blink frequency")};
|
||||
|
||||
/* Count a cooldown period for gamepad axes */
|
||||
Animation cooldown_animation;
|
||||
|
||||
/*!
|
||||
* Get the arcade time as the amount of time remaining before the limit is reached.
|
||||
*
|
||||
|
@ -479,11 +483,21 @@ public:
|
|||
*/
|
||||
Cakefoot();
|
||||
|
||||
/*!
|
||||
* Log detected joysticks and open the first one that is usable as a game controller
|
||||
*/
|
||||
void open_game_controller();
|
||||
|
||||
/*!
|
||||
* Respond to command events
|
||||
*/
|
||||
void respond(SDL_Event&);
|
||||
|
||||
/*!
|
||||
* @return true if the game is currently paused
|
||||
*/
|
||||
bool paused() const;
|
||||
|
||||
/*!
|
||||
* Start timers, enable auto refresh, and run the super class's run function, which starts the game's update loop.
|
||||
*
|
||||
|
@ -498,6 +512,10 @@ public:
|
|||
*/
|
||||
void update(float timestamp);
|
||||
|
||||
/*!
|
||||
* Close the controller, then call Game::quit()
|
||||
*/
|
||||
void quit();
|
||||
};
|
||||
|
||||
#if defined(EMSCRIPTEN)
|
||||
|
@ -508,10 +526,25 @@ public:
|
|||
*
|
||||
* @param event_type Emscripten's ID for the visibilitychange event
|
||||
* @param visibility_change_event Emscripten's event object
|
||||
* @param user_data Emscripten's API allows wser data to be passed, but this is treated as null
|
||||
* @param user_data The game object passed as a void pointer through Emscripten's API
|
||||
* @return True to indicate that the event was consumed by the handler
|
||||
*/
|
||||
EM_BOOL respond_to_visibility_change(int event_type, const EmscriptenVisibilityChangeEvent* visibility_change_event, void* user_data);
|
||||
|
||||
/*!
|
||||
* This event handler should be registered with emscripten_set_gamepadconnected_callback so it will be called automatically when a
|
||||
* gamepad is connected. If there isn't a gamepad already registered, the first detected gamepad will be registered as the game's
|
||||
* controller.
|
||||
*
|
||||
* Even if a USB gamepad is connected when the program loads, the web browser may not register it as connected until a button is
|
||||
* pressed, so this event will need to be triggered before any controller is usable on a web page.
|
||||
*
|
||||
* @param event_type Emscripten's ID for the gamepadconnected event
|
||||
* @param visibility_change_event Emscripten's event object
|
||||
* @param user_data The game object passed as a void pointer through Emscripten's API
|
||||
* @return True to indicate that the event was consumed by the handler
|
||||
*/
|
||||
EM_BOOL respond_to_gamepad_connected(int event_type, const EmscriptenGamepadEvent* gamepad_event, void* user_data);
|
||||
#endif
|
||||
|
||||
/*!
|
||||
|
|
Loading…
Reference in New Issue