Skip to content

Commit 1450f40

Browse files
authored
Add logic for music, prefs and its UI. (#45)
* Add scene music, rename `Home` to `MenuScene`. * Add `ui::Slider`. * Fix mutable `ui::View` iteration: use cached views. * Fix redundant on change callbacks in `ui::Slider`. * Add `Prefs` and its UI.
1 parent 71dcda6 commit 1450f40

File tree

21 files changed

+380
-36
lines changed

21 files changed

+380
-36
lines changed

assets/styles.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@
5555
"content_text_tint": "#231d2aff"
5656
}
5757
},
58+
"sliders": {
59+
"default": {
60+
"progress_bar": "default",
61+
"knob_diameter": 30,
62+
"knob_tint": "#9f2b68ff"
63+
}
64+
},
5865
"loading_screen": {
5966
"background_tint": "#231d2aff",
6067
"spinner": {

src/spaced/spaced/game/enemy.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,11 @@ void Enemy::force_death() {
3232
m_health_bar.set_progress(0.0f);
3333
}
3434

35-
void Enemy::tick(Seconds const dt, bool const /*in_play*/) {
35+
void Enemy::tick(Seconds const /*dt*/, bool const /*in_play*/) {
3636
m_health_bar.position = shape.transform.position;
3737
m_health_bar.position.y += 0.5f * shape.get_shape().size.y + 20.0f;
3838
m_health_bar.size = {shape.get_shape().size.x, 10.0f};
3939
m_health_bar.set_progress(health.get_hit_points() / health.get_total_hit_points());
40-
m_health_bar.tick(dt);
4140
}
4241

4342
void Enemy::draw(Shader& shader) const {

src/spaced/spaced/prefs.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#include <bave/persistor.hpp>
2+
#include <djson/json.hpp>
3+
#include <spaced/prefs.hpp>
4+
#include <spaced/services/audio.hpp>
5+
#include <spaced/services/styles.hpp>
6+
#include <spaced/ui/button.hpp>
7+
#include <spaced/ui/slider.hpp>
8+
#include <spaced/ui/text.hpp>
9+
10+
namespace spaced {
11+
using bave::App;
12+
using bave::NotNull;
13+
using bave::Persistor;
14+
15+
namespace {
16+
constexpr std::string_view uri_v{"spaced/prefs.json"};
17+
} // namespace
18+
19+
auto Prefs::load(App const& app) -> Prefs {
20+
auto ret = Prefs{};
21+
auto const persistor = Persistor{app};
22+
if (persistor.exists(uri_v)) {
23+
auto const json = persistor.read_json(uri_v);
24+
from_json(json["music_gain"], ret.music_gain);
25+
from_json(json["sfx_gain"], ret.sfx_gain);
26+
}
27+
return ret;
28+
}
29+
30+
void Prefs::save(App const& app) const {
31+
auto json = dj::Json{};
32+
to_json(json["music_gain"], music_gain);
33+
to_json(json["sfx_gain"], sfx_gain);
34+
auto const persistor = Persistor{app};
35+
persistor.write_json(uri_v, json);
36+
}
37+
38+
Prefs::View::View(NotNull<App const*> app, Services const& services)
39+
: ui::View(services), m_app(app), m_audio(&services.get<IAudio>()), m_prefs(Prefs::load(*app)) {
40+
auto const& styles = services.get<Styles>();
41+
42+
auto bg = std::make_unique<ui::OutlineQuad>();
43+
bg->set_size({400.0f, 400.0f});
44+
bg->set_outline_width(5.0f);
45+
bg->set_corner_ratio(0.1f);
46+
bg->set_tint(styles.rgbas["milk"]);
47+
push(std::move(bg));
48+
49+
auto text = std::make_unique<ui::Text>(services);
50+
text->text.set_string("music");
51+
text->set_position({0.0f, 130.0f});
52+
push(std::move(text));
53+
54+
auto slider = std::make_unique<ui::Slider>(services);
55+
slider->set_value(m_prefs.music_gain);
56+
slider->on_change = [this](float const val) {
57+
m_prefs.music_gain = val;
58+
m_audio->set_music_gain(val);
59+
};
60+
slider->set_position({0.0f, 100.0f});
61+
push(std::move(slider));
62+
63+
text = std::make_unique<ui::Text>(services);
64+
text->text.set_string("sfx");
65+
text->set_position({0.0f, 30.0f});
66+
push(std::move(text));
67+
68+
slider = std::make_unique<ui::Slider>(services);
69+
slider->set_value(m_prefs.sfx_gain);
70+
slider->on_change = [this](float const val) {
71+
m_prefs.sfx_gain = val;
72+
m_audio->set_sfx_gain(val);
73+
};
74+
slider->set_position({0.0f, 0.0f});
75+
push(std::move(slider));
76+
77+
auto button = std::make_unique<ui::Button>(services);
78+
button->set_text("close");
79+
button->callback = [this] { set_destroyed(); };
80+
button->set_position({0.0f, -100.0f});
81+
push(std::move(button));
82+
}
83+
84+
Prefs::View::~View() { m_prefs.save(*m_app); }
85+
} // namespace spaced

src/spaced/spaced/prefs.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#pragma once
2+
#include <bave/app.hpp>
3+
#include <spaced/ui/view.hpp>
4+
5+
namespace spaced {
6+
class IAudio;
7+
8+
struct Prefs {
9+
class View;
10+
11+
float music_gain{1.0f};
12+
float sfx_gain{1.0f};
13+
14+
static auto load(bave::App const& app) -> Prefs;
15+
void save(bave::App const& app) const;
16+
};
17+
18+
class Prefs::View : public ui::View {
19+
public:
20+
View(View const&) = delete;
21+
View(View&&) = delete;
22+
auto operator=(View const&) = delete;
23+
auto operator=(View&&) = delete;
24+
25+
explicit View(bave::NotNull<bave::App const*> app, Services const& services);
26+
~View() override;
27+
28+
private:
29+
bave::NotNull<bave::App const*> m_app;
30+
bave::NotNull<IAudio*> m_audio;
31+
Prefs m_prefs;
32+
};
33+
} // namespace spaced

src/spaced/spaced/scene.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void Scene::push_view(std::unique_ptr<ui::View> view) {
3838

3939
void Scene::tick_frame(Seconds const dt) {
4040
tick(dt);
41-
for (auto const& view : m_views) { view->tick(dt); }
41+
for (auto const& view : cache_views()) { view->tick(dt); }
4242
std::erase_if(m_views, [](auto const& view) { return view->is_destroyed(); });
4343
}
4444

@@ -51,11 +51,19 @@ void Scene::render_frame() const {
5151

5252
template <typename F>
5353
auto Scene::on_ui_event(F per_view) -> bool {
54-
for (auto it = m_views.rbegin(); it != m_views.rend(); ++it) {
54+
auto const cached_views = cache_views();
55+
for (auto it = cached_views.rbegin(); it != cached_views.rend(); ++it) {
5556
auto const& view = *it;
5657
per_view(*view);
5758
if (view->block_input_events) { return true; }
5859
}
5960
return false;
6061
}
62+
63+
auto Scene::cache_views() -> std::span<bave::Ptr<ui::View> const> {
64+
m_cached_views.clear();
65+
m_cached_views.reserve(m_views.size());
66+
for (auto const& view : m_views) { m_cached_views.push_back(view.get()); }
67+
return m_cached_views;
68+
}
6169
} // namespace spaced

src/spaced/spaced/scene.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class Scene : public bave::PolyPinned {
1818
void tick_frame(bave::Seconds dt);
1919
void render_frame() const;
2020

21+
[[nodiscard]] virtual auto get_music_uri() const -> std::string_view { return {}; }
22+
2123
[[nodiscard]] auto get_app() const -> bave::App& { return m_app; }
2224
[[nodiscard]] auto get_services() const -> Services const& { return m_services; }
2325

@@ -49,8 +51,11 @@ class Scene : public bave::PolyPinned {
4951
template <typename F>
5052
auto on_ui_event(F per_view) -> bool;
5153

54+
auto cache_views() -> std::span<bave::Ptr<ui::View> const>;
55+
5256
bave::App& m_app;
5357
Services const& m_services;
5458
std::vector<std::unique_ptr<ui::View>> m_views{};
59+
std::vector<bave::Ptr<ui::View>> m_cached_views{};
5560
};
5661
} // namespace spaced

src/spaced/spaced/scenes/game.cpp

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include <bave/imgui/im_text.hpp>
33
#include <spaced/assets/asset_list.hpp>
44
#include <spaced/scenes/game.hpp>
5-
#include <spaced/scenes/home.hpp>
5+
#include <spaced/scenes/menu.hpp>
66
#include <spaced/services/scene_switcher.hpp>
77
#include <spaced/services/stats.hpp>
88
#include <spaced/services/styles.hpp>
@@ -23,11 +23,13 @@ using bave::Ptr;
2323
using bave::Seconds;
2424
using bave::Shader;
2525

26-
auto Game::get_manifest() -> AssetManifest {
26+
auto GameScene::get_manifest() -> AssetManifest {
2727
return AssetManifest{
2828
.audio_clips =
2929
{
3030
"sfx/bubble.wav",
31+
"music/menu.mp3",
32+
"music/game.mp3",
3133
},
3234
.particle_emitters =
3335
{
@@ -38,7 +40,7 @@ auto Game::get_manifest() -> AssetManifest {
3840
};
3941
}
4042

41-
Game::Game(App& app, Services const& services) : Scene(app, services, "Game"), m_save(&app), m_world(&services, this) {
43+
GameScene::GameScene(App& app, Services const& services) : Scene(app, services, "Game"), m_save(&app), m_world(&services, this) {
4244
clear_colour = services.get<Styles>().rgbas["mocha"];
4345

4446
auto hud = std::make_unique<Hud>(services);
@@ -49,19 +51,19 @@ Game::Game(App& app, Services const& services) : Scene(app, services, "Game"), m
4951
++services.get<Stats>().game.play_count;
5052
}
5153

52-
void Game::on_focus(FocusChange const& focus_change) { m_world.player.on_focus(focus_change); }
54+
void GameScene::on_focus(FocusChange const& focus_change) { m_world.player.on_focus(focus_change); }
5355

54-
void Game::on_key(KeyInput const& key_input) {
56+
void GameScene::on_key(KeyInput const& key_input) {
5557
if (key_input.key == Key::eEscape && key_input.action == Action::eRelease && key_input.mods == KeyMods{}) {
56-
get_services().get<ISceneSwitcher>().switch_to<Home>();
58+
get_services().get<ISceneSwitcher>().switch_to<MenuScene>();
5759
}
5860
}
5961

60-
void Game::on_move(PointerMove const& pointer_move) { m_world.player.on_move(pointer_move); }
62+
void GameScene::on_move(PointerMove const& pointer_move) { m_world.player.on_move(pointer_move); }
6163

62-
void Game::on_tap(PointerTap const& pointer_tap) { m_world.player.on_tap(pointer_tap); }
64+
void GameScene::on_tap(PointerTap const& pointer_tap) { m_world.player.on_tap(pointer_tap); }
6365

64-
void Game::tick(Seconds const dt) {
66+
void GameScene::tick(Seconds const dt) {
6567
auto ft = bave::DeltaTime{};
6668

6769
m_world.tick(dt);
@@ -70,19 +72,19 @@ void Game::tick(Seconds const dt) {
7072
if constexpr (bave::debug_v) { inspect(dt, ft.update()); }
7173
}
7274

73-
void Game::render(Shader& shader) const { m_world.draw(shader); }
75+
void GameScene::render(Shader& shader) const { m_world.draw(shader); }
7476

75-
void Game::add_score(std::int64_t const score) {
77+
void GameScene::add_score(std::int64_t const score) {
7678
m_score += score;
7779
m_hud->set_score(m_score);
7880
update_hi_score();
7981
}
8082

81-
void Game::on_game_over() {
83+
void GameScene::on_game_over() {
8284
auto dci = ui::DialogCreateInfo{
8385
.size = {600.0f, 200.0f},
8486
.content_text = "GAME OVER",
85-
.main_button = {.text = "RESTART", .callback = [this] { get_services().get<ISceneSwitcher>().switch_to<Game>(); }},
87+
.main_button = {.text = "RESTART", .callback = [this] { get_services().get<ISceneSwitcher>().switch_to<GameScene>(); }},
8688
.second_button = {.text = "QUIT", .callback = [this] { get_app().shutdown(); }},
8789
};
8890

@@ -91,13 +93,13 @@ void Game::on_game_over() {
9193
push_view(std::move(dialog));
9294
}
9395

94-
void Game::update_hi_score() {
96+
void GameScene::update_hi_score() {
9597
if (m_score <= m_save.get_hi_score()) { return; }
9698
m_save.set_hi_score(m_score);
9799
m_hud->set_hi_score(m_save.get_hi_score());
98100
}
99101

100-
void Game::inspect(Seconds const dt, Seconds const frame_time) {
102+
void GameScene::inspect(Seconds const dt, Seconds const frame_time) {
101103
if constexpr (bave::imgui_v) {
102104
m_debug.fps.tick(dt);
103105

@@ -118,6 +120,9 @@ void Game::inspect(Seconds const dt, Seconds const frame_time) {
118120
im_text("fps: {}", m_debug.fps.fps);
119121
ImGui::SliderInt("fps limit", &m_debug.fps.limit, 5, 1000);
120122
ImGui::Checkbox("fps lock", &m_debug.fps.lock);
123+
124+
ImGui::Separator();
125+
if (ImGui::Button("reload scene")) { get_services().get<ISceneSwitcher>().switch_to<GameScene>(); }
121126
}
122127
ImGui::End();
123128

src/spaced/spaced/scenes/game.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
#include <spaced/scene.hpp>
99

1010
namespace spaced {
11-
class Game : public Scene, public IScorer {
11+
class GameScene : public Scene, public IScorer {
1212
public:
1313
static auto get_manifest() -> AssetManifest;
1414

15-
Game(bave::App& app, Services const& services);
15+
GameScene(bave::App& app, Services const& services);
1616

1717
private:
1818
void on_focus(bave::FocusChange const& focus_change) final;
@@ -23,6 +23,8 @@ class Game : public Scene, public IScorer {
2323
void tick(bave::Seconds dt) final;
2424
void render(bave::Shader& shader) const final;
2525

26+
[[nodiscard]] auto get_music_uri() const -> std::string_view final { return "music/game.mp3"; }
27+
2628
[[nodiscard]] auto get_score() const -> std::int64_t final { return m_score; }
2729
void add_score(std::int64_t score) final;
2830
void on_game_over();

src/spaced/spaced/scenes/load_assets.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include <spaced/assets/asset_list.hpp>
22
#include <spaced/scenes/game.hpp>
3-
#include <spaced/scenes/home.hpp>
43
#include <spaced/scenes/load_assets.hpp>
4+
#include <spaced/scenes/menu.hpp>
55
#include <spaced/services/resources.hpp>
66
#include <spaced/services/scene_switcher.hpp>
77
#include <spaced/util.hpp>
@@ -15,19 +15,19 @@ using bave::Shader;
1515
namespace {
1616
auto make_load_stages(Loader loader, Services const& services) -> std::vector<AsyncExec::Stage> {
1717
auto asset_list = AssetList{std::move(loader), services};
18-
asset_list.add_manifest(Game::get_manifest());
18+
asset_list.add_manifest(GameScene::get_manifest());
1919
auto ret = asset_list.build_task_stages();
2020
auto& stage = ret.emplace_back();
2121
auto& resources = services.get<Resources>();
22-
stage.push_back(util::create_font_atlas_task(resources.main_font, Home::get_text_heights()));
22+
stage.push_back(util::create_font_atlas_task(resources.main_font, MenuScene::get_text_heights()));
2323
return ret;
2424
}
2525
} // namespace
2626

2727
LoadAssets::LoadAssets(App& app, Services const& services)
2828
: Scene(app, services, "LoadAssets"), m_loading_screen(services), m_load(make_load_stages(make_loader(), services)) {}
2929

30-
void LoadAssets::on_loaded() { get_services().get<ISceneSwitcher>().switch_to<Home>(); }
30+
void LoadAssets::on_loaded() { get_services().get<ISceneSwitcher>().switch_to<MenuScene>(); }
3131

3232
void LoadAssets::tick(Seconds const dt) {
3333
auto const load_status = m_load.update();

0 commit comments

Comments
 (0)