Quellcode durchsuchen

:construction: added initial controller support

game itself does not yet support controllers
Felix Bytow vor 1 Jahr
Ursprung
Commit
6c57fc5985
7 geänderte Dateien mit 271 neuen und 70 gelöschten Zeilen
  1. 27 0
      SDL.cxx
  2. 7 0
      SDL.hxx
  3. 13 0
      game/CreditsState.cxx
  4. 173 68
      game/MenuState.cxx
  5. 19 0
      game/MenuState.hxx
  6. 19 2
      game/SplashState.cxx
  7. 13 0
      main.cxx

+ 27 - 0
SDL.cxx

@@ -43,6 +43,11 @@ SDL::SDL(std::uint32_t const flags)
   }
   SDL_Log("Initialized SDL_ttf successfully.");
 
+  auto const num_joysticks = SDL_NumJoysticks();
+  for (int n = 0; n<num_joysticks; ++n) {
+    add_controller(n);
+  }
+
   instance_ = this;
 }
 
@@ -50,6 +55,11 @@ SDL::~SDL() noexcept
 {
   assert(instance_!=nullptr);
 
+  for (auto const controller: controllers_) {
+    SDL_Log("Closing controller %s.", SDL_GameControllerName(controller));
+    SDL_GameControllerClose(controller);
+  }
+
   TTF_Quit();
   SDL_Log("Shut down SDL_ttf successfully.");
 
@@ -73,4 +83,21 @@ SDL& SDL::require(std::uint32_t const flags) noexcept
   return instance();
 }
 
+void SDL::add_controller(int const which)
+{
+  controllers_.push_back(SDL_GameControllerOpen(which));
+  SDL_Log("Opened controller %s.", SDL_GameControllerNameForIndex(which));
+}
+
+void SDL::remove_controller(int which)
+{
+  auto const controller = SDL_GameControllerFromInstanceID(which);
+  SDL_Log("Closing controller %s.", SDL_GameControllerName(controller));
+  SDL_GameControllerClose(controller);
+  auto const it = std::find(controllers_.begin(), controllers_.end(), controller);
+  if (it!=controllers_.end()) {
+    controllers_.erase(it);
+  }
+}
+
 

+ 7 - 0
SDL.hxx

@@ -9,6 +9,7 @@
 #include <source_location>
 #include <stdexcept>
 #include <string_view>
+#include <vector>
 
 #include "NonCopyable.hxx"
 
@@ -45,8 +46,14 @@ public:
 
   static SDL& require(std::uint32_t flags) noexcept;
 
+  void add_controller(int which);
+
+  void remove_controller(int which);
+
 private:
   static SDL* instance_;
+
+  std::vector<SDL_GameController*> controllers_;
 };
 
 #endif // SNAKE_SDL_HXX

+ 13 - 0
game/CreditsState.cxx

@@ -62,6 +62,19 @@ void CreditsState::on_event(GameStateManager& gsm, SDL_Event const& event)
       break;
     }
   }
+  else if (event.type==SDL_CONTROLLERBUTTONUP) {
+    switch (event.cbutton.which) {
+    default:
+      break;
+    case SDL_CONTROLLER_BUTTON_A:
+      [[fallthrough]];
+    case SDL_CONTROLLER_BUTTON_B:
+      [[fallthrough]];
+    case SDL_CONTROLLER_BUTTON_START:
+      gsm.pop_state();
+      break;
+    }
+  }
 }
 
 void CreditsState::update(GameStateManager& gsm, std::chrono::milliseconds const delta_time)

+ 173 - 68
game/MenuState.cxx

