Ver Fonte

:sparkles::fire: added the credits screen and finally removed the dummy state

Felix Bytow há 1 ano atrás
pai
commit
df4be7c540

+ 1 - 1
CMakeLists.txt

@@ -44,7 +44,7 @@ add_executable(Snake WIN32 MACOSX_BUNDLE
     game/PlayingState.cxx game/PlayingState.hxx
     game/GameOverState.cxx game/GameOverState.hxx
     game/HighScoreState.cxx game/HighScoreState.hxx
-    game/DummyState.cxx game/DummyState.hxx
+    game/CreditsState.cxx game/CreditsState.hxx
     game/AssetManager.cxx game/AssetManager.hxx
     game/ui/Button.cxx game/ui/Button.hxx
     game/ui/LineInput.cxx game/ui/LineInput.hxx

BIN
assets/Boost-logo.png


BIN
assets/SDL_logo.png


BIN
assets/jetbrains-ai-logo.png


BIN
assets/kenney-logo.png


+ 138 - 0
game/CreditsState.cxx

@@ -0,0 +1,138 @@
+#include "CreditsState.hxx"
+
+#include "GameStateManager.hxx"
+
+#include <type_traits>
+
+CreditsState::CreditsState()
+    :font_{"kenney_pixel.ttf"}, jetbrains_ai_logo_{"jetbrains-ai-logo.png"}, kenney_logo_{"kenney-logo.png"},
+     sdl_logo_{"SDL_logo.png"}, boost_logo_{"Boost-logo.png"}
+{
+}
+
+void CreditsState::on_enter(GameStateManager& gsm)
+{
+  TTF_Font* const font = font_;
+
+  scroll_y_ = 0.0;
+  done_ = false;
+  scroll_items_ = {
+      "Copyright © 2024, Felix Bytow <drako@drako.guru>",
+      External{jetbrains_ai_logo_, "with some help from JetBrains AI Assistant"},
+      External{kenney_logo_, "Font & UI Pack from kenney.nl"},
+      sdl_logo_,
+      boost_logo_,
+  };
+
+  int summed_size = -50;
+  for (auto const& item: scroll_items_) {
+    summed_size += std::visit([font]<typename T>(T const& it) {
+      int w, h = 0;
+      if constexpr (std::is_same_v<T, char const*>) {
+        TTF_SizeUTF8(font, it, &w, &h);
+      }
+      else if constexpr (std::is_same_v<T, External>) {
+        int logo_h, text_h;
+        TTF_SizeUTF8(font, it.text_, &w, &text_h);
+        SDL_QueryTexture(it.texture_, nullptr, nullptr, &w, &logo_h);
+        h = logo_h+10+text_h;
+      }
+      else if constexpr (std::is_same_v<T, SDL_Texture*>) {
+        SDL_QueryTexture(it, nullptr, nullptr, &w, &h);
+      }
+      return h;
+    }, item);
+    summed_size += 50;
+  }
+  scroll_size_ = static_cast<double>(summed_size);
+}
+
+void CreditsState::on_event(GameStateManager& gsm, SDL_Event const& event)
+{
+  if (event.type==SDL_KEYUP) {
+    switch (event.key.keysym.scancode) {
+    default:
+      break;
+    case SDL_SCANCODE_ESCAPE:
+      [[fallthrough]];
+    case SDL_SCANCODE_RETURN:
+      [[fallthrough]];
+    case SDL_SCANCODE_SPACE:
+      gsm.pop_state();
+      break;
+    }
+  }
+}
+
+void CreditsState::update(GameStateManager& gsm, std::chrono::milliseconds const delta_time)
+{
+  if (done_) {
+    gsm.pop_state();
+  }
+
+  scroll_y_ += 0.05*static_cast<double>(delta_time.count());
+}
+
+void CreditsState::render(SDLRenderer& renderer)
+{
+  TTF_Font* const font = font_;
+
+  SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+  SDL_RenderClear(renderer);
+
+  int window_width = 0, window_height = 0;
+  SDL_GetRendererOutputSize(renderer, &window_width, &window_height);
+
+  int y = window_height-static_cast<int>(scroll_y_);
+  if (y<=-static_cast<int>(scroll_size_)) {
+    // everything is now outside the screen at the top
+    done_ = true;
+  }
+
+  for (auto const& item: scroll_items_) {
+    std::visit([&renderer, &y, window_width, font]<typename T>(T const& it) {
+      int h = 0;
+      if constexpr (std::is_same_v<T, char const*>) {
+        auto const surface = TTF_RenderUTF8_Solid(font, it, {255, 255, 255, SDL_ALPHA_OPAQUE});
+        auto const texture = SDL_CreateTextureFromSurface(renderer, surface);
+        SDL_FreeSurface(surface);
+
+        int w;
+        SDL_QueryTexture(texture, nullptr, nullptr, &w, &h);
+        SDL_Rect const rect{.x = (window_width-w)/2, .y = y, .w = w, .h = h};
+        SDL_RenderCopy(renderer, texture, nullptr, &rect);
+
+        SDL_DestroyTexture(texture);
+      }
+      else if constexpr (std::is_same_v<T, External>) {
+        int w, logo_h, text_h;
+
+        SDL_QueryTexture(it.texture_, nullptr, nullptr, &w, &logo_h);
+        SDL_Rect const logo_rect{.x = (window_width-w)/2, .y = y, .w = w, .h = logo_h};
+        SDL_RenderCopy(renderer, it.texture_, nullptr, &logo_rect);
+
+        auto const surface = TTF_RenderUTF8_Solid(font, it.text_, {255, 255, 255, SDL_ALPHA_OPAQUE});
+        auto const texture = SDL_CreateTextureFromSurface(renderer, surface);
+        SDL_FreeSurface(surface);
+
+        SDL_QueryTexture(texture, nullptr, nullptr, &w, &text_h);
+        SDL_Rect const text_rect{.x = (window_width-w)/2, .y = y+logo_h+10, .w = w, .h = text_h};
+        SDL_RenderCopy(renderer, texture, nullptr, &text_rect);
+
+        SDL_DestroyTexture(texture);
+
+        h = logo_h+10+text_h;
+      }
+      else if constexpr (std::is_same_v<T, SDL_Texture*>) {
+        int w;
+        SDL_QueryTexture(it, nullptr, nullptr, &w, &h);
+        SDL_Rect const rect{.x = (window_width-w)/2, .y = y, .w = w, .h = h};
+        SDL_RenderCopy(renderer, it, nullptr, &rect);
+      }
+
+      y += h+50;
+    }, item);
+  }
+
+  SDL_RenderPresent(renderer);
+}

+ 46 - 0
game/CreditsState.hxx

@@ -0,0 +1,46 @@
+#pragma once
+
+#ifndef SNAKE_CREDITSSTATE_HXX
+#define SNAKE_CREDITSSTATE_HXX
+
+#include "GameState.hxx"
+
+#include "AssetManager.hxx"
+
+#include <variant>
+#include <vector>
+
+struct External final {
+  SDL_Texture* texture_;
+  char const* text_;
+};
+
+class CreditsState final : public GameState {
+public:
+  CreditsState();
+
+  void on_enter(GameStateManager& gsm) override;
+
+  void on_event(GameStateManager& gsm, SDL_Event const& event) override;
+
+  void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) override;
+
+  void render(SDLRenderer& renderer) override;
+
+private:
+  Asset<TTF_Font*> font_;
+
+  Asset<SDL_Texture*> jetbrains_ai_logo_;
+  Asset<SDL_Texture*> kenney_logo_;
+  Asset<SDL_Texture*> sdl_logo_;
+  Asset<SDL_Texture*> boost_logo_;
+
+  std::vector<std::variant<char const*, External, SDL_Texture*>> scroll_items_{};
+
+  double scroll_y_{0.0};
+  double scroll_size_{0.0};
+  // this is a hack because we detect being done in render() and use that info in update()
+  bool done_{false};
+};
+
+#endif // SNAKE_CREDITSSTATE_HXX

