Jelajahi Sumber

:sparkles: added renderer and first GameState prototype

Felix Bytow 1 tahun lalu
induk
melakukan
abf98d50c2
11 mengubah file dengan 238 tambahan dan 8 penghapusan
  1. 4 0
      CMakeLists.txt
  2. 38 0
      SDLRenderer.cxx
  3. 33 0
      SDLRenderer.hxx
  4. 1 1
      SDLWindow.cxx
  5. 1 1
      SDLWindow.hxx
  6. 25 0
      game/GameState.hxx
  7. 48 0
      game/GameStateManager.cxx
  8. 38 0
      game/GameStateManager.hxx
  9. 15 0
      game/LoadingState.cxx
  10. 15 0
      game/LoadingState.hxx
  11. 20 6
      main.cxx

+ 4 - 0
CMakeLists.txt

@@ -30,6 +30,10 @@ add_executable(Snake WIN32
     main.cxx
     SDL.cxx SDL.hxx
     SDLWindow.cxx SDLWindow.hxx
+    SDLRenderer.cxx SDLRenderer.hxx
+    game/GameState.hxx
+    game/GameStateManager.cxx game/GameStateManager.hxx
+    game/LoadingState.cxx game/LoadingState.hxx
 )
 
 target_link_libraries(Snake PRIVATE

+ 38 - 0
SDLRenderer.cxx

@@ -0,0 +1,38 @@
+#include "SDLRenderer.hxx"
+
+SDLRenderer::SDLRenderer(SDLWindow& window)
+{
+  renderer_ = SDL_CreateRenderer(window, 0, SDL_RENDERER_ACCELERATED);
+  if (renderer_==nullptr) {
+    throw SDLError("Failed to create renderer");
+  }
+  SDL_RenderSetVSync(renderer_, SDL_TRUE);
+  SDL_Log("Created renderer successfully.");
+}
+
+SDLRenderer::~SDLRenderer() noexcept
+{
+  destroy();
+}
+
+SDLRenderer::SDLRenderer(SDLRenderer&& src) noexcept
+    :renderer_{src.renderer_}
+{
+  src.renderer_ = nullptr;
+}
+
+SDLRenderer& SDLRenderer::operator=(SDLRenderer&& src) noexcept
+{
+  destroy();
+  std::swap(renderer_, src.renderer_);
+  return *this;
+}
+
+void SDLRenderer::destroy() noexcept
+{
+  if (renderer_!=nullptr) {
+    SDL_DestroyRenderer(renderer_);
+    SDL_Log("Destroyed renderer successfully.");
+    renderer_ = nullptr;
+  }
+}

+ 33 - 0
SDLRenderer.hxx

@@ -0,0 +1,33 @@
+#pragma once
+
+#ifndef SNAKE_SDLRENDERER_HXX
+#define SNAKE_SDLRENDERER_HXX
+
+#include "SDLWindow.hxx"
+
+class SDLRenderer final {
+public:
+  SDLRenderer(SDLWindow& window);
+
+  ~SDLRenderer() noexcept;
+
+  SDLRenderer(SDLRenderer const&) = delete;
+
+  SDLRenderer& operator=(SDLRenderer const&) = delete;
+
+  SDLRenderer(SDLRenderer&& src) noexcept;
+
+  SDLRenderer& operator=(SDLRenderer&& src) noexcept;
+
+  void destroy() noexcept;
+
+  operator SDL_Renderer*() const noexcept // NOLINT(*-explicit-constructor)
+  {
+    return renderer_;
+  }
+
+private:
+  SDL_Renderer* renderer_;
+};
+
+#endif // SNAKE_SDLRENDERER_HXX

+ 1 - 1
SDLWindow.cxx

@@ -12,7 +12,7 @@ SDLWindow::SDLWindow(std::string_view title, int x, int y, int w, int h, std::ui
   SDL_Log("Created window successfully.");
 }
 
-SDLWindow::~SDLWindow()
+SDLWindow::~SDLWindow() noexcept
 {
   destroy();
 }

+ 1 - 1
SDLWindow.hxx

@@ -11,7 +11,7 @@ class SDLWindow final {
 public:
   SDLWindow(std::string_view title, int x, int y, int w, int h, std::uint32_t flags = 0u);
 
-  ~SDLWindow();
+  ~SDLWindow() noexcept;
 
   SDLWindow(SDLWindow const&) = delete;
 

+ 25 - 0
game/GameState.hxx

@@ -0,0 +1,25 @@
+#pragma once
+
+#ifndef SNAKE_GAMESTATE_HXX
+#define SNAKE_GAMESTATE_HXX
+
+#include <chrono>
+
+class GameStateManager;
+
+class SDLRenderer;
+
+class GameState {
+public:
+  virtual ~GameState() noexcept = default;
+
+  virtual void on_enter() { }
+
+  virtual void on_leave() { }
+
+  virtual void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) = 0;
+
+  virtual void render(SDLRenderer& renderer) = 0;
+};
+
+#endif // SNAKE_GAMESTATE_HXX

+ 48 - 0
game/GameStateManager.cxx

@@ -0,0 +1,48 @@
+#include "GameStateManager.hxx"
+
+GameStateManager::GameStateManager()
+    :states_{{GameStates::Loading}}
+{
+  loading_.on_enter();
+}
+
+GameStateManager::~GameStateManager()
+{
+  while (!states_.empty()) {
+    pop_state();
+  }
+}
+
+GameState* GameStateManager::current()
+{
+  if (states_.empty())
+    return nullptr;
+
+  switch (states_.top()) {
+  default:
+    return nullptr; // TODO: handle all game states
+  case GameStates::Loading:
+    return &loading_;
+  }
+}
+
+void GameStateManager::push_state(GameStates const new_state)
+{
+  states_.push(new_state);
+  current()->on_enter();
+}
+
+void GameStateManager::pop_state()
+{
+  auto const state = current();
+  if (state!=nullptr) {
+    state->on_leave();
+    states_.pop();
+  }
+}
+
+void GameStateManager::replace_state(GameStates const new_state)
+{
+  pop_state();
+  push_state(new_state);
+}

+ 38 - 0
game/GameStateManager.hxx

@@ -0,0 +1,38 @@
+#pragma once
+
+#ifndef SNAKE_GAMESTATEMANAGER_HXX
+#define SNAKE_GAMESTATEMANAGER_HXX
+
+#include "LoadingState.hxx"
+
+#include <stack>
+
+enum class GameStates {
+  Loading,
+  Splash,
+  MainMenu,
+  Game,
+  GameOver,
+};
+
+class GameStateManager final {
+public:
+  GameStateManager();
+
+  ~GameStateManager();
+
+  GameState* current();
+
+  void push_state(GameStates new_state);
+
+  void pop_state();
+
+  void replace_state(GameStates new_state);
+
+private:
+  std::stack<GameStates> states_;
+
+  LoadingState loading_;
+};
+
+#endif // SNAKE_GAMESTATEMANAGER_HXX

+ 15 - 0
game/LoadingState.cxx

@@ -0,0 +1,15 @@
+#include "LoadingState.hxx"
+
+#include "../SDLRenderer.hxx"
+
+void LoadingState::update(GameStateManager& gsm, std::chrono::milliseconds const delta_time)
+{
+  (void)gsm;
+  (void)delta_time;
+}
+
+void LoadingState::render(SDLRenderer& renderer) {
+  SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+  SDL_RenderClear(renderer);
+  SDL_RenderPresent(renderer);
+}

+ 15 - 0
game/LoadingState.hxx

@@ -0,0 +1,15 @@
+#pragma once
+
+#ifndef SNAKE_LOADINGSTATE_HXX
+#define SNAKE_LOADINGSTATE_HXX
+
+#include "GameState.hxx"
+
+class LoadingState final : public GameState {
+public:
+  void update(GameStateManager& gsm, std::chrono::milliseconds delta_time) override;
+
+  void render(SDLRenderer& renderer) override;
+};
+
+#endif // SNAKE_LOADINGSTATE_HXX

+ 20 - 6
main.cxx

@@ -1,21 +1,34 @@
 #include "SDL.hxx"
 #include "SDLWindow.hxx"
+#include "SDLRenderer.hxx"
+
+#include "game/GameStateManager.hxx"
 
 #include <cstdlib>
 
-void main_loop(SDLWindow& window)
+void main_loop(SDLRenderer& renderer)
 {
+  using namespace std::chrono;
+
+  GameStateManager gsm;
+
+  auto start = high_resolution_clock::now();
   for (;;) {
     SDL_Event evt;
     while (SDL_PollEvent(&evt)!=0) {
-      if (evt.type==SDL_QUIT) {
+      if (evt.type==SDL_QUIT)
         return;
-      }
     }
 
-    // Game logic, render... (Implement your logic here if needed.)
+    auto const state = gsm.current();
+    if (state==nullptr)
+      return;
+
+    auto const end = high_resolution_clock::now();
+    auto const delta_time = duration_cast<milliseconds>(end-start);
 
-    SDL_Delay(16);
+    state->update(gsm, delta_time);
+    state->render(renderer);
   }
 }
 
@@ -28,8 +41,9 @@ int main(int argc, char** argv) try
       0, 0,
       SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_BORDERLESS
   };
+  SDLRenderer renderer{window};
 
-  main_loop(window);
+  main_loop(renderer);
   return EXIT_SUCCESS;
 } catch (std::exception const& ex) {
   SDL_LogError(SDL_LOG_CATEGORY_ERROR, "%s", ex.what());