@@ -9,6 +9,7 @@
 void MenuState::on_enter(GameStateManager& gsm)
 {
   active_button_ = 0;
+  last_controller_direction_ = 0;
 
   auto const& tm = TranslationManager::instance();
   new_game_button_.set_title(tm.get_translation("New game"));
@@ -58,74 +59,27 @@ void MenuState::on_enter(GameStateManager& gsm)
 
 void MenuState::on_event(GameStateManager& gsm, SDL_Event const& evt)
 {
-  if (evt.type==SDL_KEYUP) {
-    switch (evt.key.keysym.scancode) {
-    default:
-      break;
-    case SDL_SCANCODE_PAUSE:
-      if (gsm.parent()==nullptr)
-        break;
-      [[fallthrough]];
-    case SDL_SCANCODE_ESCAPE:
-      gsm.pop_state();
-      break;
-    case SDL_SCANCODE_UP:
-      SDL_ShowCursor(SDL_DISABLE);
-      --active_button_;
-      if (active_button_<0)
-        active_button_ = 4;
-      else if (active_button_==1 && !continue_button_.is_visible())
-        active_button_ = 0;
-      break;
-    case SDL_SCANCODE_DOWN:
-      SDL_ShowCursor(SDL_DISABLE);
-      ++active_button_;
-      if (active_button_>4)
-        active_button_ = 0;
-      else if (active_button_==1 && !continue_button_.is_visible())
-        active_button_ = 2;
-      break;
-    case SDL_SCANCODE_RETURN:
-      switch (active_button_) {
-      case 0:
-        new_game_button_.trigger();
-        break;
-      case 1:
-        continue_button_.trigger();
-        break;
-      case 2:
-        high_score_button_.trigger();
-        break;
-      case 3:
-        credits_button_.trigger();
-        break;
-      case 4:
-        quit_button_.trigger();
-        break;
-      }
-      break;
-    }
-  }
-  else if (evt.type==SDL_MOUSEMOTION) {
-    SDL_ShowCursor(SDL_ENABLE);
-
-    auto const x = evt.motion.x;
-    auto const y = evt.motion.y;
-
-    std::array<Button*, 5> buttons{&new_game_button_, nullptr, &high_score_button_, &credits_button_, &quit_button_};
-    if (continue_button_.is_visible())
-      buttons[1] = &continue_button_;
-
-    for (std::size_t n = 0; n<buttons.size(); ++n) {
-      if (buttons[n]==nullptr)
-        continue;
-
-      auto const box = buttons[n]->get_bounding_box();
-      if (x>=box.x && x<=box.x+box.w && y>=box.y && y<=box.y+box.h) {
-        active_button_ = static_cast<int>(n);
-        break;
-      }
-    }
+  switch (evt.type) {
+  default:
+    break;
+  case SDL_KEYUP:
+    handle_key_up(gsm, evt.key.keysym.scancode);
+    break;
+  case SDL_KEYDOWN:
+    handle_key_down(evt.key.keysym.scancode);
+    break;
+  case SDL_CONTROLLERBUTTONUP:
+    handle_controller_button_up(gsm, evt.cbutton.button);
+    break;
+  case SDL_CONTROLLERBUTTONDOWN:
+    handle_controller_button_down(evt.cbutton.button);
+    break;
+  case SDL_CONTROLLERAXISMOTION:
+    handle_controller_axis_motion(evt.caxis.axis, evt.caxis.value);
+    break;
+  case SDL_MOUSEMOTION:
+    handle_mouse_movement(evt.motion.x, evt.motion.y);
+    break;
   }
 }
 
@@ -196,3 +150,154 @@ void MenuState::on_leave()
 {
   SDL_ShowCursor(SDL_DISABLE);
 }
+
+void MenuState::select_previous_button()
+{
+  SDL_ShowCursor(SDL_DISABLE);
+  --active_button_;
+  if (active_button_<0)
+    active_button_ = 4;
+  else if (active_button_==1 && !continue_button_.is_visible())
+    active_button_ = 0;
+}
+
+void MenuState::select_next_button()
+{
+  SDL_ShowCursor(SDL_DISABLE);
+  ++active_button_;
+  if (active_button_>4)
+    active_button_ = 0;
+  else if (active_button_==1 && !continue_button_.is_visible())
+    active_button_ = 2;
+}
+
+void MenuState::handle_key_up(GameStateManager& gsm, SDL_Scancode const scancode)
+{
+  switch (scancode) {
+  default:
+    break;
+  case SDL_SCANCODE_PAUSE:
+    if (gsm.parent()==nullptr)
+      break;
+    [[fallthrough]];
+  case SDL_SCANCODE_ESCAPE:
+    gsm.pop_state();
+    break;
+  case SDL_SCANCODE_SPACE:
+    [[fallthrough]];
+  case SDL_SCANCODE_RETURN:
+    trigger_active_button();
+    break;
+  }
+}
+
+void MenuState::handle_key_down(SDL_Scancode const scancode)
+{
+  switch (scancode) {
+  default:
+    break;
+  case SDL_SCANCODE_UP:
+    select_previous_button();
+    break;
+  case SDL_SCANCODE_DOWN:
+    select_next_button();
+    break;
+  }
+}
+
+void MenuState::trigger_active_button()
+{
+  switch (active_button_) {
+  case 0:
+    new_game_button_.trigger();
+    break;
+  case 1:
+    continue_button_.trigger();
+    break;
+  case 2:
+    high_score_button_.trigger();
+    break;
+  case 3:
+    credits_button_.trigger();
+    break;
+  case 4:
+    quit_button_.trigger();
+    break;
+  }
+}
+
+void MenuState::handle_mouse_movement(int const x, int const y)
+{
+  SDL_ShowCursor(SDL_ENABLE);
+
+  std::array<Button*, 5> buttons{&new_game_button_, nullptr, &high_score_button_, &credits_button_, &quit_button_};
+  if (continue_button_.is_visible())
+    buttons[1] = &continue_button_;
+
+  for (std::size_t n = 0; n<buttons.size(); ++n) {
+    if (buttons[n]==nullptr)
+      continue;
+
+    auto const box = buttons[n]->get_bounding_box();
+    if (x>=box.x && x<=box.x+box.w && y>=box.y && y<=box.y+box.h) {
+      active_button_ = static_cast<int>(n);
+      break;
+    }
+  }
+}
+
+void MenuState::handle_controller_button_up(GameStateManager& gsm, std::uint8_t const button)
+{
+  switch (button) {
+  default:
+    break;
+  case SDL_CONTROLLER_BUTTON_START:
+    [[fallthrough]];
+  case SDL_CONTROLLER_BUTTON_B:
+    if (gsm.parent()==nullptr)
+      break;
+    gsm.pop_state();
+    break;
+  case SDL_CONTROLLER_BUTTON_A:
+    trigger_active_button();
+    break;
+  }
+}
+
+void MenuState::handle_controller_button_down(std::uint8_t const button)
+{
+  switch (button) {
+  default:
+    break;
+  case SDL_CONTROLLER_BUTTON_DPAD_UP:
+    select_previous_button();
+    break;
+  case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
+    select_next_button();
+    break;
+  }
+}
+
+void MenuState::handle_controller_axis_motion(std::uint8_t const axis, std::int16_t const value)
+{
+  if (axis!=SDL_CONTROLLER_AXIS_LEFTY)
+    return;
+
+  if (value<-10'000) {
+    if (last_controller_direction_==-1)
+      return;
+
+    last_controller_direction_ = -1;
+    select_previous_button();
+  }
+  else if (value>10'000) {
+    if (last_controller_direction_==1)
+      return;
+
+    last_controller_direction_ = 1;
+    select_next_button();
+  }
+  else {
+    last_controller_direction_ = 0;
+  }
+}

+ 19 - 0
game/MenuState.hxx

@@ -28,6 +28,24 @@ private:
   static int constexpr BUTTON_HEIGHT = 80;
   static int constexpr BUTTON_WIDTH = 350;
 
+  void handle_key_up(GameStateManager& gsm, SDL_Scancode scancode);
+
+  void handle_key_down(SDL_Scancode scancode);
+
+  void handle_controller_button_up(GameStateManager& gsm, std::uint8_t button);
+
+  void handle_controller_button_down(std::uint8_t button);
+
+  void handle_mouse_movement(int x, int y);
+
+  void handle_controller_axis_motion(std::uint8_t axis, std::int16_t value);
+
+  void select_previous_button();
+
+  void select_next_button();
+
+  void trigger_active_button();
+
   Button new_game_button_{0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, UiColor::Green};
   Button continue_button_{0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, UiColor::Blue};
   Button high_score_button_{0, 0, BUTTON_WIDTH, BUTTON_HEIGHT};
@@ -37,6 +55,7 @@ private:
   std::optional<PlayingState*> game_{};
 
   int active_button_{0};
+  int last_controller_direction_{0};
 };
 
 #endif // SNAKE_MENUSTATE_HXX

+ 19 - 2
game/SplashState.cxx

@@ -6,7 +6,10 @@ using namespace std::chrono_literals;
 
 void SplashState::on_event(GameStateManager& gsm, SDL_Event const& evt)
 {
-  if (evt.type==SDL_KEYUP) {
+  switch (evt.type) {
+  default:
+    break;
+  case SDL_KEYUP:
     switch (evt.key.keysym.scancode) {
     default:
       break;
@@ -18,6 +21,20 @@ void SplashState::on_event(GameStateManager& gsm, SDL_Event const& evt)
       gsm.replace_state(GameStates::MainMenu);
       break;
     }
+    break;
+  case SDL_CONTROLLERBUTTONUP:
+    switch (evt.cbutton.button) {
+    default:
+      break;
+    case SDL_CONTROLLER_BUTTON_A:
+      [[fallthrough]];
+    case SDL_CONTROLLER_BUTTON_B:
+      [[fallthrough]];
+    case SDL_CONTROLLER_BUTTON_START:
+      gsm.replace_state(GameStates::MainMenu);
+      break;
+    }
+    break;
   }
 }
 
@@ -86,4 +103,4 @@ void SplashState::on_enter(GameStateManager& gsm)
   SDL_SetTextureBlendMode(logo_, SDL_BLENDMODE_BLEND);
 
   SDL_SetWindowIcon(gsm.window(), am.get_surface_asset("snake-icon.png"));
-}
+}

+ 13 - 0
main.cxx

@@ -18,6 +18,19 @@ void main_loop(SDLWindow& window, SDLRenderer& renderer)
   for (;;) {
     SDL_Event evt;
     while (SDL_PollEvent(&evt)!=0) {
+      switch (evt.type) {
+      default:
+        break;
+      case SDL_QUIT:
+        return;
+      case SDL_CONTROLLERDEVICEADDED:
+        SDL::instance().add_controller(evt.cdevice.which);
+        break;
+      case SDL_CONTROLLERDEVICEREMOVED:
+        SDL::instance().remove_controller(evt.cdevice.which);
+        break;
+      }
+
       if (evt.type==SDL_QUIT)
         return;