Skip to content

Refactor lighthouse #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Binary file added assets/images/lighthouse.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <game.hpp>
#include <klib/visitor.hpp>
#include <log.hpp>
#include <util/random.hpp>

namespace miracle {
namespace {
Expand All @@ -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<le::IDataLoader>().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() {
Expand Down
32 changes: 32 additions & 0 deletions src/enemy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <enemy.hpp>
#include <algorithm>
#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<le::ServiceLocator const*> 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<le::Context>().framebuffer_size();
auto const radius = static_cast<float>(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
34 changes: 34 additions & 0 deletions src/enemy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once
#include <glm/gtx/norm.hpp>
#include <kvf/time.hpp>
#include <le2d/context.hpp>
#include <le2d/drawable/shape.hpp>
#include <le2d/event.hpp>
#include <le2d/renderer.hpp>
#include <le2d/service_locator.hpp>
#include <optional>
#include "enemy_params.hpp"
#include "glm/vec2.hpp"
#include "le2d/texture.hpp"

namespace miracle {
class Enemy {
public:
explicit Enemy(gsl::not_null<le::ServiceLocator const*> 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<le::ServiceLocator const*> m_services;
std::optional<le::Texture> 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
7 changes: 7 additions & 0 deletions src/enemy_params.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#pragma once
#include "glm/vec2.hpp"

struct EnemyParams {
glm::vec2 target_pos{};
float move_speed{};
};
55 changes: 36 additions & 19 deletions src/game.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
#include <game.hpp>
#include <glm/gtx/norm.hpp>
#include <le2d/context.hpp>
#include <cmath>
#include <cstddef>
#include <vector>
#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<le::ServiceLocator const*> 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<le::ServiceLocator const*> 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) {
Expand All @@ -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<Enemy> 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
12 changes: 10 additions & 2 deletions src/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
#include <le2d/event.hpp>
#include <le2d/renderer.hpp>
#include <le2d/service_locator.hpp>
#include <cstddef>
#include "enemy.hpp"
#include "lighhouse.hpp"

namespace miracle {
class Game {
Expand All @@ -14,13 +17,18 @@ class Game {

void tick(kvf::Seconds dt);
void render(le::Renderer& renderer) const;
void spawn_wave();

private:
gsl::not_null<le::ServiceLocator const*> 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<Enemy> m_enemies{};
};
} // namespace miracle
25 changes: 25 additions & 0 deletions src/lighhouse.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once
#include <glm/gtx/norm.hpp>
#include <kvf/time.hpp>
#include <le2d/context.hpp>
#include <le2d/drawable/shape.hpp>
#include <le2d/event.hpp>
#include <le2d/renderer.hpp>
#include <le2d/service_locator.hpp>
#include <optional>
#include "le2d/texture.hpp"

namespace miracle {
class Lighthouse {
public:
explicit Lighthouse(gsl::not_null<le::ServiceLocator const*> services);

void rotate_towards_cursor(glm::vec2 cursor_pos);
void render(le::Renderer& renderer) const;

private:
gsl::not_null<le::ServiceLocator const*> m_services;
std::optional<le::Texture> m_texture;
le::drawable::Circle m_sprite{};
};
} // namespace miracle
28 changes: 28 additions & 0 deletions src/lighthouse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <lighhouse.hpp>
#include "le2d/asset_loader.hpp"
#include "le2d/data_loader.hpp"

namespace miracle {
Lighthouse::Lighthouse(gsl::not_null<le::ServiceLocator const*> services) : m_services(services) {
m_sprite.create(200.0f);
auto const& data_loader = services->get<le::IDataLoader>();
auto const& context = services->get<le::Context>();
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
44 changes: 44 additions & 0 deletions src/util/random.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once
#include <glm/ext/vector_float2.hpp>
#include <klib/assert.hpp>
#include <klib/concepts.hpp>
#include <cmath>
#include <numbers>
#include <random>
namespace miracle::util {
/// \brief Wrapper that reuses the same random engine for all calls.
class Random {
public:
template <klib::NumberT Type>
[[nodiscard]] static auto in_range(Type const min, Type const max) -> Type {
if constexpr (std::integral<Type>) {
auto dist = std::uniform_int_distribution<Type>{min, max};
return dist(m_engine);
} else {
auto dist = std::uniform_real_distribution<Type>{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 <klib::NumberT Type>
[[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<float>);
return {radius * std::cos(angle), radius * std::sin(angle)};
}
} // namespace miracle::util