diff --git a/ext/depzip.jsonc b/ext/depzip.jsonc index e3dcbbf..d98fab9 100644 --- a/ext/depzip.jsonc +++ b/ext/depzip.jsonc @@ -2,7 +2,7 @@ "packages": [ { "uri": "karnkaul/le2d", - "branch": "v0.4.7" + "branch": "v0.4.8" } ] } \ No newline at end of file diff --git a/ext/src.zip b/ext/src.zip index 44a7cb0..fe87156 100644 Binary files a/ext/src.zip and b/ext/src.zip differ diff --git a/lib/include/chomper/runtimes/game.hpp b/lib/include/chomper/runtimes/game.hpp index 3accf0b..bd4f851 100644 --- a/lib/include/chomper/runtimes/game.hpp +++ b/lib/include/chomper/runtimes/game.hpp @@ -3,11 +3,15 @@ #include "chomper/engine.hpp" #include "chomper/player.hpp" #include "chomper/runtime.hpp" +#include "chomper/ui/countdown.hpp" #include "chomper/world.hpp" #include #include #include #include +#include +#include +#include namespace chomper::runtime { // driven by Engine, owner (whether indirectly) of all game things. @@ -49,7 +53,6 @@ class Game : public IRuntime, public klib::Pinned { std::vector m_emptyTiles{}; - le::drawable::Text m_countdownText{}; - kvf::Seconds m_countdown{3}; + std::optional m_countdown{}; }; } // namespace chomper::runtime diff --git a/lib/include/chomper/theme.hpp b/lib/include/chomper/theme.hpp new file mode 100644 index 0000000..460eb75 --- /dev/null +++ b/lib/include/chomper/theme.hpp @@ -0,0 +1,6 @@ +#pragma once +#include + +namespace chomper::theme { +constexpr auto clearColor_v = kvf::Color{glm::vec4{.34f, .54f, .2f, 1.f}}; +} // namespace chomper::theme diff --git a/lib/include/chomper/ui/countdown.hpp b/lib/include/chomper/ui/countdown.hpp new file mode 100644 index 0000000..3eaadc2 --- /dev/null +++ b/lib/include/chomper/ui/countdown.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "le2d/drawable/shape.hpp" +#include "le2d/drawable/text.hpp" +#include + +namespace chomper::ui { +class Countdown { + public: + static constexpr auto textHeight_v = le::TextHeight{120}; + + explicit Countdown(gsl::not_null font, le::TextHeight textHeight = textHeight_v, kvf::Seconds timer = 3s); + + [[nodiscard]] auto getRemain() const -> kvf::Seconds { + return m_remain; + } + + void tick(kvf::Seconds dt); + void draw(le::IRenderer& renderer) const; + + private: + void setTimerText(std::chrono::seconds value); + void updateSector(); + + gsl::not_null m_font; + le::TextHeight m_textHeight{}; + + le::drawable::Sector m_sector{}; + le::drawable::Circle m_background{}; + le::drawable::Text m_text{}; + + kvf::Seconds m_timer{}; + kvf::Seconds m_remain{}; +}; +} // namespace chomper::ui diff --git a/lib/src/engine.cpp b/lib/src/engine.cpp index de52ee2..fcdd666 100644 --- a/lib/src/engine.cpp +++ b/lib/src/engine.cpp @@ -2,6 +2,7 @@ #include "chomper/build_version.hpp" #include "chomper/inclusive_range.hpp" #include "chomper/runtimes/entrypoint.hpp" +#include "chomper/theme.hpp" #include "chomper/viewport.hpp" #include #include @@ -12,8 +13,6 @@ namespace chomper { namespace { -constexpr auto clearColor_v = kvf::Color{glm::vec4{.34f, .54f, .2f, 1.f}}; - std::unique_ptr createEntrypoint(Engine& engine) { return std::make_unique(&engine); } @@ -52,7 +51,7 @@ void Engine::run() { m_runtime->tick(scaledDt); // render runtime. - auto& renderer = m_context->begin_render(clearColor_v); + auto& renderer = m_context->begin_render(theme::clearColor_v); renderer.viewport = viewport_v; renderer.polygon_mode = m_runtimeState.wireframe ? vk::PolygonMode::eLine : vk::PolygonMode::eFill; diff --git a/lib/src/runtimes/game.cpp b/lib/src/runtimes/game.cpp index 8d9d0db..fbae186 100644 --- a/lib/src/runtimes/game.cpp +++ b/lib/src/runtimes/game.cpp @@ -6,11 +6,6 @@ #include namespace chomper::runtime { -namespace { -constexpr auto countdownParams_v = le::drawable::Text::Params{ - .height = le::TextHeight{60}, -}; -} // namespace using ActionValue = le::input::action::Value; Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine->getInputRouter()) { @@ -21,7 +16,7 @@ Game::Game(gsl::not_null engine) : m_engine(engine), m_mapping(&engine- m_collectibles->spawn(*m_player); - m_countdownText.set_string(engine->getResources().getMainFont(), "3", countdownParams_v); + m_countdown.emplace(&engine->getResources().getMainFont()); } void Game::tick(kvf::Seconds const dt) { @@ -31,9 +26,11 @@ void Game::tick(kvf::Seconds const dt) { } ImGui::End(); - if (m_countdown.count() > 0) { - m_countdown -= dt; - m_countdownText.set_string(m_engine->getResources().getMainFont(), std::format("{}", static_cast(m_countdown.count() + 1)), countdownParams_v); + if (m_countdown) { + m_countdown->tick(dt); + if (m_countdown->getRemain() <= 0s) { + m_countdown.reset(); + } return; } @@ -51,8 +48,8 @@ void Game::render(le::IRenderer& renderer) const { m_world->draw(renderer); m_collectibles->draw(renderer); m_player->draw(renderer); - if (m_countdown.count() > 0) { - m_countdownText.draw(renderer); + if (m_countdown) { + m_countdown->draw(renderer); } } diff --git a/lib/src/ui/countdown.cpp b/lib/src/ui/countdown.cpp new file mode 100644 index 0000000..88bf6bf --- /dev/null +++ b/lib/src/ui/countdown.cpp @@ -0,0 +1,66 @@ +#include "chomper/ui/countdown.hpp" +#include "chomper/theme.hpp" +#include + +namespace chomper::ui { +Countdown::Countdown(gsl::not_null font, le::TextHeight const textHeight, kvf::Seconds const timer) + : m_font(font), m_textHeight(textHeight), m_timer(timer), m_remain(timer) { + auto const diameter = float(m_textHeight) + 30.0f; + m_background.create(diameter); + m_background.tint = theme::clearColor_v; +} + +void Countdown::tick(kvf::Seconds const dt) { + if (m_remain <= 0s) { + return; + } + + auto const prevSeconds = std::chrono::duration_cast(m_remain); + m_remain -= dt; + auto const currSeconds = std::chrono::duration_cast(m_remain); + if (prevSeconds > currSeconds) { + setTimerText(currSeconds + 1s); + } + + updateSector(); +} + +void Countdown::draw(le::IRenderer& renderer) const { + if (m_remain <= 0s) { + return; + } + + m_sector.draw(renderer); + m_background.draw(renderer); + m_text.draw(renderer); +} + +void Countdown::setTimerText(std::chrono::seconds const value) { + auto const params = le::drawable::Text::Params{ + .height = m_textHeight, + .expand = le::drawable::TextExpand::eBoth, + }; + auto const text = std::format("{}", value.count()); + m_text.set_string(*m_font, text, params); + + // technically this isn't correct y-centering because parts of glyphs can be above/below the baseline, + // and Text::get_size() is insufficient to adjust for that. + // here, since each displayed glyph (0-9) is entirely above the baseline (unlike say 'g'), + // it can be pushed down by half the size and it will "look" consistently y-centered. + // this is what is known as a "hack". + m_text.transform.position.y = -0.5f * m_text.get_size().y; +} + +void Countdown::updateSector() { + KLIB_ASSERT(m_timer > 0s); + auto const ratio = m_remain / m_timer; + static constexpr auto degreesBegin_v{90.0f}; + auto const degreesEnd = degreesBegin_v + (ratio * 360.0f); + auto const diameter = m_background.get_diameter() + 30.0f; + auto const sectorParams = le::shape::Sector::Params{ + .degrees_begin = degreesBegin_v, + .degrees_end = degreesEnd, + }; + m_sector.create(diameter, sectorParams); +} +} // namespace chomper::ui