+ 0 - 44
game/DummyState.cxx

@@ -1,44 +0,0 @@
-#include "DummyState.hxx"
-#include "GameStateManager.hxx"
-
-#include "../SDLRenderer.hxx"
-
-void DummyState::on_enter(GameStateManager& gsm)
-{
-  name_input_.set_value("");
-  name_input_.set_focus(true);
-
-  pause_button_.set_on_click([&gsm] {
-    gsm.push_state(GameStates::MainMenu);
-  });
-}
-
-void DummyState::on_leave()
-{
-  SDL_StopTextInput();
-}
-
-void DummyState::on_event(GameStateManager& gsm, SDL_Event const& evt)
-{
-  name_input_.on_event(evt);
-
-  if (evt.type==SDL_KEYUP && evt.key.keysym.scancode==SDL_SCANCODE_ESCAPE)
-    gsm.push_state(GameStates::MainMenu);
-}
-
-void DummyState::update(GameStateManager& gsm, std::chrono::milliseconds delta_time)
-{
-  pause_button_.update();
-  name_input_.update(delta_time);
-}
-
-void DummyState::render(SDLRenderer& renderer)
-{
-  SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
-  SDL_RenderClear(renderer);
-
-  pause_button_.render(renderer);
-  name_input_.render(renderer);
-
-  SDL_RenderPresent(renderer);
-}

