Skip to content

Commit 0913308

Browse files
committed
YAGNI: Simplify asset workflow, remove reflected factories, etc. (#33)
* [WIP] Simplify Enemy spawn flow, fix race in `Resources`. * Remove `EnemySpawner`, `EnemyFactoryBuilder`. * Remove `WorldSpec`, add `AssetManifest`. Hard-code manifest in game.cpp. * Refactor asset loading into a dedicated scene. * Remove `setup()`, can be done in constructors now. * Misc cleanup.
1 parent 4557121 commit 0913308

36 files changed

+280
-488
lines changed

assets/worlds/playground.json

-24
This file was deleted.

src/spaced/spaced/game/asset_list.cpp renamed to src/spaced/spaced/assets/asset_list.cpp

+8-27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
#include <spaced/game/asset_list.hpp>
2-
#include <spaced/game/asset_loader.hpp>
1+
#include <spaced/assets/asset_list.hpp>
2+
#include <spaced/assets/asset_loader.hpp>
33
#include <spaced/services/resources.hpp>
44

55
namespace spaced {
@@ -37,31 +37,12 @@ auto AssetList::add_audio_clip(std::string uri) -> AssetList& {
3737
return *this;
3838
}
3939

40-
auto AssetList::read_world_spec(std::string_view const uri) -> WorldSpec {
41-
if (uri.empty()) { return {}; }
42-
43-
auto const json = m_loader.load_json(uri);
44-
if (!json) { return {}; }
45-
46-
auto ret = WorldSpec{};
47-
ret.name = json["name"].as_string();
48-
ret.background_tint = json["background_tint"].as_string();
49-
50-
if (auto const& player = json["player"]) {
51-
ret.player.tint = player["tint"].as_string();
52-
ret.player.exhaust_emitter = player["exhaust_emitter"].as_string();
53-
ret.player.death_emitter = player["death_emitter"].as_string();
54-
add_particle_emitter(ret.player.exhaust_emitter);
55-
add_particle_emitter(ret.player.death_emitter);
56-
}
57-
58-
for (auto const& enemy_factory : json["enemy_factories"].array_view()) {
59-
add_particle_emitter(enemy_factory["death_emitter"].as<std::string>());
60-
for (auto const& death_sfx : enemy_factory["death_sfx"].array_view()) { add_audio_clip(death_sfx.as<std::string>()); }
61-
ret.enemy_factories.push_back(enemy_factory);
62-
}
63-
64-
return ret;
40+
void AssetList::add_manifest(AssetManifest manifest) {
41+
for (auto& uri : manifest.textures) { add_texture(std::move(uri), false); }
42+
for (auto& uri : manifest.mip_mapped_textures) { add_texture(std::move(uri), true); }
43+
for (auto& uri : manifest.fonts) { add_font(std::move(uri)); }
44+
for (auto& uri : manifest.audio_clips) { add_audio_clip(std::move(uri)); }
45+
for (auto& uri : manifest.particle_emitters) { add_particle_emitter(std::move(uri)); }
6546
}
6647

6748
auto AssetList::build_task_stages() const -> std::vector<AsyncExec::Stage> {

src/spaced/spaced/game/asset_list.hpp renamed to src/spaced/spaced/assets/asset_list.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22
#include <bave/loader.hpp>
3+
#include <spaced/assets/asset_manifest.hpp>
34
#include <spaced/async_exec.hpp>
4-
#include <spaced/game/world_spec.hpp>
55
#include <spaced/services/services.hpp>
66
#include <set>
77

@@ -18,7 +18,7 @@ class AssetList {
1818
auto add_particle_emitter(std::string uri) -> AssetList&;
1919
auto add_audio_clip(std::string uri) -> AssetList&;
2020

21-
auto read_world_spec(std::string_view uri) -> WorldSpec;
21+
void add_manifest(AssetManifest manifest);
2222

2323
[[nodiscard]] auto build_task_stages() const -> std::vector<AsyncExec::Stage>;
2424

src/spaced/spaced/game/asset_loader.cpp renamed to src/spaced/spaced/assets/asset_loader.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include <bave/graphics/particle_system.hpp>
22
#include <bave/json_io.hpp>
33
#include <bave/logger.hpp>
4-
#include <spaced/game/asset_loader.hpp>
4+
#include <spaced/assets/asset_loader.hpp>
55
#include <mutex>
66

77
namespace spaced {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
#include <string>
3+
#include <vector>
4+
5+
namespace spaced {
6+
struct AssetManifest {
7+
std::vector<std::string> textures{};
8+
std::vector<std::string> mip_mapped_textures{};
9+
std::vector<std::string> fonts{};
10+
std::vector<std::string> audio_clips{};
11+
std::vector<std::string> particle_emitters{};
12+
};
13+
} // namespace spaced

src/spaced/spaced/async_exec.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
namespace spaced {
77
using namespace std::chrono_literals;
88

9-
AsyncExec::AsyncExec(std::span<Task const> tasks) {
9+
AsyncExec::AsyncExec(std::vector<Task> tasks) {
1010
if (tasks.empty()) { return; }
1111

1212
m_total = static_cast<int>(tasks.size());
1313
enqueue(tasks);
1414
}
1515

16-
AsyncExec::AsyncExec(std::span<Stage> stages) {
16+
AsyncExec::AsyncExec(std::vector<Stage> stages) {
1717
if (stages.empty()) { return; }
1818
std::move(stages.begin(), stages.end(), std::back_inserter(m_stages));
1919
m_total = std::accumulate(m_stages.begin(), m_stages.end(), 0, [](int count, auto const& tasks) { return static_cast<int>(tasks.size()) + count; });

src/spaced/spaced/async_exec.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ class AsyncExec {
1414

1515
struct Status;
1616

17-
explicit AsyncExec(std::span<Task const> tasks);
18-
explicit AsyncExec(std::span<Stage> stages);
17+
explicit AsyncExec(std::vector<Task> tasks);
18+
explicit AsyncExec(std::vector<Stage> stages);
1919

2020
auto update() -> Status;
2121

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

-54
This file was deleted.

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

-29
This file was deleted.

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace spaced {
55
class Creep : public Enemy {
66
public:
7-
explicit Creep(Services const& services, bave::NotNull<IEnemyDeathListener*> listener) : Enemy(services, listener, "Creep") {}
7+
explicit Creep(Services const& services) : Enemy(services, "Creep") {}
88

99
void tick(bave::Seconds dt, bool in_play) override;
1010

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include <bave/core/random.hpp>
2+
#include <spaced/game/enemies/creep.hpp>
3+
#include <spaced/game/enemies/creep_factory.hpp>
4+
#include <spaced/services/resources.hpp>
5+
#include <spaced/services/styles.hpp>
6+
7+
namespace spaced {
8+
using bave::random_in_range;
9+
using bave::Seconds;
10+
11+
auto CreepFactory::spawn_enemy() -> std::unique_ptr<Enemy> {
12+
auto ret = std::make_unique<Creep>(get_services());
13+
if (!m_tints.empty()) {
14+
auto const& rgbas = get_services().get<Styles>().rgbas;
15+
auto const tint_index = random_in_range(std::size_t{}, m_tints.size() - 1);
16+
ret->shape.tint = rgbas[m_tints.at(tint_index)];
17+
}
18+
ret->health = m_initial_health;
19+
return ret;
20+
}
21+
} // namespace spaced
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
#include <djson/json.hpp>
3+
#include <spaced/game/enemy_factory.hpp>
4+
5+
namespace spaced {
6+
class CreepFactory : public EnemyFactory {
7+
public:
8+
using EnemyFactory::EnemyFactory;
9+
10+
[[nodiscard]] auto spawn_enemy() -> std::unique_ptr<Enemy> final;
11+
12+
private:
13+
std::array<std::string_view, 2> m_tints{"orange", "milk"};
14+
float m_initial_health{2.0f};
15+
};
16+
} // namespace spaced

src/spaced/spaced/game/enemy.cpp

+9-12
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,38 @@ using bave::RoundedQuad;
1111
using bave::Seconds;
1212
using bave::Shader;
1313

14-
Enemy::Enemy(Services const& services, bave::NotNull<IEnemyDeathListener*> listener, std::string_view const type)
15-
: health_bar(services), m_layout(&services.get<ILayout>()), m_listener(listener), m_type(type) {
14+
Enemy::Enemy(Services const& services, std::string_view const type) : m_layout(&services.get<ILayout>()), m_health_bar(services), m_type(type) {
1615
static constexpr auto init_size_v = glm::vec2{100.0f};
1716
auto const play_area = m_layout->get_play_area();
1817
auto const y_min = play_area.rb.y + 0.5f * init_size_v.y;
1918
auto const y_max = play_area.lt.y - 0.5f * init_size_v.y - 50.0f;
2019
setup(init_size_v, random_in_range(y_min, y_max));
2120

22-
health_bar.set_style(services.get<Styles>().progress_bars["enemy"]);
21+
m_health_bar.set_style(services.get<Styles>().progress_bars["enemy"]);
2322
}
2423

2524
auto Enemy::take_damage(float const damage) -> bool {
2625
if (is_destroyed()) { return false; }
2726
health.inflict_damage(damage);
28-
if (health.is_dead()) { m_listener->on_death(EnemyDeath{.position = shape.transform.position, .points = points}); }
2927
return true;
3028
}
3129

3230
void Enemy::force_death() {
3331
health = 0.0f;
34-
health_bar.set_progress(0.0f);
35-
m_listener->on_death(EnemyDeath{.position = shape.transform.position});
32+
m_health_bar.set_progress(0.0f);
3633
}
3734

3835
void Enemy::tick(Seconds const dt, bool const /*in_play*/) {
39-
health_bar.position = shape.transform.position;
40-
health_bar.position.y += 0.5f * shape.get_shape().size.y + 20.0f;
41-
health_bar.size = {shape.get_shape().size.x, 10.0f};
42-
health_bar.set_progress(health.get_hit_points() / health.get_total_hit_points());
43-
health_bar.tick(dt);
36+
m_health_bar.position = shape.transform.position;
37+
m_health_bar.position.y += 0.5f * shape.get_shape().size.y + 20.0f;
38+
m_health_bar.size = {shape.get_shape().size.x, 10.0f};
39+
m_health_bar.set_progress(health.get_hit_points() / health.get_total_hit_points());
40+
m_health_bar.tick(dt);
4441
}
4542

4643
void Enemy::draw(Shader& shader) const {
4744
shape.draw(shader);
48-
health_bar.draw(shader);
45+
m_health_bar.draw(shader);
4946
}
5047

5148
void Enemy::setup(glm::vec2 max_size, float y_position) {

src/spaced/spaced/game/enemy.hpp

+7-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
namespace spaced {
1313
class Enemy : public IDamageable, public bave::IDrawable {
1414
public:
15-
explicit Enemy(Services const& services, bave::NotNull<IEnemyDeathListener*> listener, std::string_view type);
15+
explicit Enemy(Services const& services, std::string_view type);
1616

1717
[[nodiscard]] auto get_bounds() const -> bave::Rect<> override { return shape.get_bounds(); }
1818
auto take_damage(float damage) -> bool override;
@@ -28,22 +28,25 @@ class Enemy : public IDamageable, public bave::IDrawable {
2828
void setup(glm::vec2 max_size, float y_position);
2929

3030
[[nodiscard]] auto get_layout() const -> ILayout const& { return *m_layout; }
31-
[[nodiscard]] auto get_death_listener() const -> IEnemyDeathListener& { return *m_listener; }
3231

3332
void inspect() {
3433
if constexpr (bave::debug_v) { do_inspect(); }
3534
}
3635

3736
bave::RoundedQuadShape shape{};
38-
ui::ProgressBar health_bar;
3937
Health health{};
4038
std::int64_t points{10};
4139

40+
std::string death_emitter{"particles/explode.json"};
41+
std::vector<std::string> death_sfx{"sfx/bubble.wav"};
42+
4243
private:
4344
virtual void do_inspect();
4445

4546
bave::NotNull<ILayout const*> m_layout;
46-
bave::NotNull<IEnemyDeathListener*> m_listener;
47+
48+
ui::ProgressBar m_health_bar;
49+
4750
std::string_view m_type{};
4851
bool m_destroyed{};
4952
};

src/spaced/spaced/game/enemy_factory.cpp

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@
33

44
namespace spaced {
55
using bave::NotNull;
6+
using bave::Seconds;
67

7-
IEnemyFactory::IEnemyFactory(NotNull<Services const*> services) : m_services(services), m_audio(&services->get<IAudio>()) {}
8+
EnemyFactory::EnemyFactory(NotNull<Services const*> services) : m_services(services), m_audio(&services->get<IAudio>()) {}
89

9-
void IEnemyFactory::play_death_sfx() { m_audio->play_any_sfx(m_death_sfx); }
10+
auto EnemyFactory::tick(Seconds const dt) -> std::unique_ptr<Enemy> {
11+
if (spawn_rate <= 0s) { return {}; }
12+
13+
m_elapsed += dt;
14+
if (m_elapsed >= spawn_rate) {
15+
m_elapsed = 0s;
16+
return spawn_enemy();
17+
}
18+
return {};
19+
}
1020
} // namespace spaced

0 commit comments

Comments
 (0)