Преглед изворни кода

:sparkles: added asset manager

Felix Bytow пре 1 година
родитељ
комит
7a1e3479c7

+ 2 - 0
CMakeLists.txt

@@ -34,6 +34,8 @@ add_executable(Snake WIN32
     game/GameState.hxx
     game/GameStateManager.cxx game/GameStateManager.hxx
     game/LoadingState.cxx game/LoadingState.hxx
+    game/AssetManager.cxx
+    game/AssetManager.hxx
 )
 
 target_link_libraries(Snake PRIVATE

+ 30 - 7
SDL.cxx

@@ -1,13 +1,17 @@
 #include "SDL.hxx"
 
 #include <cassert>
-#include <sstream>
+#include <format>
 
-SDLError::SDLError(std::string_view const context, std::source_location location)
-    :std::runtime_error{(std::ostringstream{}
-    << context << " at "
-    << location.file_name() << ":" << location.line() << ":" << location.column()
-    << " - " << SDL_GetError()).str()}
+#include <SDL_image.h>
+#include <SDL_ttf.h>
+
+SDLError::SDLError(std::string_view const message, std::source_location location)
+    :std::runtime_error{std::format(
+    "{}:{}:{} - {} ({})",
+    location.file_name(), location.line(), location.column(),
+    message,
+    SDL_GetError())}
 {
 }
 
@@ -17,15 +21,34 @@ SDL::SDL(std::uint32_t const flags)
 {
   assert(instance_==nullptr);
   if (SDL_Init(flags)!=0) {
-    throw SDLError("Failed to initialize SDL");
+    throw SDLError{"Failed to initialize SDL."};
   }
   SDL_Log("Initialized SDL successfully.");
+
+  auto const img_flags = IMG_INIT_PNG | IMG_INIT_JPG;
+  if (IMG_Init(img_flags)!=img_flags) {
+    throw SDLError{"Failed to initialize SDL_image."};
+  }
+  SDL_Log("Initialized SDL_image successfully.");
+
+  if (TTF_Init()!=0) {
+    throw SDLError{"Failed to initialize SDL_ttf."};
+  }
+  SDL_Log("Initialized SDL_ttf successfully.");
+
   instance_ = this;
 }
 
 SDL::~SDL() noexcept
 {
   assert(instance_!=nullptr);
+
+  TTF_Quit();
+  SDL_Log("Shut down SDL_ttf successfully.");
+
+  IMG_Quit();
+  SDL_Log("Shut down SDL_image successfully.");
+
   SDL_Quit();
   SDL_Log("Shut down SDL successfully.");
   instance_ = nullptr;

+ 1 - 1
SDL.hxx

@@ -21,7 +21,7 @@
 class SDLError final : public std::runtime_error {
 public:
   explicit SDLError(
-      std::string_view context,
+      std::string_view message,
       std::source_location location = std::source_location::current()
   );
 };

+ 1 - 1
SDLRenderer.cxx

@@ -4,7 +4,7 @@ SDLRenderer::SDLRenderer(SDLWindow& window)
 {
   renderer_ = SDL_CreateRenderer(window, 0, SDL_RENDERER_ACCELERATED);
   if (renderer_==nullptr) {
-    throw SDLError("Failed to create renderer");
+    throw SDLError("Failed to create renderer.");
   }
   SDL_RenderSetVSync(renderer_, SDL_TRUE);
   SDL_Log("Created renderer successfully.");

+ 1 - 1
SDLWindow.cxx

@@ -7,7 +7,7 @@ SDLWindow::SDLWindow(std::string_view title, int x, int y, int w, int h, std::ui
   SDL::require(SDL_INIT_VIDEO);
   window_ = SDL_CreateWindow(title.data(), x, y, w, h, flags);
   if (window_==nullptr) {
-    throw SDLError("Failed to create window");
+    throw SDLError("Failed to create window.");
   }
   SDL_Log("Created window successfully.");
 }

BIN
assets/blue_button_down.png


BIN
assets/blue_button_up.png


BIN
assets/kenvector_future.ttf


BIN
assets/kenvector_future_thin.ttf



+ 97 - 0
game/AssetManager.cxx

@@ -0,0 +1,97 @@
+#include "AssetManager.hxx"
+
+#include <cassert>
+#include <filesystem>
+#include <format>
+
+#include <SDL_image.h>
+
+AssetManager* AssetManager::instance_ = nullptr;
+
+AssetManager::AssetManager(SDLRenderer& renderer)
+    :renderer_{renderer}
+{
+  namespace fs = std::filesystem;
+
+  assert(instance_==nullptr);
+
+  auto current_path = fs::current_path();
+  auto const asset_directory =
+      fs::exists(current_path / "assets") ? current_path / "assets" :
+      fs::exists(current_path.parent_path() / "assets") ? current_path.parent_path() / "assets" :
+      fs::path{};
+  if (asset_directory.empty()) {
+    throw std::runtime_error("Assets directory not found.");
+  }
+
+  total_assets_ = std::distance(fs::directory_iterator(asset_directory),
+                                fs::directory_iterator());
+
+  loading_thread_ = std::thread{&AssetManager::load_assets, this, asset_directory};
+  instance_ = this;
+}
+
+AssetManager::~AssetManager()
+{
+  assert(instance_!=nullptr);
+  if (loading_thread_.joinable()) {
+    loading_thread_.join();
+  }
+  for (auto& kv: texture_assets_) {
+    SDL_DestroyTexture(kv.second);
+    SDL_Log("Unloaded texture %s successfully.", kv.first.c_str());
+  }
+  for (auto& kv: font_assets_) {
+    TTF_CloseFont(kv.second);
+    SDL_Log("Unloaded font %s successfully.", kv.first.c_str());
+  }
+  instance_ = nullptr;
+}
+
+void AssetManager::load_assets(std::filesystem::path const& asset_directory)
+{
+  for (auto const& entry: std::filesystem::directory_iterator(asset_directory)) {
+    auto const path = entry.path().string();
+    auto const ext = entry.path().extension();
+    if (ext==".png" || ext==".jpg") {
+      SDL_Surface* temp_surface = IMG_Load(path.c_str());
+      auto const texture = SDL_CreateTextureFromSurface(renderer_, temp_surface);
+      SDL_FreeSurface(temp_surface);
+      if (texture == nullptr) {
+        throw SDLError{std::format("Failed to load texture {}.", path)};
+      }
+      texture_assets_[path] = texture;
+      SDL_Log("Loaded texture %s successfully.", path.c_str());
+    }
+    else if (ext==".ttf") {
+      auto const font = TTF_OpenFont(path.c_str(), 16);
+      if (font == nullptr) {
+        throw SDLError{std::format("Failed to load font {}.", path)};
+      }
+      font_assets_[path] = font;
+      SDL_Log("Loaded font %s successfully.", path.c_str());
+    }
+    ++assets_loaded_;
+  }
+}
+
+float AssetManager::get_progress() const
+{
+  return static_cast<float>(assets_loaded_)/static_cast<float>(total_assets_);
+}
+
+SDL_Texture* AssetManager::get_texture_asset(std::string const& filepath)
+{
+  return texture_assets_[filepath];
+}
+
+TTF_Font* AssetManager::get_font_asset(std::string const& filepath)
+{
+  return font_assets_[filepath];
+}
+
+AssetManager& AssetManager::instance() noexcept
+{
+  assert(instance_!=nullptr);
+  return *instance_;
+}

+ 48 - 0
game/AssetManager.hxx

@@ -0,0 +1,48 @@
+#pragma once
+
+#ifndef SNAKE_ASSETMANAGER_HXX
+#define SNAKE_ASSETMANAGER_HXX
+
+#include "../SDLRenderer.hxx"
+#include <SDL_ttf.h>
+
+#include <atomic>
+#include <filesystem>
+#include <thread>
+#include <unordered_map>
+
+class AssetManager final {
+
+public:
+  explicit AssetManager(SDLRenderer& renderer);
+
+  AssetManager(AssetManager const&) = delete;
+
+  AssetManager& operator=(AssetManager const&) = delete;
+
+  ~AssetManager();
+
+  static AssetManager& instance() noexcept;
+
+  float get_progress() const;
+
+  SDL_Texture* get_texture_asset(std::string const& filepath);
+
+  TTF_Font* get_font_asset(std::string const& filepath);
+
+private:
+  void load_assets(std::filesystem::path const& asset_directory);
+
+  static AssetManager* instance_;
+
+  std::atomic<size_t> assets_loaded_{0u};
+  std::atomic<size_t> total_assets_{0u};
+  std::thread loading_thread_;
+
+  SDLRenderer& renderer_;
+
+  std::unordered_map<std::string, SDL_Texture*> texture_assets_;
+  std::unordered_map<std::string, TTF_Font*> font_assets_;
+};
+
+#endif // SNAKE_ASSETMANAGER_HXX

+ 5 - 0
game/LoadingState.cxx

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

+ 14 - 0
licenses/Kenney UI Pack License.txt

@@ -0,0 +1,14 @@
+
+###############################################################################
+
+	UI pack by Kenney Vleugels (www.kenney.nl)
+
+			------------------------------
+
+			        License (CC0)
+	       http://creativecommons.org/publicdomain/zero/1.0/
+
+	You may use these graphics in personal and commercial projects.
+	Credit (Kenney or www.kenney.nl) would be nice but is not mandatory.
+
+###############################################################################

+ 3 - 1
main.cxx

@@ -2,6 +2,7 @@
 #include "SDLWindow.hxx"
 #include "SDLRenderer.hxx"
 
+#include "game/AssetManager.hxx"
 #include "game/GameStateManager.hxx"
 
 #include <cstdlib>
@@ -10,7 +11,8 @@ void main_loop(SDLRenderer& renderer)
 {
   using namespace std::chrono;
 
-  GameStateManager gsm;
+  AssetManager am{renderer};
+  GameStateManager gsm{};
 
   auto start = high_resolution_clock::now();
   for (;;) {