diff --git a/Client/assets/sprites/bullet.gif b/Client/assets/sprites/bullet.gif new file mode 100644 index 00000000..52d15395 Binary files /dev/null and b/Client/assets/sprites/bullet.gif differ diff --git a/Client/assets/sprites/player1.gif b/Client/assets/sprites/player1.gif new file mode 100644 index 00000000..c2bf1b16 Binary files /dev/null and b/Client/assets/sprites/player1.gif differ diff --git a/Client/assets/sprites/player2.gif b/Client/assets/sprites/player2.gif new file mode 100644 index 00000000..f0850418 Binary files /dev/null and b/Client/assets/sprites/player2.gif differ diff --git a/Client/assets/sprites/player3.gif b/Client/assets/sprites/player3.gif new file mode 100644 index 00000000..4d388f3d Binary files /dev/null and b/Client/assets/sprites/player3.gif differ diff --git a/Client/assets/sprites/player4.gif b/Client/assets/sprites/player4.gif new file mode 100644 index 00000000..822125e1 Binary files /dev/null and b/Client/assets/sprites/player4.gif differ diff --git a/Client/assets/sprites/player5.gif b/Client/assets/sprites/player5.gif new file mode 100644 index 00000000..737435e3 Binary files /dev/null and b/Client/assets/sprites/player5.gif differ diff --git a/Client/src/app.cpp b/Client/src/app.cpp index f298bd62..2024fe0c 100644 --- a/Client/src/app.cpp +++ b/Client/src/app.cpp @@ -14,6 +14,7 @@ #include "packets/server/spawn.hpp" #include "systems/IO.hpp" #include "systems/Menus.hpp" +#include "systems/animationSystem.hpp" #include "systems/interpolation.hpp" #include "systems/network.hpp" #include "systems/renderer.hpp" @@ -52,13 +53,14 @@ void App::registerAllComponents() void App::registerAllSystems() { + _toolbox.engine.getEcs()->registerSystem(std::make_shared()); _toolbox.engine.getEcs()->registerSystem(std::make_shared()); _toolbox.engine.getEcs()->registerSystem(std::make_shared(_networkService)); + _toolbox.engine.getEcs()->registerSystem( + std::make_shared(_client, _networkService)); _toolbox.engine.getEcs()->registerSystem(std::make_shared(_shouldStop)); _toolbox.engine.getEcs()->registerSystem( std::make_shared(_lobbies, _networkService, _toolbox.engine)); - _toolbox.engine.getEcs()->registerSystem( - std::make_shared(_client, _networkService)); } void App::registerAllCallbacks() diff --git a/Client/src/components/animations.hpp b/Client/src/components/animations.hpp index 5d09a3d9..4e748b07 100644 --- a/Client/src/components/animations.hpp +++ b/Client/src/components/animations.hpp @@ -10,7 +10,8 @@ struct Animation int current_frame; float frame_time; float elapsed_time; - int frame_with; + int frame_width; int frame_height; + bool loop; }; } // namespace components diff --git a/Client/src/gui/assetManager.cpp b/Client/src/gui/assetManager.cpp index 732ae4d1..166de721 100644 --- a/Client/src/gui/assetManager.cpp +++ b/Client/src/gui/assetManager.cpp @@ -4,44 +4,51 @@ #include "logger/Logger.h" -static constexpr std::string_view ENEMY_TEXTURE_PATH = "Client/assets/sprites/enemyAerial.gif"; -static constexpr std::string_view BULLET_TEXTURE_PATH = "Client/assets/sprites/bullets.gif"; +static constexpr std::string_view ENEMY_TEXTURE_PATH = + ASSET_FILEPATH_PREFIX "/sprites/enemyAerial.gif"; +static constexpr std::string_view BULLET_TEXTURE_PATH = ASSET_FILEPATH_PREFIX "/sprites/bullet.gif"; // static constexpr std::string_view UI_MENU_BG_PATH = "../../Client/assets/ui/MenuBackground.png"; // static constexpr std::string_view UI_PAUSE_BTN_PATH = "../../Client/assets/ui/PauseButton.png"; -void AssetManager::init() +void gui::AssetManager::init() { + _background = std::make_unique(BACKGROUND_TEXTURE_FILEPATH.data()); size_t entityCount = static_cast(entity::Type::kBullet) + 1; _textures.reserve(entityCount); - _textures.emplace_back(gui::PLAYER_TEXTURE_FILEPATH.data(), 0.2f); - - _textures.emplace_back(ENEMY_TEXTURE_PATH.data(), 0.15f); - - _textures.emplace_back(BULLET_TEXTURE_PATH.data(), 0.1f); + _textures.emplace_back(gui::PLAYER_TEXTURE_FILEPATH.data(), 1.0f); + _textures.emplace_back(ENEMY_TEXTURE_PATH.data(), 1.0f); + _textures.emplace_back(BULLET_TEXTURE_PATH.data(), 1.0f); + LOG_INFO("AssetManager: Loaded {} game textures and {} UI textures", + _textures.size(), + _uiTextures.size()); // _uiTextures[gui::UIAsset::kMenuBackground] = // std::make_unique(UI_MENU_BG_PATH.data(), 1.0f); // _uiTextures[gui::UIAsset::kPauseButton] = // std::make_unique(UI_PAUSE_BTN_PATH.data(), 0.5f); - - LOG_INFO("AssetManager: Loaded {} game textures and {} UI textures", - _textures.size(), - _uiTextures.size()); } -const gui::Texture& AssetManager::getTexture(entity::Type id) const +const gui::Texture& gui::AssetManager::getTexture(entity::Type id) const { size_t index = static_cast(id); if (index >= _textures.size()) { LOG_ERR("AssetManager: Attempting to access unknown game texture index {}", index); - return _textures[0]; // Return player texture as fallback + return _textures[0]; } return _textures[index]; } -const gui::Texture& AssetManager::getUITexture(gui::UIAsset id) const +const Texture2D& gui::AssetManager::getBackground() const +{ + if (!_background) { + return _textures.back().getTexture(); + } + return _background->getTexture(); +} + +const gui::Texture& gui::AssetManager::getUITexture(gui::UIAsset id) const { auto it = _uiTextures.find(id); if (it == _uiTextures.end()) { diff --git a/Client/src/gui/assetManager.hpp b/Client/src/gui/assetManager.hpp index bf08b179..7e793394 100644 --- a/Client/src/gui/assetManager.hpp +++ b/Client/src/gui/assetManager.hpp @@ -18,7 +18,41 @@ enum class UIAsset kScoreFont }; -} // namespace gui +struct SpriteConfig +{ + int frameCount; + int frameWidth; + int frameHeight; + float frameTime; +}; + +struct AnimationConfig +{ + int frameCount; + float frameTime; + int frameWidth; + int frameHeight; + bool loop; +}; + +static const std::unordered_map entityTypeToAnimation = { + {entity::Type::kPlayer, + {5, + 0.15f, + 33, + 15, + true}}, // {Number of frames, Time per frame, Frame width, Frame height, Loop} + {entity::Type::kEnemy, {8, 0.15f, 32, 32, true}}, + {entity::Type::kBullet, {3, 0.15f, 18, 14, false}}, +}; + +inline AnimationConfig typeToAnimation(const entity::Type& type) +{ + if (!entityTypeToAnimation.contains(type)) { + return {1, 0.0f, 0, 0, false}; + } + return entityTypeToAnimation.at(type); +} class AssetManager { @@ -30,9 +64,17 @@ class AssetManager [[nodiscard]] const gui::Texture& getTexture(entity::Type id) const; + [[nodiscard]] const Texture2D& getBackground() const; + [[nodiscard]] const gui::Texture& getUITexture(gui::UIAsset id) const; + [[nodiscard]] const gui::SpriteConfig& getSpriteConfig(entity::Type id) const; + private: + std::unique_ptr _background; std::vector _textures; + std::map _spriteConfigs; std::map> _uiTextures; }; + +} // namespace gui diff --git a/Client/src/gui/texture.hpp b/Client/src/gui/texture.hpp index ab33ff58..277c509e 100644 --- a/Client/src/gui/texture.hpp +++ b/Client/src/gui/texture.hpp @@ -7,7 +7,11 @@ namespace gui { -static constexpr std::string_view PLAYER_TEXTURE_FILEPATH = "Client/assets/sprites/players.gif"; +#define ASSET_FILEPATH_PREFIX "Client/assets" +static constexpr std::string_view PLAYER_TEXTURE_FILEPATH = + ASSET_FILEPATH_PREFIX "/sprites/player1.gif"; +static constexpr std::string_view BACKGROUND_TEXTURE_FILEPATH = + ASSET_FILEPATH_PREFIX "/background.png"; class Texture { diff --git a/Client/src/handlers/handle_spawn.cpp b/Client/src/handlers/handle_spawn.cpp index 9e0f104a..51f90bf0 100644 --- a/Client/src/handlers/handle_spawn.cpp +++ b/Client/src/handlers/handle_spawn.cpp @@ -1,6 +1,8 @@ #include "app.hpp" +#include "components/animations.hpp" #include "components/sprite.hpp" #include "components/target_pos.hpp" +#include "gui/assetManager.hpp" #include "handlers.hpp" #include "rteng.hpp" @@ -13,6 +15,19 @@ static void addGraphicalComponents(rtecs::types::EntityID id, toolbox.engine.getEcs()->addEntityComponents( id, {type.value().get().type}); } + + const gui::AnimationConfig& config = gui::typeToAnimation(type.value().get().type); + if (config.frameCount > 1) { + toolbox.engine.getEcs()->addEntityComponents(id, + {config.frameCount, + 0, + config.frameTime, + 0.0f, + config.frameWidth, + config.frameHeight, + config.loop}); + } + rtecs::types::OptionalRef pos = toolbox.engine.getEcs()->group().getEntity(id); if (pos) { @@ -42,5 +57,4 @@ void handleSpawn(Spawn packet, addGraphicalComponents(real, toolbox); LOG_TRACE_R2("Finished recomposing entity {}", real); } - } // namespace packet::handler diff --git a/Client/src/systems/Menus.cpp b/Client/src/systems/Menus.cpp index 452e80f3..34c5eee8 100644 --- a/Client/src/systems/Menus.cpp +++ b/Client/src/systems/Menus.cpp @@ -49,8 +49,7 @@ MenuRenderer::MenuRenderer(client::Lobby& lobby, : ASystem("MenuRenderer"), _service(service), _lobby(lobby), - _engine(engine), - _texture(BACKGROUND_TEXTURE_FILEPATH.data()) + _engine(engine) { _buttons.reserve(5); @@ -118,15 +117,13 @@ void MenuRenderer::renderPreGameMenu() void MenuRenderer::apply(rtecs::ECS&) { if (_engine.getGameState() == game::state::GameRunning || WindowShouldClose()) { + if (!WindowShouldClose()) { + EndDrawing(); + } return; } const size_t menuState = _engine.getMenuState(); - BeginDrawing(); - - ClearBackground(GREEN); - DrawTexture(_texture.getTexture(), 0, 0, WHITE); - if (menuState == menu::state::MenuLobby) { renderPreGameMenu(); } diff --git a/Client/src/systems/Menus.hpp b/Client/src/systems/Menus.hpp index 0d6cbb38..e6030c6b 100644 --- a/Client/src/systems/Menus.hpp +++ b/Client/src/systems/Menus.hpp @@ -14,13 +14,15 @@ struct Lobby; #define SHARE_FONT(s) std::make_shared(LoadFontEx(s, FONT_SIZE, nullptr, 250)); -static constexpr std::string_view START_BUTTON_FILEPATH = "Client/assets/buttons/start.png"; -static constexpr std::string_view PLAY_BUTTON_FILEPATH = "Client/assets/buttons/play.png"; -static constexpr std::string_view CREDITS_BUTTON_FILEPATH = "Client/assets/buttons/credits.png"; -static constexpr std::string_view CREATE_BUTTON_FILEPATH = "Client/assets/buttons/create.png"; -static constexpr std::string_view BACKGROUND_TEXTURE_FILEPATH = "Client/assets/background.png"; -static constexpr std::string_view BASIC_FONT = "Client/assets/basic.ttf"; -static constexpr std::string_view DYSLEXIC_FONT = "Client/assets/dyslexic.ttf"; +static constexpr std::string_view START_BUTTON_FILEPATH = + ASSET_FILEPATH_PREFIX "/buttons/start.png"; +static constexpr std::string_view PLAY_BUTTON_FILEPATH = ASSET_FILEPATH_PREFIX "/buttons/play.png"; +static constexpr std::string_view CREDITS_BUTTON_FILEPATH = + ASSET_FILEPATH_PREFIX "/buttons/credits.png"; +static constexpr std::string_view CREATE_BUTTON_FILEPATH = + ASSET_FILEPATH_PREFIX "/buttons/create.png"; +static constexpr std::string_view BASIC_FONT = ASSET_FILEPATH_PREFIX "/basic.ttf"; +static constexpr std::string_view DYSLEXIC_FONT = ASSET_FILEPATH_PREFIX "/dyslexic.ttf"; static constexpr Color SEMI_WHITE = {255, 255, 255, 255 / 2}; @@ -119,7 +121,6 @@ class MenuRenderer : public rtecs::systems::ASystem std::shared_ptr _basicFont; std::shared_ptr _dyslexicFont; rteng::GameEngine& _engine; - gui::Texture _texture; std::vector _buttons; }; diff --git a/Client/src/systems/animationSystem.cpp b/Client/src/systems/animationSystem.cpp new file mode 100644 index 00000000..86fe7908 --- /dev/null +++ b/Client/src/systems/animationSystem.cpp @@ -0,0 +1,30 @@ +#include "animationSystem.hpp" + +#include + +#include "components/animations.hpp" + +namespace systems { + +AnimationSystem::AnimationSystem() + : rtecs::systems::ASystem("AnimationSystem") +{ +} + +void AnimationSystem::apply(rtecs::ECS& ecs) +{ + float dt = GetFrameTime(); + + ecs.group().apply( + [dt](const rtecs::types::EntityID&, components::Animation& anim) { + anim.elapsed_time += dt; + if (anim.elapsed_time >= anim.frame_time) { + anim.elapsed_time = 0; + + if (anim.loop || anim.current_frame < anim.frame_count - 1) { + anim.current_frame = (anim.current_frame + 1) % anim.frame_count; + } + } + }); +} +} // namespace systems diff --git a/Client/src/systems/animationSystem.hpp b/Client/src/systems/animationSystem.hpp new file mode 100644 index 00000000..569b074d --- /dev/null +++ b/Client/src/systems/animationSystem.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "rtecs/ECS.hpp" +#include "rtecs/systems/ASystem.hpp" + +namespace systems { + +class AnimationSystem final : public rtecs::systems::ASystem +{ +public: + AnimationSystem(); + ~AnimationSystem() override = default; + + void apply(rtecs::ECS& ecs) override; +}; + +} // namespace systems diff --git a/Client/src/systems/renderer.cpp b/Client/src/systems/renderer.cpp index 79380c49..cd789b42 100644 --- a/Client/src/systems/renderer.cpp +++ b/Client/src/systems/renderer.cpp @@ -1,5 +1,7 @@ #include "renderer.hpp" +#include "components/animations.hpp" +#include "components/hitbox.hpp" #include "components/position.hpp" #include "components/sprite.hpp" #include "enums/entity_types.hpp" @@ -27,15 +29,47 @@ void Renderer::apply(rtecs::ECS& ecs) return; } BeginDrawing(); - ClearBackground({0, 255, 0, 255}); + ClearBackground(GREEN); + DrawTexture(_assetManager.getBackground(), 0, 0, WHITE); ecs.group().apply( - [this](const rtecs::types::EntityID&, - const components::Sprite& sprite, - const components::Position& pos) { + [&](const rtecs::types::EntityID& id, + const components::Sprite& sprite, + const components::Position& pos) { const gui::Texture& tex = _assetManager.getTexture(sprite.type); - DrawTextureEx(tex.getTexture(), {pos.x, pos.y}, 0.0f, tex.getScale(), WHITE); + Texture2D rawTex = tex.getTexture(); + + Rectangle sourceRec = {0.0f, 0.0f, (float)rawTex.width, (float)rawTex.height}; + + const auto animOpt = + ecs.group().getEntity(id); + if (animOpt.has_value()) { + const components::Animation& anim = animOpt.value().get(); + sourceRec.x = anim.current_frame * anim.frame_width; + sourceRec.y = 0.0f; + sourceRec.width = (float)anim.frame_width; + sourceRec.height = (float)anim.frame_height; + } + + float scaleX = (float)tex.getScale(); + float scaleY = (float)tex.getScale(); + + const auto hitboxOpt = + ecs.group().getEntity(id); + + if (hitboxOpt.has_value()) { + const auto& hb = hitboxOpt.value().get(); + if (sourceRec.width != 0) { + scaleX = hb.width / sourceRec.width; + } + if (sourceRec.height != 0) { + scaleY = hb.height / sourceRec.height; + } + } + + Rectangle destRec = {pos.x, pos.y, sourceRec.width * scaleX, sourceRec.height * scaleY}; + + DrawTexturePro(rawTex, sourceRec, destRec, {0, 0}, 0.0f, WHITE); }); - EndDrawing(); } } // namespace systems diff --git a/Client/src/systems/renderer.hpp b/Client/src/systems/renderer.hpp index 3f0c6397..f86ba36f 100644 --- a/Client/src/systems/renderer.hpp +++ b/Client/src/systems/renderer.hpp @@ -9,6 +9,7 @@ #include "gui/assetManager.hpp" #include "gui/texture.hpp" #include "rtecs/systems/ASystem.hpp" +#include "rteng.hpp" namespace systems { @@ -21,7 +22,7 @@ class Renderer final : public rtecs::systems::ASystem private: bool& _closing; - AssetManager _assetManager; + gui::AssetManager _assetManager; }; } // namespace systems