diff --git a/CMakeLists.txt b/CMakeLists.txt index eaae4c9..e2bb869 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,7 +129,14 @@ set(SOURCES src/game/physics/body_adapter.hpp src/sprites/masked_sprite.cpp src/sprites/masked_sprite.hpp - src/sprites/configs/masked_sprite_config.hpp src/sprites/masked_sprite_hole.hpp) + src/sprites/configs/masked_sprite_config.hpp + src/sprites/masked_sprite_hole.hpp + src/game/time/countdown.cpp + src/game/time/countdown.hpp + src/game/layer/global_layer.cpp + src/game/layer/global_layer.hpp + src/typography/font_manager.cpp + src/typography/font_manager.hpp) set(PHYSICS_00_SOURCES src/prototypes/physics_00.cpp) diff --git a/assets/fonts/pixel.ttf b/assets/fonts/pixel.ttf new file mode 100644 index 0000000..618e5bb Binary files /dev/null and b/assets/fonts/pixel.ttf differ diff --git a/src/collectables.hpp b/src/collectables.hpp index 4db4939..d742d90 100644 --- a/src/collectables.hpp +++ b/src/collectables.hpp @@ -6,7 +6,12 @@ #include "game/collectables/collectable_config.hpp" std::map const all_collectables = { - {"box", CollectableConfig("rosebush")} + {"rose", CollectableConfig("rose", 1)}, + {"rosebush", CollectableConfig("rosebush", 3)}, + {"stone", CollectableConfig("stone", 5)}, + {"bike", CollectableConfig("bike", 10)}, + {"small-tree", CollectableConfig("small-tree", 20)}, + {"tram", CollectableConfig("tram", 50)} }; #endif //HOLESOME_COLLECTABLES_HPP diff --git a/src/config.h b/src/config.h index fc6abee..f717123 100644 --- a/src/config.h +++ b/src/config.h @@ -34,6 +34,7 @@ #define MASKED_HOLE_BORDER_TRANSITION_SIZE 0.2f #define MASKED_HOLE_DARKNESS_LIMIT 0.5f #define COLLECTABLE_SCALE 5.f +#define MASKED_SPRITE_LOWER_BOUND -3.f // Tracking view defaults #define DEF_TV_IS_ABSOLUTE_FREE_MOVE_THRESHOLD false @@ -70,4 +71,14 @@ #define DB_WORLD_GRID_RENDER false #define DB_TRACKING_VIEW_CENTER false +// Fonts +#define FONT_BASE_PATH "assets/fonts/" +#define DEFAULT_FONT "pixel" +#define COUNTDOWN_FONT_SIZE 50 + +// Name => Path +const std::map all_fonts = { + {"pixel", "pixel.ttf"} +}; + #endif //HOLESOME_CONFIG_H diff --git a/src/game/camera/tracking_view.cpp b/src/game/camera/tracking_view.cpp index 4b72412..e415e82 100644 --- a/src/game/camera/tracking_view.cpp +++ b/src/game/camera/tracking_view.cpp @@ -3,7 +3,8 @@ #include "../player/player_collection.hpp" TrackingView::TrackingView(TrackingViewOptions options) : options(options), - view(new sf::View()), + view(new sf::View(options.initialCenter, + options.minViewSize)), trackables({}) { marker = new CircleObject(DB_CIRCLE_RADIUS, sf::Color::Yellow); @@ -22,8 +23,8 @@ void TrackingView::lateUpdate() if (!trackables.empty()) { followTrackables(); - adjustSizeToTrackables(); } + adjustSizeToTrackables(); } void TrackingView::setSize(sf::Vector2f newSize) @@ -143,6 +144,11 @@ void TrackingView::processTrackableStates() TrackingArea TrackingView::getTrackingArea() const { + if (trackables.empty()) + { + return TrackingArea(); + } + auto initialTrackable = trackables[0]; TrackingArea area = { initialTrackable->getTrackablePosition() - initialTrackable->getTrackableSize() / 2.f, diff --git a/src/game/camera/tracking_view_options.hpp b/src/game/camera/tracking_view_options.hpp index 98d1beb..cf067be 100644 --- a/src/game/camera/tracking_view_options.hpp +++ b/src/game/camera/tracking_view_options.hpp @@ -21,18 +21,20 @@ struct TrackingViewOptions float softResizeSpeed = DEF_TV_SOFT_RESIZE_SPEED; /** - * If setWorld to 0, view will not be limited. + * If set to 0, view will not be limited. */ sf::Vector2f minViewSize = DEF_TV_MIN_VIEW_SIZE; /** - * If setWorld to 0, view will not be limited. + * If set to 0, view will not be limited. */ sf::Vector2f maxViewSize = DEF_TV_MAX_VIEW_SIZE; + sf::Vector2f initialCenter = {0, 0}; + /** * Will be added to tracked area size twice, as padding for each side. - * If isAbsoluteViewSizePadding is setWorld to true, padding will be added to view size, is multiplied with tracking size and added. + * If isAbsoluteViewSizePadding is set to true, padding will be added to view size, is multiplied with tracking size and added. */ sf::Vector2f viewSizePadding = DEF_TV_VIEW_SIZE_PADDING; bool isAbsoluteViewSizePadding = DEF_TV_IS_ABSOLUTE_VIEW_SIZE_PADDING; diff --git a/src/game/collectables/collectable.cpp b/src/game/collectables/collectable.cpp index 6152fd6..ad3e51f 100644 --- a/src/game/collectables/collectable.cpp +++ b/src/game/collectables/collectable.cpp @@ -2,8 +2,12 @@ #include "../../sprites/versatile_sprite.hpp" #include "../../config.h" #include "../input/input_mapper.h" +#include "../physics/holes/holes_simulation.hpp" +#include "collection/collectables_collection.hpp" +#include "../player/player_collection.hpp" -Collectable::Collectable() +Collectable::Collectable(int points) + : points(points) { collectableId = collectableCount; collectableCount++; @@ -24,10 +28,12 @@ float Collectable::getDepth() const void Collectable::setSprite(const std::string &spriteName) { // Create versatile sprite - auto sprite = std::make_shared(spriteName); - size = sprite->getSize() * COLLECTABLE_SCALE; - sprite->setSize(size); - addChildScreenOffset(sprite, IsometricCoordinates(-size / 2.f)); + auto spriteObject = std::make_shared(spriteName); + size = spriteObject->getSize() * COLLECTABLE_SCALE; + spriteObject->setSize(size); + addChildScreenOffset(spriteObject, IsometricCoordinates(-size / 2.f)); + + sprite = spriteObject; // Set half size offset of coordinates coordinates->move(IsometricCoordinates(0, -size.x / 2.f, 0)); @@ -37,3 +43,16 @@ sf::Vector2f Collectable::getSize() const { return size / WORLD_TO_ISO_SCALE; } + +void Collectable::preRenderUpdate() +{ + if (!sprite->isVisible()) + { + auto closestPlayer = PlayerCollection::getInstance()->getClosestPlayer(*coordinates); + closestPlayer->consume(points); + setActive(false); + return; + } + + GameObject::preRenderUpdate(); +} diff --git a/src/game/collectables/collectable.hpp b/src/game/collectables/collectable.hpp index 70251d2..7215948 100644 --- a/src/game/collectables/collectable.hpp +++ b/src/game/collectables/collectable.hpp @@ -4,11 +4,12 @@ #include "../game_object.h" #include "../player/player.hpp" +#include "../../sprites/masked_sprite.hpp" class Collectable : public GameObject { public: - Collectable(); + Collectable(int points); void setSprite(const std::string &spriteName); @@ -23,11 +24,15 @@ public: return collectableId; } + void preRenderUpdate() override; + private: int collectableId = 0; static inline int collectableCount = 0; + std::shared_ptr sprite; sf::Vector2f size; + int points = 0; std::shared_ptr consumedBy = nullptr; }; diff --git a/src/game/collectables/collectable_config.hpp b/src/game/collectables/collectable_config.hpp index 34fcbae..ffb9717 100644 --- a/src/game/collectables/collectable_config.hpp +++ b/src/game/collectables/collectable_config.hpp @@ -6,9 +6,10 @@ struct CollectableConfig { std::string spriteName; + int points = 0; - explicit CollectableConfig(std::string spriteName) - : spriteName(std::move(spriteName)) + explicit CollectableConfig(std::string spriteName, int points) + : spriteName(std::move(spriteName)), points(points) {} CollectableConfig() = default; diff --git a/src/game/collectables/collectable_factory.cpp b/src/game/collectables/collectable_factory.cpp index 8fcca64..d46d1fd 100644 --- a/src/game/collectables/collectable_factory.cpp +++ b/src/game/collectables/collectable_factory.cpp @@ -3,7 +3,7 @@ std::shared_ptr CollectableFactory::createFromInLevelConfig(const CollectableInLevel &config) { auto collectableConfig = config.collectableConfig; - auto collectable = std::make_shared(); + auto collectable = std::make_shared(config.collectableConfig.points); collectable->coordinates->setGrid(config.position); collectable->setSprite(collectableConfig.spriteName); diff --git a/src/game/collectables/collection/collectables_collection.cpp b/src/game/collectables/collection/collectables_collection.cpp index fd52503..267e105 100644 --- a/src/game/collectables/collection/collectables_collection.cpp +++ b/src/game/collectables/collection/collectables_collection.cpp @@ -4,6 +4,7 @@ #include "collectables_depth_collection.hpp" #include "../../../logging/easylogging++.h" #include "../../../config.h" +#include "../../physics/holes/holes_simulation.hpp" std::shared_ptr CollectablesCollection::getInstance() { @@ -28,8 +29,26 @@ void CollectablesCollection::createEmpty(int maxDepth) } } -void CollectablesCollection::remove(const std::shared_ptr &collectable) +void CollectablesCollection::remove(int collectableId) { + HolesSimulation::getInstance()->removeCollectable(collectableId); + + std::shared_ptr collectable = nullptr; + for (auto &child: getChildren()) + { + collectable = std::dynamic_pointer_cast(child); + if (collectable->getId() == collectableId) + { + break; + } + } + + if (collectable == nullptr) + { + LOG(ERROR) << "No collectables left to remove."; + return; + } + depthCollections[collectable->getDepth()]->remove(collectable); removeChild(collectable); } @@ -44,7 +63,21 @@ void CollectablesCollection::update() { GameObject::update(); - if (!CONSIDER_COLLECTABLE_DEPTH_MOVEMENT) { + // Remove inactive collectables + auto collectables = getChildren(); + for (auto &child: collectables) + { + auto collectable = std::dynamic_pointer_cast(child); + if (collectable->getActive()) + { + continue; + } + + remove(collectable->getId()); + } + + if (!CONSIDER_COLLECTABLE_DEPTH_MOVEMENT) + { return; } diff --git a/src/game/collectables/collection/collectables_collection.hpp b/src/game/collectables/collection/collectables_collection.hpp index a01bf5b..0f64297 100644 --- a/src/game/collectables/collection/collectables_collection.hpp +++ b/src/game/collectables/collection/collectables_collection.hpp @@ -21,7 +21,7 @@ public: void draw(sf::RenderWindow *window) override; void add(const std::shared_ptr& collectable); - void remove(const std::shared_ptr& collectable); + void remove(int collectableId); private: static inline std::shared_ptr singletonInstance = nullptr; diff --git a/src/game/game.cpp b/src/game/game.cpp index e74cc52..b588bb8 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6,6 +6,7 @@ #include "level/level_loader.hpp" #include "physics/map/map_simulation.hpp" #include "../logging/easylogging++.h" +#include "layer/global_layer.hpp" Game::Game(std::shared_ptr window) : window(std::move(window)) { @@ -145,3 +146,9 @@ bool Game::isLevelLoaded() const { return loadedLevelConfig.isValid(); } + +void Game::startCountdown(int durationInSeconds) +{ + countdown = std::make_shared(durationInSeconds); + GlobalLayer::getInstance()->add(countdown); +} diff --git a/src/game/game.h b/src/game/game.h index fbbf1c7..6cb17d6 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -9,6 +9,7 @@ #include "camera/tracking_view.h" #include "level/level_config.hpp" #include "frame_counter.hpp" +#include "time/countdown.hpp" class TrackingView; @@ -16,8 +17,10 @@ class TrackingView; class Game { public: - static std::shared_ptr constructInstance(const std::shared_ptr& window); + static std::shared_ptr constructInstance(const std::shared_ptr &window); + static std::shared_ptr getInstance(); + explicit Game(std::shared_ptr window); void run(); @@ -28,9 +31,11 @@ public: void setLevel(LevelConfig levelConfig); + void startCountdown(int durationInSeconds); + [[nodiscard]] bool isLevelLoaded() const; - void addGameObject(const std::shared_ptr& gameObject); + void addGameObject(const std::shared_ptr &gameObject); std::shared_ptr window; private: @@ -41,6 +46,7 @@ private: LevelConfig loadedLevelConfig = {}; std::shared_ptr frameCounter = std::make_shared(); + std::shared_ptr countdown = std::make_shared(); void drawFrame(); diff --git a/src/game/layer/global_layer.cpp b/src/game/layer/global_layer.cpp new file mode 100644 index 0000000..a4cf365 --- /dev/null +++ b/src/game/layer/global_layer.cpp @@ -0,0 +1,39 @@ +#include "global_layer.hpp" + +void GlobalLayer::clear() +{ + clearChildren(); + addDetachedChild(view); +} + +std::shared_ptr GlobalLayer::getInstance() +{ + if (singletonInstance == nullptr) + { + singletonInstance = std::make_shared(); + } + return singletonInstance; +} + +void GlobalLayer::draw(sf::RenderWindow *window) +{ + view->setViewForWindow(); + GameObject::draw(window); +} + +GlobalLayer::GlobalLayer() +{ + // Reference screen size of 1920x1080 + TrackingViewOptions options = { + .minViewSize = sf::Vector2f{10, 1080}, + .initialCenter = sf::Vector2f{1920, 1080} / 2.0f, + }; + + view = std::make_shared(options); + addDetachedChild(view); +} + +void GlobalLayer::add(const std::shared_ptr& gameObject) +{ + addDetachedChild(gameObject); +} diff --git a/src/game/layer/global_layer.hpp b/src/game/layer/global_layer.hpp new file mode 100644 index 0000000..22483dc --- /dev/null +++ b/src/game/layer/global_layer.hpp @@ -0,0 +1,27 @@ +#ifndef HOLESOME_GLOBAL_LAYER_HPP +#define HOLESOME_GLOBAL_LAYER_HPP + + +#include "../game_object.h" +#include "../camera/tracking_view.h" + +class GlobalLayer : public GameObject +{ +public: + GlobalLayer(); + + static std::shared_ptr getInstance(); + + void clear(); + + void draw(sf::RenderWindow *window) override; + + void add(const std::shared_ptr& gameObject); + +private: + static inline std::shared_ptr singletonInstance = nullptr; + std::shared_ptr view; +}; + + +#endif //HOLESOME_GLOBAL_LAYER_HPP diff --git a/src/game/level/level_config.hpp b/src/game/level/level_config.hpp index c31a428..0a00445 100644 --- a/src/game/level/level_config.hpp +++ b/src/game/level/level_config.hpp @@ -13,6 +13,7 @@ struct LevelConfig { std::string name; + int durationInSeconds = 0; sf::Vector2i worldMapSize = {}; TileMapConfig tileMapConfig = {}; std::vector playerSpawnPoints = {}; @@ -20,11 +21,13 @@ struct LevelConfig std::vector skyColors = {}; LevelConfig(std::string name, + int durationInSeconds, const std::vector &playerSpawnPoints, const std::vector &collectables, std::vector skyColors, const TileMapConfig &tileMapConfig) : name(std::move(name)), + durationInSeconds(durationInSeconds), playerSpawnPoints(playerSpawnPoints), tileMapConfig(tileMapConfig), skyColors(std::move(skyColors)) diff --git a/src/game/level/level_loader.cpp b/src/game/level/level_loader.cpp index 8d5e00e..e93a725 100644 --- a/src/game/level/level_loader.cpp +++ b/src/game/level/level_loader.cpp @@ -11,6 +11,7 @@ #include "../camera/multiplayer_view.hpp" #include "../physics/holes/holes_simulation.hpp" #include "../physics/holes/layouts/hole_layout.hpp" +#include "../layer/global_layer.hpp" void LevelLoader::loadLevel(const LevelConfig &levelConfig) { @@ -23,6 +24,7 @@ void LevelLoader::loadLevel(const LevelConfig &levelConfig) MapSimulation::getInstance()->resetMap(levelConfig.worldMapSize); HolesSimulation::getInstance()->clear(); PlayerCollection::getInstance()->clear(); + GlobalLayer::getInstance()->clear(); HoleLayout::getInstance()->clear(); // Add views @@ -60,6 +62,10 @@ void LevelLoader::loadLevel(const LevelConfig &levelConfig) spawnCollectable(collectableInfo); } + game->startCountdown(levelConfig.durationInSeconds); + + // Must be last + game->addGameObject(GlobalLayer::getInstance()); LOG(INFO) << "Finished loading level '" << levelConfig.name << "'."; } diff --git a/src/game/physics/holes/holes_simulation.cpp b/src/game/physics/holes/holes_simulation.cpp index b1499db..2ed92a3 100644 --- a/src/game/physics/holes/holes_simulation.cpp +++ b/src/game/physics/holes/holes_simulation.cpp @@ -2,7 +2,8 @@ std::shared_ptr HolesSimulation::getInstance() { - if (singletonInstance == nullptr) { + if (singletonInstance == nullptr) + { singletonInstance = std::make_shared(); } @@ -13,7 +14,8 @@ std::vector> HolesSimulation::getCollecta { std::vector> collectableSimulations{}; - for (auto &child : getChildren()) { + for (auto &child: getChildren()) + { auto sim = std::dynamic_pointer_cast(child); collectableSimulations.push_back(sim); } @@ -35,8 +37,10 @@ void HolesSimulation::addCollectable(const std::shared_ptr &collect void HolesSimulation::lateUpdate() { // Remove disabled collectables - for (const auto& sim : getCollectableSimulations()) { - if (sim->getCollectable()->getActive()) { + for (const auto &sim: getCollectableSimulations()) + { + if (sim->getCollectable()->getActive()) + { continue; } @@ -45,3 +49,15 @@ void HolesSimulation::lateUpdate() GameObject::lateUpdate(); } + +void HolesSimulation::removeCollectable(int collectableId) +{ + for (const auto &sim: getCollectableSimulations()) + { + if (sim->getCollectable()->getId() == collectableId) + { + removeChild(sim); + return; + } + } +} diff --git a/src/game/physics/holes/holes_simulation.hpp b/src/game/physics/holes/holes_simulation.hpp index 74552cd..c2b2cca 100644 --- a/src/game/physics/holes/holes_simulation.hpp +++ b/src/game/physics/holes/holes_simulation.hpp @@ -13,6 +13,8 @@ public: void addCollectable(const std::shared_ptr &collectable); + void removeCollectable(int collectableId); + void lateUpdate() override; void clear(); diff --git a/src/game/player/player.cpp b/src/game/player/player.cpp index c5bb9e7..d6b6b12 100644 --- a/src/game/player/player.cpp +++ b/src/game/player/player.cpp @@ -14,7 +14,7 @@ Player::Player(std::shared_ptr assignedInput, const std::string & skinSprite = std::make_shared(skinRessourceName, getIsoSize()); addChildScreenOffset(skinSprite, IsometricCoordinates(-getIsoSize() / 2.f)); - updateRadiusBasedOnLevel(); + updateRadiusBasedOnPoints(); LOG(INFO) << "Player " << playerId << " created."; } @@ -41,19 +41,6 @@ void Player::update() auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds(); coordinates->move(moveDelta); - if (input->isPerformingAction(GameAction::GROW)) - { - points = (points + 1) * (1 + 1 * FRAME_TIME.asSeconds()); - } else if (input->isPerformingAction(GameAction::SHRINK)) - { - points *= 1 - 1 * FRAME_TIME.asSeconds(); - if (points < 0) { - points = 0; - } - } - - updateRadiusBasedOnLevel(); - GameObject::update(); } @@ -101,10 +88,18 @@ long Player::getPoints() const return points; } -void Player::updateRadiusBasedOnLevel() +void Player::updateRadiusBasedOnPoints() { long points = getPoints(); - float newWorldRadius = PLAYER_MIN_RADIUS + PLAYER_RADIUS_PER_LEVEL * pow(points / 100.f, 2); + float newWorldRadius = PLAYER_MIN_RADIUS + PLAYER_RADIUS_PER_LEVEL * points / 10.f; setWorldRadius(newWorldRadius); } + +void Player::consume(int points) +{ + this->points += points; + LOG(INFO) << "Player " << playerId << " consumed " << points << " points. Total: " << this->points; + + updateRadiusBasedOnPoints(); +} diff --git a/src/game/player/player.hpp b/src/game/player/player.hpp index 90cef17..331f060 100644 --- a/src/game/player/player.hpp +++ b/src/game/player/player.hpp @@ -31,6 +31,8 @@ public: [[nodiscard]] long getPoints() const; + void consume(int points); + TranslatedCoordinates spawnPosition; private: std::shared_ptr input; @@ -44,7 +46,7 @@ private: void setWorldRadius(float newWorldRadius); - void updateRadiusBasedOnLevel(); + void updateRadiusBasedOnPoints(); }; diff --git a/src/game/player/player_collection.cpp b/src/game/player/player_collection.cpp index 00aa517..08d87e8 100644 --- a/src/game/player/player_collection.cpp +++ b/src/game/player/player_collection.cpp @@ -144,3 +144,22 @@ void PlayerCollection::updateInputIdentityAllowance() const { InputMapper::getInstance()->allowNewInputIdentities = getPlayers().size() < maxPlayerCount; } + +std::shared_ptr PlayerCollection::getClosestPlayer(const TranslatedCoordinates& point) const +{ + std::shared_ptr closestPlayer = nullptr; + float closestDistance = INFINITY; + for (auto &player: getPlayers()) + { + auto playerCenterGround = player->coordinates->world().toGroundCoordinates(); + auto pointCenterGround = point.world().toGroundCoordinates(); + // Normalize distance by player radius to get a value below 1 for something inside the player + float distance = length(playerCenterGround - pointCenterGround) / player->getWorldRadius(); + if (distance < closestDistance) + { + closestPlayer = player; + closestDistance = distance; + } + } + return closestPlayer; +} diff --git a/src/game/player/player_collection.hpp b/src/game/player/player_collection.hpp index ac3aaaf..04aed42 100644 --- a/src/game/player/player_collection.hpp +++ b/src/game/player/player_collection.hpp @@ -18,6 +18,8 @@ public: void removePlayer(const std::shared_ptr& player); + std::shared_ptr getClosestPlayer(const TranslatedCoordinates& point) const; + [[nodiscard]] std::vector> getPlayers() const; [[nodiscard]] std::shared_ptr getPlayerById(int playerId) const; diff --git a/src/game/time/countdown.cpp b/src/game/time/countdown.cpp new file mode 100644 index 0000000..ab1f818 --- /dev/null +++ b/src/game/time/countdown.cpp @@ -0,0 +1,70 @@ +#include +#include +#include "countdown.hpp" +#include "../../typography/font_manager.hpp" +#include "../../config.h" + +void Countdown::restart(int durationInSeconds) +{ + this->durationInSeconds = durationInSeconds; + timeElapsed = sf::Time::Zero; + clock.restart(); + stopped = false; +} + +Countdown::Countdown(int durationInSeconds) +{ + restart(durationInSeconds); +} + +void Countdown::stop() +{ + stopped = true; +} + +bool Countdown::isFinished() const +{ + return timeElapsed.asSeconds() >= durationInSeconds; +} + +void Countdown::update() +{ + GameObject::update(); + + if (stopped) + { + return; + } + + timeElapsed += clock.restart(); +} + +void Countdown::draw(sf::RenderWindow *window) +{ + GameObject::draw(window); + + // Draw the countdown + int timeLeft = durationInSeconds - timeElapsed.asSeconds(); + if (timeLeft <= 0) + { + timeLeft = 0; + } + + int minutes = timeLeft / 60; + int seconds = timeLeft % 60; + std::stringstream ss; + ss << std::setfill('0') << std::setw(2) << minutes << ":" << std::setw(2) << seconds; + auto timeLeftString = ss.str(); + + + auto font = FontManager::getInstance()->getDefaultFont(); + auto text = sf::Text(timeLeftString, *font, COUNTDOWN_FONT_SIZE); + text.setFillColor(sf::Color::White); + text.setPosition(1920 / 2.f, 5); + + // Align top center + sf::FloatRect textRect = text.getLocalBounds(); + text.setOrigin(textRect.left + textRect.width / 2.0f, + textRect.top); + window->draw(text); +} diff --git a/src/game/time/countdown.hpp b/src/game/time/countdown.hpp new file mode 100644 index 0000000..1b4c319 --- /dev/null +++ b/src/game/time/countdown.hpp @@ -0,0 +1,31 @@ +#ifndef HOLESOME_COUNTDOWN_HPP +#define HOLESOME_COUNTDOWN_HPP + + +#include +#include "../game_object.h" + +class Countdown : public GameObject +{ +public: + explicit Countdown(int durationInSeconds = 0); + + void update() override; + + void restart(int durationInSeconds); + + void stop(); + + [[nodiscard]] bool isFinished() const; + + void draw(sf::RenderWindow *window) override; + +private: + sf::Clock clock; + int durationInSeconds = 0; + sf::Time timeElapsed = sf::Time::Zero; + bool stopped = false; +}; + + +#endif //HOLESOME_COUNTDOWN_HPP diff --git a/src/levels.hpp b/src/levels.hpp index 717fada..a4c97ab 100644 --- a/src/levels.hpp +++ b/src/levels.hpp @@ -8,21 +8,25 @@ #define INITIAL_LEVEL "default" std::map const all_levels = { - {"default", LevelConfig("Default", {{0, 0}, - {18, 18}, - {0, 18}, - {18, 0}}, { - CollectableInLevel("box", {3, 5}), - CollectableInLevel("box", {4, 5}), - CollectableInLevel("box", {10, 6}), - CollectableInLevel("box", {2, 8}), - CollectableInLevel("box", {1, 2}), - CollectableInLevel("box", {4, 3}), - CollectableInLevel("box", {8, 3}), - CollectableInLevel("box", {6, 7}), - CollectableInLevel("box", {5, 5}), - CollectableInLevel("box", {9, 5}), - CollectableInLevel("box", {0, 1}) + {"default", LevelConfig("Default", + 30, + { + {0, 0}, + {18, 18}, + {0, 18}, + {18, 0} + }, { + CollectableInLevel("rose", {3, 5}), + CollectableInLevel("rosebush", {4, 5}), + CollectableInLevel("stone", {10, 6}), + CollectableInLevel("bike", {2, 8}), + CollectableInLevel("rose", {1, 2}), + CollectableInLevel("small-tree", {4, 3}), + CollectableInLevel("rose", {8, 3}), + CollectableInLevel("rose", {6, 7}), + CollectableInLevel("rose", {5, 5}), + CollectableInLevel("tram", {9, 5}), + CollectableInLevel("rose", {0, 1}) }, { // Blues @@ -40,25 +44,27 @@ std::map const all_levels = { sf::Color(20, 18, 11), sf::Color::Black }, - TileMapConfig("iso-tiles", {{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}}) + TileMapConfig("iso-tiles", { + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4} + }) )} }; diff --git a/src/main.cpp b/src/main.cpp index 462e8b5..607399e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,11 +4,14 @@ #include "texture_config.h" #include "game/level/level_loader.hpp" #include "levels.hpp" +#include "typography/font_manager.hpp" void loadAllTextures(); void runGame(); +void loadAllFonts(); + INITIALIZE_EASYLOGGINGPP int main(int argc, char *argv[]) @@ -16,9 +19,20 @@ int main(int argc, char *argv[]) START_EASYLOGGINGPP(argc, argv); loadAllTextures(); + loadAllFonts(); runGame(); } +void loadAllFonts() +{ + LOG(INFO) << "Loading fonts..."; + for (auto const &[key, path]: all_fonts) + { + FontManager::getInstance()->loadFont(key, path); + } + LOG(INFO) << "Finished loading fonts."; +} + void runGame() { LOG(INFO) << "Starting game ..."; diff --git a/src/sprites/animated_sprite.cpp b/src/sprites/animated_sprite.cpp index 597d708..78e871e 100644 --- a/src/sprites/animated_sprite.cpp +++ b/src/sprites/animated_sprite.cpp @@ -77,3 +77,8 @@ void AnimatedSprite::preRenderUpdate() renderPosition = calculateRotatedCornerPosition(position, angle); } + +bool AnimatedSprite::isVisible() const +{ + return true; +} diff --git a/src/sprites/animated_sprite.hpp b/src/sprites/animated_sprite.hpp index 5baf4aa..bedaf27 100644 --- a/src/sprites/animated_sprite.hpp +++ b/src/sprites/animated_sprite.hpp @@ -25,6 +25,8 @@ public: void preRenderUpdate() override; + bool isVisible() const override; + void setRotation(float angle) override; private: diff --git a/src/sprites/masked_sprite.cpp b/src/sprites/masked_sprite.cpp index d47ba7a..f3a407a 100644 --- a/src/sprites/masked_sprite.cpp +++ b/src/sprites/masked_sprite.cpp @@ -24,7 +24,15 @@ void MaskedSprite::preRenderUpdate() { GameObject::preRenderUpdate(); - // TODO: Did anything change? Is update of masked sprite necessary? + if (isHidden) + { + return; + } + + if (previousAngle == angle && previousRenderPosition == renderPosition) + { + return; + } if (angle == 0) { @@ -38,6 +46,14 @@ void MaskedSprite::preRenderUpdate() sprite->setRotation(angle); updateFreshMaskedSprite(); + + previousAngle = angle; + previousRenderPosition = renderPosition; + + if (isHidden) + { + LOG(INFO) << "Masked sprite has been permanently hidden"; + } } void MaskedSprite::draw(sf::RenderWindow *window) @@ -49,14 +65,12 @@ void MaskedSprite::draw(sf::RenderWindow *window) void MaskedSprite::updateFreshMaskedSprite() { - // TODO: Calculate depth per pixel auto maskedImage = std::make_shared(); maskedImage->create(textureRect.width, textureRect.height, sf::Color::Transparent); - -// todo: or use sf::RenderTexture? - maskedImage->copy(*image, 0, 0); + isHidden = true; + // Calculate world coordinates for each pixel auto coordinatesTopLeftCorner = TranslatedCoordinates( IsometricCoordinates(renderPosition.x, renderPosition.y, coordinates->isometric().depth)); @@ -77,6 +91,7 @@ void MaskedSprite::updateFreshMaskedSprite() topLeftCorner + xAxis * (xOffset * xFactorPerPixel) + yAxis * (yOffset * yFactorPerPixel); if (pixelPosition.y >= 0) { + isHidden = false; continue; } @@ -124,6 +139,7 @@ sf::Color MaskedSprite::calculateNewPixelColor(sf::Color currentColor, sf::Vecto if (position.y >= 0) { // Pixel is above ground + isHidden = false; return currentColor; } @@ -138,6 +154,7 @@ sf::Color MaskedSprite::calculateNewPixelColor(sf::Color currentColor, sf::Vecto auto players = PlayerCollection::getInstance()->getPlayers(); if (players.empty()) { + isHidden = false; return currentColor; } @@ -153,6 +170,16 @@ sf::Color MaskedSprite::calculateNewPixelColor(sf::Color currentColor, sf::Vecto } float biggestHoleAlphaFactor = *std::max_element(holeAlphaFactors.begin(), holeAlphaFactors.end()); + if (biggestHoleAlphaFactor > 0) + { + isHidden = false; + } + currentColor.a *= biggestHoleAlphaFactor; return currentColor; } + +bool MaskedSprite::isVisible() const +{ + return !isHidden; +} diff --git a/src/sprites/masked_sprite.hpp b/src/sprites/masked_sprite.hpp index fa0b649..bd31d36 100644 --- a/src/sprites/masked_sprite.hpp +++ b/src/sprites/masked_sprite.hpp @@ -27,6 +27,8 @@ public: void setRotation(float angle) override; + bool isVisible() const override; + private: std::shared_ptr sprite; std::shared_ptr maskedTexture; @@ -34,7 +36,11 @@ private: std::shared_ptr image; sf::IntRect textureRect; float angle = 0; + float previousAngle = -10; sf::Vector2f renderPosition; + sf::Vector2f previousRenderPosition = sf::Vector2f(-10, 0); + + bool isHidden = false; void updateFreshMaskedSprite(); diff --git a/src/sprites/single_sprite.cpp b/src/sprites/single_sprite.cpp index 4f70e29..b29130c 100644 --- a/src/sprites/single_sprite.cpp +++ b/src/sprites/single_sprite.cpp @@ -61,3 +61,8 @@ void SingleSprite::preRenderUpdate() renderPosition = calculateRotatedCornerPosition(position, angle); } + +bool SingleSprite::isVisible() const +{ + return true; +} diff --git a/src/sprites/single_sprite.hpp b/src/sprites/single_sprite.hpp index 41a69db..b874a2f 100644 --- a/src/sprites/single_sprite.hpp +++ b/src/sprites/single_sprite.hpp @@ -25,6 +25,8 @@ public: void preRenderUpdate() override; + bool isVisible() const override; + private: sf::Sprite sprite; float angle = 0; diff --git a/src/sprites/sprite.hpp b/src/sprites/sprite.hpp index 67ffa1c..3a2629e 100644 --- a/src/sprites/sprite.hpp +++ b/src/sprites/sprite.hpp @@ -25,6 +25,11 @@ public: throw std::runtime_error("Sprite::getSprite() not implemented"); } + [[nodiscard]] virtual bool isVisible() const + { + throw std::runtime_error("Sprite::isVisible() not implemented"); + } + /** * Rotates a sprite around its center. * @param angle [0, 360] diff --git a/src/sprites/versatile_sprite.cpp b/src/sprites/versatile_sprite.cpp index df7a276..c542002 100644 --- a/src/sprites/versatile_sprite.cpp +++ b/src/sprites/versatile_sprite.cpp @@ -46,3 +46,8 @@ void VersatileSprite::setRotation(float angle) { sprite->setRotation(angle); } + +bool VersatileSprite::isVisible() const +{ + return sprite->isVisible(); +} diff --git a/src/sprites/versatile_sprite.hpp b/src/sprites/versatile_sprite.hpp index f25ea81..1c57447 100644 --- a/src/sprites/versatile_sprite.hpp +++ b/src/sprites/versatile_sprite.hpp @@ -22,6 +22,8 @@ public: void setRotation(float angle) override; + bool isVisible() const override; + private: std::shared_ptr sprite = nullptr; }; diff --git a/src/typography/font_manager.cpp b/src/typography/font_manager.cpp new file mode 100644 index 0000000..247b2c5 --- /dev/null +++ b/src/typography/font_manager.cpp @@ -0,0 +1,37 @@ +#include "font_manager.hpp" +#include "../config.h" + +std::shared_ptr FontManager::getInstance() +{ + if (singletonInstance == nullptr) + { + singletonInstance = std::make_shared(); + } + return singletonInstance; +} + +void FontManager::loadFont(const std::string &name, const std::string &fontPath) +{ + auto font = std::make_shared(); + auto fullPath = FONT_BASE_PATH + fontPath; + if (!font->loadFromFile(fullPath)) + { + throw std::runtime_error("Failed to load font: " + fullPath); + } + + fonts[name] = font; +} + +std::shared_ptr FontManager::getFont(const std::string &name) +{ + if (fonts.find(name) == fonts.end()) + { + throw std::runtime_error("Font not found: " + name); + } + return fonts[name]; +} + +std::shared_ptr FontManager::getDefaultFont() +{ + return getFont(DEFAULT_FONT); +} diff --git a/src/typography/font_manager.hpp b/src/typography/font_manager.hpp new file mode 100644 index 0000000..9233b1f --- /dev/null +++ b/src/typography/font_manager.hpp @@ -0,0 +1,27 @@ +#ifndef HOLESOME_FONT_MANAGER_HPP +#define HOLESOME_FONT_MANAGER_HPP + + +#include +#include +#include + +class FontManager +{ +public: + static std::shared_ptr getInstance(); + + void loadFont(const std::string &name, const std::string &fontPath); + + std::shared_ptr getFont(const std::string &name); + + std::shared_ptr getDefaultFont(); + +private: + static inline std::shared_ptr singletonInstance = nullptr; + + std::map> fonts; +}; + + +#endif //HOLESOME_FONT_MANAGER_HPP