Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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/fonts/specialElite.ttf
Binary file not shown.
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.
9 changes: 9 additions & 0 deletions src/EnemyColliderInfo.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once
#include <glm/vec2.hpp>

namespace miracle {
struct CollisionParams {
glm::vec2 pos;
float diameter;
};
} // namespace miracle
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
36 changes: 36 additions & 0 deletions src/enemy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#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 {
if (can_render) { 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;
}

CollisionParams Enemy::get_collision_params() const { return {.pos = m_sprite.transform.position, .diameter = m_diameter}; }
void Enemy::take_damage(std::size_t dmg) {
can_render = true;
m_health = (dmg >= m_health) ? 0 : (m_health - dmg);
}

} // namespace miracle
37 changes: 37 additions & 0 deletions src/enemy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#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 <cstddef>
#include <optional>
#include "EnemyColliderInfo.hpp"
#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);
void take_damage(std::size_t dmg);
[[nodiscard]] std::size_t get_health() const { return m_health; }
[[nodiscard]] CollisionParams get_collision_params() const;
bool can_render{false};

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{};
};
101 changes: 81 additions & 20 deletions src/game.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
#include <game.hpp>
#include <glm/gtx/norm.hpp>
#include <le2d/context.hpp>
#include <cmath>
#include <cstddef>
#include <format>
#include <iterator>
#include <string>
#include <vector>
#include "enemy.hpp"
#include "enemy_params.hpp"
#include "glm/ext/vector_float2.hpp"
#include "kvf/time.hpp"
#include "le2d/asset_loader.hpp"
#include "le2d/data_loader.hpp"
#include "le2d/drawable/text.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_light(services) {
spawn_wave();
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_font = asset_loader.load_font("fonts/specialElite.ttf");
}

void Game::on_cursor_pos(le::event::CursorPos const& cursor_pos) {
Expand All @@ -19,21 +31,70 @@ 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;
m_running = m_lighthouse.get_health() > 0;
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) {
m_light.check_enemy_collision(enemy);
m_lighthouse.check_visibility_range(enemy);
update_health_text();
enemy.translate(dt);
}
// Keep track of how many enemies were defeated and calculate score
auto res = std::erase_if(m_enemies, [](Enemy const& enemy) { return !enemy.get_health(); });
update_score(static_cast<int>(res * 10));
m_light.set_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_light.render(renderer);
m_lighthouse.render(renderer);
for (auto const& enemy : m_enemies) { enemy.render(renderer); }
m_score_text.draw(renderer);
m_health_text.draw(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()));
}

void Game::update_score(int points) {
auto const framebuffer_size = m_services->get<le::Context>().framebuffer_size();
m_score_text.transform.position.y = static_cast<float>(framebuffer_size.y) / 2.0f - 50.0f;
m_score += points;
m_score_str.clear();
std::format_to(std::back_inserter(m_score_str), "Score: {}", m_score);
m_score_text.set_string(m_font, m_score_str);
}

void Game::update_health_text() {
auto const framebuffer_size = m_services->get<le::Context>().framebuffer_size();
float const x = (static_cast<float>(framebuffer_size.x) * 0.5f) - 150.0f;
float const y = (static_cast<float>(framebuffer_size.y) * 0.5f) - 50.0f;
m_health_text.transform.position = {x, y};

m_health_str.clear();
if (m_lighthouse.get_health() <= 0.0f) {
std::format_to(std::back_inserter(m_health_str), "Game Over");
} else {
std::format_to(std::back_inserter(m_health_str), "Health: {:.1f}", m_lighthouse.get_health());
}

m_health_text.set_string(m_font, m_health_str);
}

} // namespace miracle
25 changes: 22 additions & 3 deletions src/game.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
#include <le2d/event.hpp>
#include <le2d/renderer.hpp>
#include <le2d/service_locator.hpp>
#include <cstddef>
#include "enemy.hpp"
#include "le2d/drawable/text.hpp"
#include "le2d/font.hpp"
#include "lighhouse.hpp"
#include "light.hpp"

namespace miracle {
class Game {
Expand All @@ -14,13 +20,26 @@ class Game {

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

private:
gsl::not_null<le::ServiceLocator const*> m_services;
Lighthouse m_lighthouse;
Light m_light;

le::drawable::Triangle m_triangle{};
le::drawable::Circle m_circle{};

le::Font m_font{};
le::drawable::Text m_score_text{};
le::drawable::Text m_health_text{};
int m_score{};
std::string m_score_str;
std::string m_health_str;
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
34 changes: 34 additions & 0 deletions src/lighhouse.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 <cstddef>
#include <optional>
#include "enemy.hpp"
#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;
void check_visibility_range(Enemy& enemy);
void check_damage_taken(Enemy& enemy);
void take_damage(float dmg);
[[nodiscard]] float get_health() const;

private:
gsl::not_null<le::ServiceLocator const*> m_services;
float m_hitbox_diameter{150.0f};
float m_visibility_diameter{250.0f};
std::optional<le::Texture> m_texture;
le::drawable::Circle m_sprite{};
float m_health{100};
};
} // namespace miracle
17 changes: 17 additions & 0 deletions src/light.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <light.hpp>
#include "kvf/color.hpp"

namespace miracle {
Light::Light(gsl::not_null<le::ServiceLocator const*> services) : m_services(services), m_diameter(100) { m_sprite.create(150.0f, kvf::white_v); }

void Light::check_enemy_collision(Enemy& enemy) {
auto const [pos, diameter] = enemy.get_collision_params();
if (glm::distance(pos, m_sprite.transform.position) < (diameter + m_diameter) / 2) {
enemy.take_damage(1);
} else {
enemy.can_render = false;
}
}
void Light::render(le::Renderer& renderer) const { m_sprite.draw(renderer); }
void Light::set_position(glm::vec2 cursor_pos) { m_sprite.transform.position = cursor_pos; }
} // namespace miracle
26 changes: 26 additions & 0 deletions src/light.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include "enemy.hpp"
#include "glm/vec2.hpp"
#include "gsl/pointers"
#include "le2d/drawable/shape.hpp"
#include "le2d/renderer.hpp"
#include "le2d/service_locator.hpp"
#include "le2d/texture.hpp"

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

void render(le::Renderer& renderer) const;
void set_position(glm::vec2 cursor_pos);
void check_enemy_collision(Enemy& enemy);

private:
gsl::not_null<le::ServiceLocator const*> m_services;
std::optional<le::Texture> m_texture;
le::drawable::Circle m_sprite{};
float m_diameter{};
};
} // namespace miracle
Loading