Skip to content

Commit 9eb333a

Browse files
authored
Add Enemy health bars. (#30)
* Balance weapon damage a bit. * Refactor progress bar. Use rounded corners for background, shrinking quad as progress. * Data drive creep initial health, set to 2. * Add enemy health bars. * Use orange RGBA for enemy health bars. * Remove test code.
1 parent 325b770 commit 9eb333a

16 files changed

+80
-45
lines changed

assets/styles.json

+12-6
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,16 @@
3131
},
3232
"progress_bars": {
3333
"default": {
34-
"background": "#ffffffff",
35-
"fill": "#9f2b68ff",
36-
"outline": "#702963ff",
37-
"outline_width": 5
34+
"background": "#9f2b68ff",
35+
"fill": "#ffffffff",
36+
"corner_ratio": 0.5,
37+
"padding": 10
38+
},
39+
"enemy": {
40+
"background": "#f75c03ff",
41+
"fill": "#ffffffff",
42+
"corner_ratio": 0.5,
43+
"padding": 5
3844
}
3945
},
4046
"loading_screen": {
@@ -50,7 +56,7 @@
5056
"n_width": 0.800000,
5157
"height": 50.000000,
5258
"bottom_offset": 300.000000,
53-
"outline_width": 10
59+
"padding": 20
5460
}
5561
}
56-
}
62+
}

assets/worlds/playground.json

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"milk"
1414
],
1515
"spawn_rate": 2,
16+
"initial_health": 2,
1617
"death_emitter": "particles/explode.json"
1718
}
1819
]

src/spaced/spaced/game/enemies/basic_creep_factory.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ BasicCreepFactory::BasicCreepFactory(NotNull<Services const*> services, NotNull<
1717
for (auto const& tint : json["tints"].array_view()) { tints.push_back(tint.as<std::string>()); }
1818
if (auto const in_death_emitter = services->get<Resources>().get<ParticleEmitter>(json["death_emitter"].as_string())) { death_emitter = *in_death_emitter; }
1919
spawn_rate = Seconds{json["spawn_rate"].as<float>(spawn_rate.count())};
20+
initial_health = json["initial_health"].as<float>(initial_health);
2021
}
2122

2223
auto BasicCreepFactory::spawn_enemy() -> std::unique_ptr<Enemy> {
@@ -25,6 +26,7 @@ auto BasicCreepFactory::spawn_enemy() -> std::unique_ptr<Enemy> {
2526
auto const& rgbas = m_services->get<Styles>().rgbas;
2627
auto const tint_index = random_in_range(std::size_t{}, tints.size() - 1);
2728
ret->shape.tint = rgbas[tints.at(tint_index)];
29+
ret->health = initial_health;
2830
}
2931
return ret;
3032
}

