Skip to content

Commit 9975f47

Browse files
authored
Add Player death foundation. (#31)
1 parent 9eb333a commit 9975f47

File tree

13 files changed

+60
-13
lines changed

13 files changed

+60
-13
lines changed

assets/worlds/playground.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"background_tint": "mocha",
44
"player": {
55
"tint": "black",
6-
"exhaust_emitter": "particles/exhaust.json"
6+
"exhaust_emitter": "particles/exhaust.json",
7+
"death_emitter": "particles/explode.json"
78
},
89
"enemy_factories": [
910
{

src/spaced/spaced/game/asset_list.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ auto AssetList::read_world_spec(std::string_view const uri) -> WorldSpec {
4444
if (auto const& player = json["player"]) {
4545
ret.player.tint = player["tint"].as_string();
4646
ret.player.exhaust_emitter = player["exhaust_emitter"].as_string();
47+
ret.player.death_emitter = player["death_emitter"].as_string();
4748
add_particle_emitter(ret.player.exhaust_emitter);
49+
add_particle_emitter(ret.player.death_emitter);
4850
}
4951

5052
for (auto const& enemy_factory : json["enemy_factories"].array_view()) {

src/spaced/spaced/game/damageable.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ class IDamageable : public bave::Polymorphic {
77
public:
88
[[nodiscard]] virtual auto get_bounds() const -> bave::Rect<> = 0;
99
virtual auto take_damage(float damage) -> bool = 0;
10+
virtual void force_death() = 0;
1011
};
1112
} // namespace spaced

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
namespace spaced {
44
using bave::Seconds;
55

6-
void Creep::tick(Seconds const dt) {
7-
Enemy::tick(dt);
6+
void Creep::tick(Seconds const dt, bool const in_play) {
7+
Enemy::tick(dt, in_play);
8+
if (!in_play) { return; }
9+
810
shape.transform.position.x -= x_speed * dt.count();
911
if (shape.transform.position.x < -0.5f * (get_layout().get_world_space().x + shape.get_shape().size.x)) { set_destroyed(); }
1012
}

src/spaced/spaced/game/enemies/creep.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class Creep : public Enemy {
66
public:
77
explicit Creep(Services const& services, bave::NotNull<IEnemyDeathListener*> listener) : Enemy(services, listener, "Creep") {}
88

9-
void tick(bave::Seconds dt) override;
9+
void tick(bave::Seconds dt, bool in_play) override;
1010

1111
float x_speed{100.0f};
1212
};

src/spaced/spaced/game/enemy.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Enemy::Enemy(Services const& services, bave::NotNull<IEnemyDeathListener*> liste
1616
static constexpr auto init_size_v = glm::vec2{100.0f};
1717
auto const play_area = m_layout->get_play_area();
1818
auto const y_min = play_area.rb.y + 0.5f * init_size_v.y;
19-
auto const y_max = play_area.lt.y - 0.5f * init_size_v.y;
19+
auto const y_max = play_area.lt.y - 0.5f * init_size_v.y - 50.0f;
2020
setup(init_size_v, random_in_range(y_min, y_max));
2121

2222
health_bar.set_style(services.get<Styles>().progress_bars["enemy"]);
@@ -29,7 +29,13 @@ auto Enemy::take_damage(float const damage) -> bool {
2929
return true;
3030
}
3131

32-
void Enemy::tick(Seconds const dt) {
32+
void Enemy::force_death() {
33+
health = 0.0f;
34+
health_bar.set_progress(0.0f);
35+
m_listener->on_death(EnemyDeath{.position = shape.transform.position});
36+
}
37+
38+
void Enemy::tick(Seconds const dt, bool const /*in_play*/) {
3339
health_bar.position = shape.transform.position;
3440
health_bar.position.y += 0.5f * shape.get_shape().size.y + 20.0f;
3541
health_bar.size = {shape.get_shape().size.x, 10.0f};

src/spaced/spaced/game/enemy.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ class Enemy : public IDamageable, public bave::IDrawable {
1616

1717
[[nodiscard]] auto get_bounds() const -> bave::Rect<> override { return shape.get_bounds(); }
1818
auto take_damage(float damage) -> bool override;
19+
void force_death() override;
1920

2021
[[nodiscard]] auto is_dead() const -> bool { return health.is_dead(); }
2122
[[nodiscard]] auto is_destroyed() const -> bool { return is_dead() || m_destroyed; }
2223
void set_destroyed() { m_destroyed = true; }
2324

24-
virtual void tick(bave::Seconds dt);
25+
virtual void tick(bave::Seconds dt, bool in_play);
2526
void draw(bave::Shader& shader) const override;
2627

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

src/spaced/spaced/game/enemy_spawner.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ EnemySpawner::EnemySpawner(std::unique_ptr<IEnemyFactory> factory) : m_factory(s
1414
if (!m_factory) { throw std::runtime_error{"Null EnemyFactory passed to EnemySpawner"}; }
1515
}
1616

17-
void EnemySpawner::tick(Seconds const dt) {
17+
void EnemySpawner::tick(Seconds const dt, bool const in_play) {
1818
if (m_factory->tick(dt)) { spawn(); }
1919

2020
for (auto const& enemy : m_enemies) {
21-
enemy->tick(dt);
21+
enemy->tick(dt, in_play);
2222
if (enemy->is_dead()) { explode_at(enemy->get_bounds().centre()); }
2323
}
2424

src/spaced/spaced/game/enemy_spawner.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class EnemySpawner {
77
public:
88
explicit EnemySpawner(std::unique_ptr<IEnemyFactory> factory);
99

10-
void tick(bave::Seconds dt);
10+
void tick(bave::Seconds dt, bool in_play);
1111
void draw(bave::Shader& shader) const;
1212

1313
void spawn() { m_enemies.push_back(m_factory->spawn_enemy()); }

src/spaced/spaced/game/player.cpp

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,24 @@ void Player::on_move(PointerMove const& pointer_move) { m_controller->on_move(po
2525
void Player::on_tap(PointerTap const& pointer_tap) { m_controller->on_tap(pointer_tap); }
2626

2727
void Player::tick(State const& state, Seconds const dt) {
28+
if (m_death) {
29+
m_death->tick(dt);
30+
if (m_death->active_particles() == 0) { m_death.reset(); }
31+
}
32+
33+
if (health.is_dead()) { return; }
34+
2835
auto const y_position = m_controller->tick(dt);
2936
set_y(y_position);
3037

38+
for (auto const& target : state.targets) {
39+
if (is_intersecting(target->get_bounds(), ship.get_bounds())) {
40+
on_death();
41+
target->force_death();
42+
return;
43+
}
44+
}
45+
3146
auto const round_state = IWeaponRound::State{.targets = state.targets, .muzzle_position = get_muzzle_position()};
3247
m_arsenal.tick(round_state, m_controller->is_firing(), dt);
3348

@@ -40,18 +55,24 @@ void Player::tick(State const& state, Seconds const dt) {
4055
}
4156

4257
void Player::draw(Shader& shader) const {
43-
m_exhaust.draw(shader);
44-
ship.draw(shader);
58+
if (!health.is_dead()) {
59+
m_exhaust.draw(shader);
60+
ship.draw(shader);
61+
}
4562
m_arsenal.draw(shader);
63+
if (m_death) { m_death->draw(shader); }
4664
}
4765

4866
void Player::setup(WorldSpec::Player const& spec) {
4967
auto const& rgbas = m_services->get<Styles>().rgbas;
5068
auto const& resources = m_services->get<Resources>();
5169
ship.tint = rgbas[spec.tint];
70+
5271
if (auto const exhaust = resources.get<ParticleEmitter>(spec.exhaust_emitter)) { m_exhaust = *exhaust; }
5372
m_exhaust.set_position(get_exhaust_position());
5473
m_exhaust.pre_warm();
74+
75+
if (auto const death = resources.get<ParticleEmitter>(spec.death_emitter)) { m_death_source = *death; }
5576
}
5677

5778
void Player::set_y(float const y) { ship.transform.position.y = y; }
@@ -74,6 +95,13 @@ void Player::setup_ship() {
7495
ship.set_shape(rounded_quad);
7596
}
7697

98+
void Player::on_death() {
99+
health = 0.0f;
100+
m_death = m_death_source;
101+
m_death->set_position(ship.transform.position);
102+
m_death->config.respawn = false;
103+
}
104+
77105
void Player::do_inspect() {
78106
if constexpr (bave::imgui_v) {
79107
if (ImGui::TreeNodeEx("Controller", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {

src/spaced/spaced/game/player.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,16 @@ class Player : public bave::IDrawable {
4747

4848
private:
4949
void setup_ship();
50+
void on_death();
5051

5152
void do_inspect();
5253

5354
bave::Logger m_log{"Player"};
5455
bave::NotNull<Services const*> m_services;
5556
std::unique_ptr<IController> m_controller;
5657
bave::ParticleEmitter m_exhaust{};
58+
bave::ParticleEmitter m_death_source{};
59+
std::optional<bave::ParticleEmitter> m_death{};
5760

5861
Arsenal m_arsenal{*m_services};
5962
};

src/spaced/spaced/game/world.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ void World::on_death(EnemyDeath const& death) {
4343
}
4444

4545
void World::tick(Seconds const dt) {
46+
bool const in_play = !player.health.is_dead();
47+
4648
m_targets.clear();
4749
for (auto& spawner : m_enemy_spawners) {
48-
spawner.tick(dt);
50+
spawner.tick(dt, in_play);
4951
spawner.append_targets(m_targets);
5052
}
5153

src/spaced/spaced/game/world_spec.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ struct WorldSpec {
77
struct Player {
88
std::string tint{};
99
std::string exhaust_emitter{};
10+
std::string death_emitter{};
1011
};
1112

1213
std::string name{};

0 commit comments

Comments
 (0)