+ 0 - 27
game/DummyState.hxx

@@ -1,27 +0,0 @@
-#pragma once
-
-#ifndef SNAKE_DUMMYSTATE_HXX
-#define SNAKE_DUMMYSTATE_HXX
-
-#include "GameState.hxx"
-#include "ui/Button.hxx"
-#include "ui/LineInput.hxx"
-
-class DummyState final : public GameState {
-public:
-  void on_enter(GameStateManager& gsm) override;
-
-  void on_leave() override;
-
-  void on_event(GameStateManager& gsm, SDL_Event const& evt) override;
-
-  void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) override;
-
-  void render(SDLRenderer& renderer) override;
-
-private:
-  Button pause_button_{"Pause", 15, 15, 800, 80};
-  LineInput name_input_{15, 200, 800, 80};
-};
-
-#endif // SNAKE_DUMMYSTATE_HXX

+ 7 - 1
game/GameStateManager.cxx

@@ -1,5 +1,7 @@
 #include "GameStateManager.hxx"
 
+#include <cassert>
+
 GameStateManager::GameStateManager()
     :states_{{GameStates::Loading}}
 {
@@ -17,7 +19,9 @@ GameState* GameStateManager::enum_to_state(GameStates const state)
 {
   switch (state) {
   default:
-    return &dummy_; // TODO: handle all game states
+    // this should no longer be reachable as all states are handled
+    assert(false);
+    break;
   case GameStates::Loading:
     return &loading_;
   case GameStates::Splash:
@@ -30,6 +34,8 @@ GameState* GameStateManager::enum_to_state(GameStates const state)
     return &game_over_;
   case GameStates::HighScores:
     return &high_score_;
+  case GameStates::Credits:
+    return &credits_;
   }
 }
 

+ 2 - 2
game/GameStateManager.hxx

@@ -9,7 +9,7 @@
 #include "PlayingState.hxx"
 #include "GameOverState.hxx"
 #include "HighScoreState.hxx"
-#include "DummyState.hxx"
+#include "CreditsState.hxx"
 
 #include <stack>
 
@@ -52,7 +52,7 @@ private:
   PlayingState game_;
   GameOverState game_over_;
   HighScoreState high_score_;
-  DummyState dummy_;
+  CreditsState credits_;
 };
 
 #endif // SNAKE_GAMESTATEMANAGER_HXX