src/spaced/spaced/game/enemies/basic_creep_factory.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class BasicCreepFactory : public IEnemyFactory {
1818
std::vector<std::string> tints{};
1919
bave::ParticleEmitter death_emitter{};
2020
bave::Seconds spawn_rate{2s};
21+
float initial_health{1.0f};
2122

2223
private:
2324
void do_inspect() final;

src/spaced/spaced/game/enemies/creep.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace spaced {
44
using bave::Seconds;
55

66
void Creep::tick(Seconds const dt) {
7+
Enemy::tick(dt);
78
shape.transform.position.x -= x_speed * dt.count();
89
if (shape.transform.position.x < -0.5f * (get_layout().get_world_space().x + shape.get_shape().size.x)) { set_destroyed(); }
910
}

src/spaced/spaced/game/enemy.cpp

+20-1
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
11
#include <bave/core/random.hpp>
22
#include <bave/imgui/im_text.hpp>
33
#include <spaced/game/enemy.hpp>
4+
#include <spaced/services/services.hpp>
5+
#include <spaced/services/styles.hpp>
46

57
namespace spaced {
68
using bave::im_text;
79
using bave::random_in_range;
810
using bave::RoundedQuad;
11+
using bave::Seconds;
12+
using bave::Shader;
913

1014
Enemy::Enemy(Services const& services, bave::NotNull<IEnemyDeathListener*> listener, std::string_view const type)
11-
: m_layout(&services.get<ILayout>()), m_listener(listener), m_type(type) {
15+
: health_bar(services), m_layout(&services.get<ILayout>()), m_listener(listener), m_type(type) {
1216
static constexpr auto init_size_v = glm::vec2{100.0f};
1317
auto const play_area = m_layout->get_play_area();
1418
auto const y_min = play_area.rb.y + 0.5f * init_size_v.y;
1519
auto const y_max = play_area.lt.y - 0.5f * init_size_v.y;
1620
setup(init_size_v, random_in_range(y_min, y_max));
21+
22+
health_bar.set_style(services.get<Styles>().progress_bars["enemy"]);
1723
}
1824

1925
auto Enemy::take_damage(float const damage) -> bool {
@@ -23,6 +29,19 @@ auto Enemy::take_damage(float const damage) -> bool {
2329
return true;
2430
}
2531

32+
void Enemy::tick(Seconds const dt) {
33+
health_bar.position = shape.transform.position;
34+
health_bar.position.y += 0.5f * shape.get_shape().size.y + 20.0f;
35+
health_bar.size = {shape.get_shape().size.x, 10.0f};
36+
health_bar.set_progress(health.get_hit_points() / health.get_total_hit_points());
37+
health_bar.tick(dt);
38+
}
39+
40+
void Enemy::draw(Shader& shader) const {
41+
shape.draw(shader);
42+
health_bar.draw(shader);
43+
}
44+
2645
void Enemy::setup(glm::vec2 max_size, float y_position) {
2746
auto rounded_quad = RoundedQuad{};
2847
rounded_quad.size = max_size;

src/spaced/spaced/game/enemy.hpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <spaced/game/health.hpp>
88
#include <spaced/services/layout.hpp>
99
#include <spaced/services/services.hpp>
10+
#include <spaced/ui/progress_bar.hpp>
1011

1112
namespace spaced {
1213
class Enemy : public IDamageable, public bave::IDrawable {
@@ -20,8 +21,8 @@ class Enemy : public IDamageable, public bave::IDrawable {
2021
[[nodiscard]] auto is_destroyed() const -> bool { return is_dead() || m_destroyed; }
2122
void set_destroyed() { m_destroyed = true; }
2223

23-
virtual void tick(bave::Seconds dt) = 0;
24-
void draw(bave::Shader& shader) const override { shape.draw(shader); }
24+
virtual void tick(bave::Seconds dt);
25+
void draw(bave::Shader& shader) const override;
2526

2627
void setup(glm::vec2 max_size, float y_position);
2728

@@ -33,6 +34,7 @@ class Enemy : public IDamageable, public bave::IDrawable {
3334
}
3435

3536
bave::RoundedQuadShape shape{};
37+
ui::ProgressBar health_bar;
3638
Health health{};
3739
std::int64_t points{10};
3840

src/spaced/spaced/game/health.cpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@
55
#include <algorithm>
66

77
namespace spaced {
8-
void Health::inflict_damage(float const amount) { set_hit_points(m_hit_points - amount); }
8+
void Health::inflict_damage(float const amount) { set_hit_points(m_current_hp - amount); }
99

10-
bool Health::is_dead() const { return m_hit_points <= 0.f; }
10+
bool Health::is_dead() const { return m_current_hp <= 0.f; }
1111

12-
float Health::get_hit_points() const { return m_hit_points; }
12+
float Health::get_hit_points() const { return m_current_hp; }
1313

14-
void Health::set_hit_points(float const hit_points) { m_hit_points = std::max(hit_points, 0.0f); }
14+
void Health::set_hit_points(float const hit_points) { m_current_hp = std::max(hit_points, 0.0f); }
1515

1616
void Health::do_inspect() {
1717
if constexpr (bave::imgui_v) {
1818
if (ImGui::TreeNode("health")) {
19-
bave::im_text("health: {:.2f}", m_hit_points);
19+
bave::im_text("health: {:.2f}", m_current_hp);
2020
bave::im_text("dead: {}", is_dead() ? "yes" : "no");
2121
if (ImGui::Button("inflict 10 damage")) { inflict_damage(10.f); }
22-
ImGui::SliderFloat("health", &m_hit_points, 0.f, 100.f, "%.2f");
22+
ImGui::SliderFloat("health", &m_current_hp, 0.f, 100.f, "%.2f");
2323
ImGui::TreePop();
2424
}
2525
}

src/spaced/spaced/game/health.hpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ class Health {
66
public:
77
static constexpr auto hit_points_v{1.0f};
88

9-
/*implicit*/ Health(float const hit_points = hit_points_v) : m_hit_points(hit_points) {}
9+
/*implicit*/ Health(float const hit_points = hit_points_v) : m_total_hp(hit_points), m_current_hp(hit_points) {}
1010

1111
void inflict_damage(float amount);
1212
[[nodiscard]] bool is_dead() const;
1313
[[nodiscard]] float get_hit_points() const;
14+
[[nodiscard]] float get_total_hit_points() const { return m_total_hp; }
1415
void set_hit_points(float hit_points);
1516

1617
operator float() const { return get_hit_points(); }
@@ -22,6 +23,7 @@ class Health {
2223
private:
2324
void do_inspect();
2425

25-
float m_hit_points{};
26+
float m_total_hp{};
27+
float m_current_hp{};
2628
};
2729
} // namespace spaced

src/spaced/spaced/game/weapons/gun_beam.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class GunBeam final : public Weapon {
99
bave::Seconds fire_duration{2s};
1010
bave::Seconds reload_delay{1s};
1111
bave::Rgba beam_tint{bave::red_v};
12-
float dps{1.0f};
12+
float dps{10.0f};
1313
};
1414

1515
explicit GunBeam(Services const& services);

src/spaced/spaced/game/weapons/gun_kinetic.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class GunKinetic final : public Weapon {
1313
void tick(bave::Seconds dt) final;
1414

1515
Projectile::Config projectile_config{};
16-
bave::Seconds reload_delay{0.2s};
16+
bave::Seconds reload_delay{0.25s};
1717

1818
private:
1919
void do_inspect() final;

src/spaced/spaced/ui/loading_screen.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ LoadingScreen::LoadingScreen(Services const& services)
1919
m_progress_bar.size = {m_style.progress_bar.n_width * world_space.x, m_style.progress_bar.height};
2020
m_progress_bar.position.y = -0.5f * world_space.y + m_style.progress_bar.bottom_offset;
2121
auto style = m_progress_bar.get_style();
22-
style.outline_width = m_style.progress_bar.outline_width;
22+
style.padding = m_style.progress_bar.padding;
2323
m_progress_bar.set_style(style);
2424
}
2525

src/spaced/spaced/ui/progress_bar.cpp

+11-10
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,27 @@ ProgressBar::ProgressBar(Services const& services) : m_style(services.get<Styles
88

99
void ProgressBar::set_progress(float const progress) {
1010
m_progress = progress;
11-
auto const width = progress * size.x;
12-
13-
m_background.set_size(size);
14-
m_background.set_corner_ratio(0.0f);
11+
auto const width = (1.0f - progress) * size.x;
1512

13+
auto bg_size = size;
14+
bg_size.x += m_style.corner_ratio * bg_size.y;
1615
auto quad = bave::RoundedQuad{};
17-
quad.size = size;
16+
quad.size = bg_size;
17+
quad.corner_radius = m_style.corner_ratio * std::min(bg_size.x, bg_size.y);
18+
m_background.set_shape(quad);
19+
20+
quad.size = size - m_style.padding;
1821
quad.corner_radius = 0.0f;
1922
quad.size.x = width;
2023
m_fill.set_shape(quad);
2124

22-
m_background.set_tint(m_style.background);
23-
m_background.set_outline_tint(m_style.outline);
25+
m_background.tint = m_style.background;
2426
m_fill.tint = m_style.fill;
2527

2628
m_fill.tint.channels.w = 0xff;
2729

28-
m_background.set_position(position);
29-
m_fill.transform.position = position;
30-
m_fill.transform.position.x += 0.5f * (-size.x + width);
30+
m_background.transform.position = m_fill.transform.position = position;
31+
m_fill.transform.position.x += 0.5f * (size.x - width);
3132
}
3233

3334
void ProgressBar::set_style(Style style) {

src/spaced/spaced/ui/progress_bar.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ class ProgressBar : public IWidget {
2222
[[nodiscard]] auto get_style() const -> Style const& { return m_style; }
2323
void set_style(Style style);
2424

25-
[[nodiscard]] auto get_size() const -> glm::vec2 final { return m_background.get_size(); }
25+
[[nodiscard]] auto get_size() const -> glm::vec2 final { return size; }
2626
[[nodiscard]] auto get_position() const -> glm::vec2 final { return position; }
2727
void set_position(glm::vec2 position) final { this->position = position; }
2828

2929
private:
3030
Style m_style{};
3131

32-
OutlineQuad m_background{};
32+
bave::RoundedQuadShape m_background{};
3333
bave::QuadShape m_fill{};
3434

3535
float m_progress{};

src/spaced/spaced/ui/style.hpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ struct LoadingScreenStyle {
1818
float n_width{0.9f};
1919
float height{50.0f};
2020
float bottom_offset{200.0f};
21-
float outline_width{10.0f};
21+
float padding{30.0f};
2222
} progress_bar{};
2323
};
2424

@@ -35,7 +35,7 @@ struct ButtonStyle {
3535
struct ProgressBarStyle {
3636
bave::Rgba background{bave::white_v};
3737
bave::Rgba fill{bave::yellow_v};
38-
bave::Rgba outline{bave::red_v};
39-
float outline_width{5.0f};
38+
float corner_ratio{0.5f};
39+
float padding{10.0f};
4040
};
4141
} // namespace spaced::ui

src/spaced/spaced/util.cpp

+10-10
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ void util::to_json(dj::Json& out, ui::LoadingScreenStyle const& loading_screen_s
1212
progress_bar["n_width"] = loading_screen_style.progress_bar.n_width;
1313
progress_bar["height"] = loading_screen_style.progress_bar.height;
1414
progress_bar["bottom_offset"] = loading_screen_style.progress_bar.bottom_offset;
15-
progress_bar["outline_width"] = loading_screen_style.progress_bar.outline_width;
15+
progress_bar["padding"] = loading_screen_style.progress_bar.padding;
1616
}
1717

1818
void util::from_json(dj::Json const& json, ui::LoadingScreenStyle& out) {
1919
using bave::from_json;
2020
from_json(json["background_tint"], out.background_tint);
2121
auto const& spinner = json["spinner"];
2222
from_json(spinner["size"], out.spinner.size);
23-
out.spinner.spin_rate.value = spinner["spin_rate"].as<float>();
23+
out.spinner.spin_rate.value = spinner["spin_rate"].as<float>(out.spinner.spin_rate.value);
2424
auto const& progress_bar = json["progress_bar"];
25-
out.progress_bar.n_width = progress_bar["n_width"].as<float>();
26-
out.progress_bar.height = progress_bar["height"].as<float>();
27-
out.progress_bar.bottom_offset = progress_bar["bottom_offset"].as<float>();
28-
out.progress_bar.outline_width = progress_bar["outline_width"].as<float>();
25+
out.progress_bar.n_width = progress_bar["n_width"].as<float>(out.progress_bar.n_width);
26+
out.progress_bar.height = progress_bar["height"].as<float>(out.progress_bar.height);
27+
out.progress_bar.bottom_offset = progress_bar["bottom_offset"].as<float>(out.progress_bar.bottom_offset);
28+
out.progress_bar.padding = progress_bar["padding"].as<float>(out.progress_bar.padding);
2929
}
3030

3131
void util::to_json(dj::Json& out, ui::ButtonStyle::Tint const& button_tint) {
@@ -60,15 +60,15 @@ void util::from_json(dj::Json const& json, ui::ButtonStyle& out) {
6060
void util::to_json(dj::Json& out, ui::ProgressBarStyle const& progress_bar_style) {
6161
to_json(out["background"], progress_bar_style.background);
6262
to_json(out["fill"], progress_bar_style.fill);
63-
to_json(out["outline"], progress_bar_style.outline);
64-
out["outline_width"] = progress_bar_style.outline_width;
63+
out["corner_ratio"] = progress_bar_style.corner_ratio;
64+
out["padding"] = progress_bar_style.padding;
6565
}
6666

6767
void util::from_json(dj::Json const& json, ui::ProgressBarStyle& out) {
6868
from_json(json["background"], out.background);
6969
from_json(json["fill"], out.fill);
70-
from_json(json["outline"], out.outline);
71-
out.outline_width = json["outline_width"].as<float>();
70+
out.corner_ratio = json["corner_ratio"].as<float>(out.corner_ratio);
71+
out.padding = json["padding"].as<float>(out.padding);
7272
}
7373

7474
auto util::create_font_atlas_task(std::shared_ptr<bave::Font> font, std::vector<bave::TextHeight> heights) -> std::function<void()> {

0 commit comments

Comments
 (0)