diff --git a/CMakeLists.txt b/CMakeLists.txt index 84f33cb..1004ebd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,3 +23,5 @@ file(GLOB_RECURSE sources LIST_DIRECTORIES false "src/*.[hc]pp") target_sources(${PROJECT_NAME} PRIVATE ${sources} ) + +target_precompile_headers(${PROJECT_NAME} REUSE_FROM le2d) diff --git a/assets/images/lighthouse.png b/assets/images/lighthouse.png new file mode 100644 index 0000000..ce3f73c Binary files /dev/null and b/assets/images/lighthouse.png differ diff --git a/src/app.cpp b/src/app.cpp index 7f17df4..1ea15cb 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace miracle { namespace { @@ -18,6 +19,7 @@ App::App() : m_context(context_ci), m_data_loader(le::FileDataLoader::upfind("as // test code, remove later. auto json = dj::Json{}; if (m_services.get().load_json(json, "test_file.json")) { log.info("loaded JSON: {}", json); } + log.debug("random_range(1, 100): {}", util::random_range(1, 100)); } void App::run() { diff --git a/src/enemy.cpp b/src/enemy.cpp new file mode 100644 index 0000000..5f5f757 --- /dev/null +++ b/src/enemy.cpp @@ -0,0 +1,32 @@ +#include +#include +#include "enemy_params.hpp" +#include "glm/geometric.hpp" +#include "glm/vec2.hpp" +#include "kvf/color.hpp" +#include "util/random.hpp" + +namespace miracle { +Enemy::Enemy(gsl::not_null services, EnemyParams const& params) + : m_services(services), m_target_pos(params.target_pos), m_move_speed(params.move_speed), m_diameter(util::random_range(40.0f, 60.0f)) { + m_sprite.create(m_diameter, kvf::red_v); + auto const framebuffer_size = m_services->get().framebuffer_size(); + auto const radius = static_cast(std::max(framebuffer_size.x, framebuffer_size.y)) / 2.0f; + + m_sprite.transform.position = util::get_random_location_on_radius(radius); + // TODO: add proper textures +} + +void Enemy::render(le::Renderer& renderer) const { m_sprite.draw(renderer); } + +void Enemy::translate(kvf::Seconds const dt) { + glm::vec2 const direction = glm::normalize(m_target_pos - m_sprite.transform.position); + glm::vec2 const movement = direction * m_move_speed * dt.count(); + m_sprite.transform.position += movement; +} + +void Enemy::check_collision(glm::vec2 pos, float radius) { + if (glm::distance(pos, m_sprite.transform.position) < radius + m_diameter / 2) { m_health = 0; } +} + +} // namespace miracle diff --git a/src/enemy.hpp b/src/enemy.hpp new file mode 100644 index 0000000..ab6da0e --- /dev/null +++ b/src/enemy.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "enemy_params.hpp" +#include "glm/vec2.hpp" +#include "le2d/texture.hpp" + +namespace miracle { +class Enemy { + public: + explicit Enemy(gsl::not_null services, EnemyParams const& params); + + void render(le::Renderer& renderer) const; + void translate(kvf::Seconds dt); + // There are temporary parameters, will take the light beam object once it is created + void check_collision(glm::vec2 pos, float radius); + [[nodiscard]] std::size_t get_health() const { return m_health; } + + private: + gsl::not_null m_services; + std::optional m_texture; + le::drawable::Circle m_sprite{}; + glm::vec2 m_target_pos{}; + float m_move_speed{}; + float m_diameter{}; + std::size_t m_health{100}; +}; +} // namespace miracle diff --git a/src/enemy_params.hpp b/src/enemy_params.hpp new file mode 100644 index 0000000..aa8e588 --- /dev/null +++ b/src/enemy_params.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "glm/vec2.hpp" + +struct EnemyParams { + glm::vec2 target_pos{}; + float move_speed{}; +}; diff --git a/src/game.cpp b/src/game.cpp index c5ed010..fc79e5e 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -1,16 +1,18 @@ #include #include #include -#include +#include +#include +#include "enemy.hpp" +#include "enemy_params.hpp" +#include "kvf/time.hpp" +#include "lighhouse.hpp" +#include "util/random.hpp" namespace miracle { -Game::Game(gsl::not_null services) : m_services(services) { - m_triangle.vertices = { - le::Vertex{.position = {-50.0f, -50.0f}}, - le::Vertex{.position = {+50.0f, -50.0f}}, - le::Vertex{.position = {+0.0f, +75.0f}}, - }; - m_circle.create(50.0f); +Game::Game(gsl::not_null services) : m_services(services), m_lighthouse(services) { + m_circle.create(70.0f); + spawn_wave(); } void Game::on_cursor_pos(le::event::CursorPos const& cursor_pos) { @@ -19,21 +21,36 @@ void Game::on_cursor_pos(le::event::CursorPos const& cursor_pos) { } void Game::tick([[maybe_unused]] kvf::Seconds const dt) { - m_circle.transform.position = m_cursor_pos; - - auto const dist_sq = glm::length2(m_cursor_pos); - if (dist_sq > 0.1f) { - auto const dist = std::sqrt(dist_sq); - auto const normalized = m_cursor_pos / dist; - static constexpr auto up_v = glm::vec2{0.0f, 1.0f}; - auto const dot = glm::dot(normalized, up_v); - auto const angle = glm::degrees(std::acos(dot)); - m_triangle.transform.orientation = m_cursor_pos.x > 0.0f ? -angle : angle; + if (!m_running) { return; } + m_time_since_last_wave_spawn += dt; + if (m_time_since_last_wave_spawn >= m_wave_interval) { + spawn_wave(); + m_time_since_last_wave_spawn = kvf::Seconds{}; } + for (auto& enemy : m_enemies) { + enemy.check_collision(m_circle.transform.position, 50.0f); + enemy.translate(dt); + } + std::erase_if(m_enemies, [](Enemy const& enemy) { return !enemy.get_health(); }); + m_circle.transform.position = m_cursor_pos; + m_lighthouse.rotate_towards_cursor(m_cursor_pos); } void Game::render(le::Renderer& renderer) const { - m_triangle.draw(renderer); m_circle.draw(renderer); + m_lighthouse.render(renderer); + for (auto const& enemy : m_enemies) { enemy.render(renderer); } +} + +void Game::spawn_wave() { + ++m_wave_count; + m_wave_interval += kvf::Seconds{5}; + std::vector new_wave; + std::size_t const wave_size = m_wave_count * 3; + new_wave.reserve(wave_size); + for (std::size_t i = 0; i < wave_size; ++i) { + new_wave.emplace_back(m_services, EnemyParams{.target_pos = glm::vec2{0.0f, 0.0f}, .move_speed = util::random_range(35.0f, 65.0f)}); + } + m_enemies.insert(m_enemies.end(), std::make_move_iterator(new_wave.begin()), std::make_move_iterator(new_wave.end())); } } // namespace miracle diff --git a/src/game.hpp b/src/game.hpp index 1601115..19c0b2d 100644 --- a/src/game.hpp +++ b/src/game.hpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include "enemy.hpp" +#include "lighhouse.hpp" namespace miracle { class Game { @@ -14,13 +17,18 @@ class Game { void tick(kvf::Seconds dt); void render(le::Renderer& renderer) const; + void spawn_wave(); private: gsl::not_null m_services; - le::drawable::Triangle m_triangle{}; le::drawable::Circle m_circle{}; - + Lighthouse m_lighthouse; glm::vec2 m_cursor_pos{}; + std::size_t m_wave_count{}; + bool m_running{true}; + kvf::Seconds m_wave_interval{}; + kvf::Seconds m_time_since_last_wave_spawn{}; + std::vector m_enemies{}; }; } // namespace miracle diff --git a/src/lighhouse.hpp b/src/lighhouse.hpp new file mode 100644 index 0000000..8696fa7 --- /dev/null +++ b/src/lighhouse.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "le2d/texture.hpp" + +namespace miracle { +class Lighthouse { + public: + explicit Lighthouse(gsl::not_null services); + + void rotate_towards_cursor(glm::vec2 cursor_pos); + void render(le::Renderer& renderer) const; + + private: + gsl::not_null m_services; + std::optional m_texture; + le::drawable::Circle m_sprite{}; +}; +} // namespace miracle diff --git a/src/lighthouse.cpp b/src/lighthouse.cpp new file mode 100644 index 0000000..d40c723 --- /dev/null +++ b/src/lighthouse.cpp @@ -0,0 +1,28 @@ +#include +#include "le2d/asset_loader.hpp" +#include "le2d/data_loader.hpp" + +namespace miracle { +Lighthouse::Lighthouse(gsl::not_null services) : m_services(services) { + m_sprite.create(200.0f); + auto const& data_loader = services->get(); + auto const& context = services->get(); + auto const asset_loader = le::AssetLoader{&data_loader, &context}; + m_texture = asset_loader.load_texture("images/lighthouse.png"); + m_sprite.texture = &m_texture.value(); +} + +void Lighthouse::rotate_towards_cursor(glm::vec2 cursor_pos) { + auto const dist_sq = glm::length2((cursor_pos)); + if (dist_sq > 0.1f) { + auto const dist = std::sqrt(dist_sq); + auto const normalized = cursor_pos / dist; + static constexpr auto up_v = glm::vec2(0.0f, 1.0f); + auto const dot = glm::dot(normalized, up_v); + auto const angle = glm::degrees(std::acos(dot)); + m_sprite.transform.orientation = cursor_pos.x > 0.0f ? -angle : angle; + } +} + +void Lighthouse::render(le::Renderer& renderer) const { m_sprite.draw(renderer); } +} // namespace miracle diff --git a/src/util/random.hpp b/src/util/random.hpp new file mode 100644 index 0000000..7f6976f --- /dev/null +++ b/src/util/random.hpp @@ -0,0 +1,44 @@ +#pragma once +#include +#include +#include +#include +#include +#include +namespace miracle::util { +/// \brief Wrapper that reuses the same random engine for all calls. +class Random { + public: + template + [[nodiscard]] static auto in_range(Type const min, Type const max) -> Type { + if constexpr (std::integral) { + auto dist = std::uniform_int_distribution{min, max}; + return dist(m_engine); + } else { + auto dist = std::uniform_real_distribution{min, max}; + return dist(m_engine); + } + } + + private: + inline static std::default_random_engine m_engine{std::random_device{}()}; +}; + +/// \returns Random value in the range [min, max]. +template +[[nodiscard]] auto random_range(Type const min, Type const max) -> Type { + return Random::in_range(min, max); +} + +/// \returns Random index in the range [0, size - 1]. +/// \pre size must be greater than 0. +[[nodiscard]] inline auto random_index(std::size_t const size) -> std::size_t { + KLIB_ASSERT(size > 0); + return Random::in_range(0uz, size - 1); +} +// Returns a random coordinate on the specified radius +[[nodiscard]] inline auto get_random_location_on_radius(float radius) -> glm::vec2 { + float const angle = random_range(0.0f, 2.0f * std::numbers::pi_v); + return {radius * std::cos(angle), radius * std::sin(angle)}; +} +} // namespace miracle::util