Added consumption and countdown

This commit is contained in:
Maximilian Giller 2023-07-09 21:24:50 +02:00
parent b0a83a90ef
commit 241d8119ce
39 changed files with 538 additions and 81 deletions

View file

@ -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)

BIN
assets/fonts/pixel.ttf Normal file

Binary file not shown.

View file

@ -6,7 +6,12 @@
#include "game/collectables/collectable_config.hpp"
std::map<std::string, CollectableConfig> 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

View file

@ -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<std::string, std::string> all_fonts = {
{"pixel", "pixel.ttf"}
};
#endif //HOLESOME_CONFIG_H

View file

@ -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,

View file

@ -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;

View file

@ -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<VersatileSprite>(spriteName);
size = sprite->getSize() * COLLECTABLE_SCALE;
sprite->setSize(size);
addChildScreenOffset(sprite, IsometricCoordinates(-size / 2.f));
auto spriteObject = std::make_shared<VersatileSprite>(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();
}

View file

@ -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> sprite;
sf::Vector2f size;
int points = 0;
std::shared_ptr<Player> consumedBy = nullptr;
};

View file

@ -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;

View file

@ -3,7 +3,7 @@
std::shared_ptr<Collectable> CollectableFactory::createFromInLevelConfig(const CollectableInLevel &config)
{
auto collectableConfig = config.collectableConfig;
auto collectable = std::make_shared<Collectable>();
auto collectable = std::make_shared<Collectable>(config.collectableConfig.points);
collectable->coordinates->setGrid(config.position);
collectable->setSprite(collectableConfig.spriteName);

View file

@ -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> CollectablesCollection::getInstance()
{
@ -28,8 +29,26 @@ void CollectablesCollection::createEmpty(int maxDepth)
}
}
void CollectablesCollection::remove(const std::shared_ptr<Collectable> &collectable)
void CollectablesCollection::remove(int collectableId)
{
HolesSimulation::getInstance()->removeCollectable(collectableId);
std::shared_ptr<Collectable> collectable = nullptr;
for (auto &child: getChildren())
{
collectable = std::dynamic_pointer_cast<Collectable>(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<Collectable>(child);
if (collectable->getActive())
{
continue;
}
remove(collectable->getId());
}
if (!CONSIDER_COLLECTABLE_DEPTH_MOVEMENT)
{
return;
}

View file

@ -21,7 +21,7 @@ public:
void draw(sf::RenderWindow *window) override;
void add(const std::shared_ptr<Collectable>& collectable);
void remove(const std::shared_ptr<Collectable>& collectable);
void remove(int collectableId);
private:
static inline std::shared_ptr<CollectablesCollection> singletonInstance = nullptr;

View file

@ -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<sf::RenderWindow> 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<Countdown>(durationInSeconds);
GlobalLayer::getInstance()->add(countdown);
}

View file

@ -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<Game> constructInstance(const std::shared_ptr<sf::RenderWindow>& window);
static std::shared_ptr<Game> constructInstance(const std::shared_ptr<sf::RenderWindow> &window);
static std::shared_ptr<Game> getInstance();
explicit Game(std::shared_ptr<sf::RenderWindow> 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>& gameObject);
void addGameObject(const std::shared_ptr<GameObject> &gameObject);
std::shared_ptr<sf::RenderWindow> window;
private:
@ -41,6 +46,7 @@ private:
LevelConfig loadedLevelConfig = {};
std::shared_ptr<FrameCounter> frameCounter = std::make_shared<FrameCounter>();
std::shared_ptr<Countdown> countdown = std::make_shared<Countdown>();
void drawFrame();

View file

@ -0,0 +1,39 @@
#include "global_layer.hpp"
void GlobalLayer::clear()
{
clearChildren();
addDetachedChild(view);
}
std::shared_ptr<GlobalLayer> GlobalLayer::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<GlobalLayer>();
}
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<TrackingView>(options);
addDetachedChild(view);
}
void GlobalLayer::add(const std::shared_ptr<GameObject>& gameObject)
{
addDetachedChild(gameObject);
}

View file

@ -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<GlobalLayer> getInstance();
void clear();
void draw(sf::RenderWindow *window) override;
void add(const std::shared_ptr<GameObject>& gameObject);
private:
static inline std::shared_ptr<GlobalLayer> singletonInstance = nullptr;
std::shared_ptr<TrackingView> view;
};
#endif //HOLESOME_GLOBAL_LAYER_HPP

View file

@ -13,6 +13,7 @@
struct LevelConfig
{
std::string name;
int durationInSeconds = 0;
sf::Vector2i worldMapSize = {};
TileMapConfig tileMapConfig = {};
std::vector<GridCoordinates> playerSpawnPoints = {};
@ -20,11 +21,13 @@ struct LevelConfig
std::vector<sf::Color> skyColors = {};
LevelConfig(std::string name,
int durationInSeconds,
const std::vector<GridCoordinates> &playerSpawnPoints,
const std::vector<CollectableInLevel> &collectables,
std::vector<sf::Color> skyColors,
const TileMapConfig &tileMapConfig)
: name(std::move(name)),
durationInSeconds(durationInSeconds),
playerSpawnPoints(playerSpawnPoints),
tileMapConfig(tileMapConfig),
skyColors(std::move(skyColors))

View file

@ -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 << "'.";
}

View file

@ -2,7 +2,8 @@
std::shared_ptr<HolesSimulation> HolesSimulation::getInstance()
{
if (singletonInstance == nullptr) {
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<HolesSimulation>();
}
@ -13,7 +14,8 @@ std::vector<std::shared_ptr<CollectableSimulation>> HolesSimulation::getCollecta
{
std::vector<std::shared_ptr<CollectableSimulation>> collectableSimulations{};
for (auto &child : getChildren()) {
for (auto &child: getChildren())
{
auto sim = std::dynamic_pointer_cast<CollectableSimulation>(child);
collectableSimulations.push_back(sim);
}
@ -35,8 +37,10 @@ void HolesSimulation::addCollectable(const std::shared_ptr<Collectable> &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;
}
}
}

View file

@ -13,6 +13,8 @@ public:
void addCollectable(const std::shared_ptr<Collectable> &collectable);
void removeCollectable(int collectableId);
void lateUpdate() override;
void clear();

View file

@ -14,7 +14,7 @@ Player::Player(std::shared_ptr<InputIdentity> assignedInput, const std::string &
skinSprite = std::make_shared<VersatileSprite>(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();
}

View file

@ -31,6 +31,8 @@ public:
[[nodiscard]] long getPoints() const;
void consume(int points);
TranslatedCoordinates spawnPosition;
private:
std::shared_ptr<InputIdentity> input;
@ -44,7 +46,7 @@ private:
void setWorldRadius(float newWorldRadius);
void updateRadiusBasedOnLevel();
void updateRadiusBasedOnPoints();
};

View file

@ -144,3 +144,22 @@ void PlayerCollection::updateInputIdentityAllowance() const
{
InputMapper::getInstance()->allowNewInputIdentities = getPlayers().size() < maxPlayerCount;
}
std::shared_ptr<Player> PlayerCollection::getClosestPlayer(const TranslatedCoordinates& point) const
{
std::shared_ptr<Player> 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;
}

View file

@ -18,6 +18,8 @@ public:
void removePlayer(const std::shared_ptr<Player>& player);
std::shared_ptr<Player> getClosestPlayer(const TranslatedCoordinates& point) const;
[[nodiscard]] std::vector<std::shared_ptr<Player>> getPlayers() const;
[[nodiscard]] std::shared_ptr<Player> getPlayerById(int playerId) const;

View file

@ -0,0 +1,70 @@
#include <SFML/Graphics/Text.hpp>
#include <iomanip>
#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);
}

View file

@ -0,0 +1,31 @@
#ifndef HOLESOME_COUNTDOWN_HPP
#define HOLESOME_COUNTDOWN_HPP
#include <SFML/System/Clock.hpp>
#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

View file

@ -8,21 +8,25 @@
#define INITIAL_LEVEL "default"
std::map<std::string, LevelConfig> 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<std::string, LevelConfig> 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}
})
)}
};

View file

@ -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 ...";

View file

@ -77,3 +77,8 @@ void AnimatedSprite::preRenderUpdate()
renderPosition = calculateRotatedCornerPosition(position, angle);
}
bool AnimatedSprite::isVisible() const
{
return true;
}

View file

@ -25,6 +25,8 @@ public:
void preRenderUpdate() override;
bool isVisible() const override;
void setRotation(float angle) override;
private:

View file

@ -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<sf::Image>();
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;
}

View file

@ -27,6 +27,8 @@ public:
void setRotation(float angle) override;
bool isVisible() const override;
private:
std::shared_ptr<sf::Sprite> sprite;
std::shared_ptr<sf::Texture> maskedTexture;
@ -34,7 +36,11 @@ private:
std::shared_ptr<sf::Image> 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();

View file

@ -61,3 +61,8 @@ void SingleSprite::preRenderUpdate()
renderPosition = calculateRotatedCornerPosition(position, angle);
}
bool SingleSprite::isVisible() const
{
return true;
}

View file

@ -25,6 +25,8 @@ public:
void preRenderUpdate() override;
bool isVisible() const override;
private:
sf::Sprite sprite;
float angle = 0;

View file

@ -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]

View file

@ -46,3 +46,8 @@ void VersatileSprite::setRotation(float angle)
{
sprite->setRotation(angle);
}
bool VersatileSprite::isVisible() const
{
return sprite->isVisible();
}

View file

@ -22,6 +22,8 @@ public:
void setRotation(float angle) override;
bool isVisible() const override;
private:
std::shared_ptr<Sprite> sprite = nullptr;
};

View file

@ -0,0 +1,37 @@
#include "font_manager.hpp"
#include "../config.h"
std::shared_ptr<FontManager> FontManager::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<FontManager>();
}
return singletonInstance;
}
void FontManager::loadFont(const std::string &name, const std::string &fontPath)
{
auto font = std::make_shared<sf::Font>();
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<sf::Font> 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<sf::Font> FontManager::getDefaultFont()
{
return getFont(DEFAULT_FONT);
}

View file

@ -0,0 +1,27 @@
#ifndef HOLESOME_FONT_MANAGER_HPP
#define HOLESOME_FONT_MANAGER_HPP
#include <memory>
#include <map>
#include <SFML/Graphics/Font.hpp>
class FontManager
{
public:
static std::shared_ptr<FontManager> getInstance();
void loadFont(const std::string &name, const std::string &fontPath);
std::shared_ptr<sf::Font> getFont(const std::string &name);
std::shared_ptr<sf::Font> getDefaultFont();
private:
static inline std::shared_ptr<FontManager> singletonInstance = nullptr;
std::map<std::string, std::shared_ptr<sf::Font>> fonts;
};
#endif //HOLESOME_FONT_MANAGER_HPP