Skip to content

Commit a138a64

Browse files
authored
Fixes, balance, polish. (#53)
* WIP bgf v0.1.8. * Fix Trooper oscillation, pause on focus loss. * Add player death and game over SFX. * bgf update, balance gameplay. * Fixup Hud. * Finalize bgf v0.1.8. Add interact SFX. * Fixup text align, add menu fade-in, display version.
1 parent 8eb8837 commit a138a64

File tree

21 files changed

+268
-53
lines changed

21 files changed

+268
-53
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: ci
2-
on: [push]
2+
on:
3+
push:
4+
branches:
5+
- '*'
36
jobs:
47
format-check:
58
runs-on: ubuntu-latest
@@ -10,17 +13,15 @@ jobs:
1013
- name: check diff
1114
run: .github/format_check_diff.sh
1215
build-linux:
13-
runs-on: ubuntu-latest
16+
runs-on: ubuntu-24.04
1417
steps:
1518
- uses: actions/checkout@v4
1619
- name: init
17-
run: |
18-
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
19-
sudo apt update -yqq && sudo apt install -yqq ninja-build xorg-dev g++-13
20+
run: sudo apt update -yqq && sudo apt install -yqq ninja-build xorg-dev
2021
- name: configure gcc
21-
run: cmake -S . --preset=default -B build -DBAVE_BUILD_SHADERS=OFF -DBAVE_USE_FREETYPE=OFF -DCAPO_USE_OPENAL=OFF -DCMAKE_CXX_COMPILER=g++-13
22+
run: cmake -S . --preset=default -B build -DBAVE_BUILD_SHADERS=OFF -DBAVE_USE_FREETYPE=OFF -DCAPO_USE_OPENAL=OFF
2223
- name: configure clang
23-
run: cmake -S . --preset=default -B clang -DBAVE_BUILD_SHADERS=OFF -DBAVE_USE_FREETYPE=OFF -DCAPO_USE_OPENAL=OFF -DCMAKE_CXX_COMPILER=clang++-15
24+
run: cmake -S . --preset=ninja-clang -B clang -DBAVE_BUILD_SHADERS=OFF -DBAVE_USE_FREETYPE=OFF -DCAPO_USE_OPENAL=OFF
2425
- name: build gcc
2526
run: cmake --build build --config=Release -- -d explain
2627
- name: build clang

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ bave*.log
1616
/spaced
1717
/notes.txt
1818
/local_store
19+
/massif.*

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ include(FetchContent)
1111
FetchContent_Declare(
1212
bgf
1313
GIT_REPOSITORY https://github.com/karnkaul/bgf
14-
GIT_TAG v0.1.7
14+
GIT_TAG v0.1.8
1515
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/bgf"
1616
)
1717

assets/images/ship_trooper.png

316 Bytes
Loading

assets/sfx/crunch.wav

58.5 KB
Binary file not shown.

assets/sfx/interact.wav

23.3 KB
Binary file not shown.

assets/sfx/lose.wav

56.5 KB
Binary file not shown.

assets/styles.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
},
3535
"progress_bars": {
3636
"default": {
37-
"background": "#9f2b68ff",
38-
"fill": "milk",
37+
"background": "#db7a7eff",
38+
"fill": "#9f2b68ff",
3939
"corner_ratio": 0.5,
4040
"padding": 20
4141
},
@@ -81,4 +81,4 @@
8181
"padding": 20
8282
}
8383
}
84-
}
84+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ Creep::Creep(bave::Services const& services) : Enemy(services, "Creep") {
1313

1414
health = 1.0f;
1515
speed = 100.0f;
16+
points = 10;
1617
}
1718
} // namespace spaced::enemy

src/spaced/spaced/game/enemies/gunner.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ Gunner::Gunner(bave::Services const& services, bave::NotNull<GunKinetic*> gun) :
1313

1414
health = 2.0f;
1515
speed = 120.0f;
16+
points = 20;
1617
}
1718
} // namespace spaced::enemy

src/spaced/spaced/game/enemies/trooper.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ using bave::Seconds;
1010
using bave::Services;
1111
using bave::Texture;
1212

13-
namespace {
14-
constexpr auto at_end(float const y, float const y_min, float const y_max) { return y_min > y || y > y_max; }
15-
} // namespace
16-
1713
Trooper::Trooper(Services const& services, NotNull<GunKinetic*> gun) : GunnerBase(services, gun, "Trooper") {
1814
if (auto texture = services.get<Resources>().get<Texture>("images/ship_trooper.png")) {
1915
m_sprite.set_size(texture->get_size());
@@ -24,6 +20,7 @@ Trooper::Trooper(Services const& services, NotNull<GunKinetic*> gun) : GunnerBas
2420

2521
health = 3.0f;
2622
speed = 150.0f;
23+
points = 30;
2724
}
2825

2926
void Trooper::do_tick(Seconds const dt) {
@@ -32,6 +29,10 @@ void Trooper::do_tick(Seconds const dt) {
3229
m_sprite.transform.position.y += m_direction * m_y_speed * dt.count();
3330
auto const y_min = get_layout().play_area.rb.y + m_sprite.get_size().y;
3431
auto const y_max = get_layout().play_area.lt.y - m_sprite.get_size().y;
35-
if (at_end(m_sprite.transform.position.y, y_min, y_max)) { m_direction = -m_direction; }
32+
if (m_sprite.transform.position.y < y_min) {
33+
m_direction = 1.0f;
34+
} else if (m_sprite.transform.position.y > y_max) {
35+
m_direction = -1.0f;
36+
}
3637
}
3738
} // namespace spaced::enemy

src/spaced/spaced/game/hud.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ using bave::Texture;
1818

1919
namespace ui = bave::ui;
2020

21-
Hud::Hud(Services const& services) : ui::View(services), m_layout(&services.get<Layout>()), m_styles(&services.get<Styles>()) {
21+
Hud::Hud(Services const& services) : m_layout(&services.get<Layout>()), m_styles(&services.get<Styles>()) {
2222
create_background();
2323
create_score(services);
2424
create_lives(services);
@@ -76,7 +76,7 @@ void Hud::create_score(Services const& services) {
7676
m_hi_score = text.get();
7777
text->text.transform.position.x = m_layout->hud_area.rb.x - 50.0f;
7878
text->text.transform.position.y -= 0.5f * m_text_bounds_size.y;
79-
text->text.set_align(Text::Align::eLeft);
79+
text->text.set_align(Text::Align::eRight);
8080
set_hi_score(0);
8181
push(std::move(text));
8282
}
@@ -98,7 +98,7 @@ void Hud::create_lives(Services const& services) {
9898
auto text = make_text(services);
9999
text->text.transform.position.y -= 0.5f * m_text_bounds_size.y;
100100
text->text.transform.position.x = m_lives_icon->get_position().x + m_lives_icon->get_size().x;
101-
text->text.set_align(Text::Align::eRight);
101+
text->text.set_align(Text::Align::eLeft);
102102
text->text.set_string("0");
103103
m_lives_count = text.get();
104104
push(std::move(text));

src/spaced/spaced/game/player.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <spaced/game/weapons/gun_beam.hpp>
1313

1414
namespace spaced {
15+
using bave::IAudio;
1516
using bave::ParticleEmitter;
1617
using bave::PointerMove;
1718
using bave::PointerTap;
@@ -24,8 +25,8 @@ using bave::Styles;
2425
using bave::Texture;
2526

2627
Player::Player(Services const& services, std::unique_ptr<IController> controller)
27-
: m_services(&services), m_stats(&services.get<Stats>()), m_controller(std::move(controller)), m_on_1up(&services.get<GameSignals>().one_up),
28-
m_shield(services), m_arsenal(services) {
28+
: m_services(&services), m_audio(&services.get<IAudio>()), m_stats(&services.get<Stats>()), m_controller(std::move(controller)),
29+
m_on_1up(&services.get<GameSignals>().one_up), m_shield(services), m_arsenal(services) {
2930
auto const& layout = services.get<Layout>();
3031
ship.transform.position.x = layout.player_x;
3132

@@ -134,6 +135,8 @@ void Player::set_shield(Seconds const ttl) {
134135
void Player::one_up() { m_on_1up->dispatch(); }
135136

136137
void Player::on_death(Seconds const dt) {
138+
m_audio->play_sfx("sfx/crunch.wav");
139+
137140
m_health = 0.0f;
138141
m_death = m_death_source;
139142
m_death->set_position(ship.transform.position);
@@ -176,7 +179,7 @@ void Player::do_inspect() {
176179
}
177180
if (ImGui::TreeNodeEx("Shield", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
178181
auto ttl = m_shield.ttl.count();
179-
if (ImGui::DragFloat("ttl", &ttl, 0.25f, 0.0f, 60.0f, "%.2f")) { m_shield.ttl = Seconds{ttl}; }
182+
if (ImGui::DragFloat("ttl", &ttl, 0.25f, 0.0f, 600.0f, "%.2f")) { m_shield.ttl = Seconds{ttl}; }
180183
ImGui::TreePop();
181184
}
182185
if (ImGui::TreeNodeEx("Status", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {

src/spaced/spaced/game/player.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class Player : public IDamageable, public bave::IDrawable {
6666

6767
bave::Logger m_log{"Player"};
6868
bave::NotNull<bave::Services const*> m_services;
69+
bave::NotNull<bave::IAudio*> m_audio;
6970
bave::NotNull<Stats*> m_stats;
7071
std::unique_ptr<IController> m_controller;
7172
bave::NotNull<Sig1up*> m_on_1up;

src/spaced/spaced/prefs.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ void Prefs::save(App const& app) const {
4242
persistor.write_json(uri_v, json);
4343
}
4444

45-
Prefs::View::View(NotNull<App const*> app, Services const& services)
46-
: ui::View(services), m_app(app), m_audio(&services.get<IAudio>()), m_prefs(Prefs::load(*app)) {
45+
Prefs::View::View(NotNull<App const*> app, Services const& services) : m_app(app), m_audio(&services.get<IAudio>()), m_prefs(Prefs::load(*app)) {
4746
auto const& styles = services.get<Styles>();
4847

4948
auto bg = std::make_unique<ui::OutlineQuad>();

src/spaced/spaced/scenes/endless.cpp

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <bave/core/fixed_string.hpp>
12
#include <bave/core/random.hpp>
23
#include <bave/services/resources.hpp>
34
#include <spaced/game/enemies/creep.hpp>
@@ -10,6 +11,7 @@
1011

1112
namespace spaced {
1213
using bave::App;
14+
using bave::FixedString;
1315
using bave::NotNull;
1416
using bave::random_in_range;
1517
using bave::Resources;
@@ -39,12 +41,39 @@ struct GunnerSpawnerT : SpawnTimer<Enemy> {
3941

4042
using GunnerSpawner = GunnerSpawnerT<enemy::Gunner>;
4143
using TrooperSpawner = GunnerSpawnerT<enemy::Trooper>;
44+
45+
auto roll_check(int const space) {
46+
if (space <= 0) { return true; }
47+
return random_in_range(0, space) == 0;
48+
}
49+
50+
auto get_creep_spawn_rate(Seconds const elapsed) -> Seconds {
51+
static constexpr auto base_rate_v = 0.9s;
52+
if (elapsed < 50s) { return base_rate_v; }
53+
if (elapsed < 100s) { return 0.75f * base_rate_v; }
54+
return 0.6f * base_rate_v;
55+
}
56+
57+
auto get_gunner_spawn_rate(Seconds const elapsed) -> Seconds {
58+
static constexpr auto base_rate_v = 9.3s;
59+
if (elapsed < 40s) { return base_rate_v; }
60+
if (elapsed < 80s) { return 0.75f * base_rate_v; }
61+
if (elapsed < 120s) { return 0.5f * base_rate_v; }
62+
return 0.25f * base_rate_v;
63+
}
64+
65+
auto get_trooper_spawn_rate(Seconds const elapsed) -> Seconds {
66+
static constexpr auto base_rate_v = 29.4s;
67+
if (elapsed < 60s) { return base_rate_v; }
68+
if (elapsed < 100s) { return 0.5f * base_rate_v; }
69+
if (elapsed < 150s) { return 0.25f * base_rate_v; }
70+
if (elapsed < 180s) { return 0.125f * base_rate_v; }
71+
return 0.0625f * base_rate_v;
72+
}
4273
} // namespace
4374

4475
EndlessScene::EndlessScene(App& app, Services const& services) : GameScene(app, services), m_enemy_gun(services) {
45-
m_on_player_scored = services.get<GameSignals>().player_scored.connect([this](Enemy const& e) {
46-
if (random_in_range(0, 10) < 3) { debug_spawn_powerup(e.get_position()); }
47-
});
76+
m_on_player_scored = services.get<GameSignals>().player_scored.connect([this](Enemy const& e) { on_player_scored(e); });
4877
}
4978

5079
void EndlessScene::on_loaded() {
@@ -59,29 +88,90 @@ void EndlessScene::on_loaded() {
5988
m_enemy_gun.projectile_config.x_speed = -m_enemy_gun.projectile_config.x_speed;
6089
m_enemy_gun.projectile_config.x_scale = -1.0f;
6190

62-
m_spawn_timers.push_back(std::make_unique<CreepSpawner>(&get_services(), 1s));
63-
m_spawn_timers.push_back(std::make_unique<GunnerSpawner>(&get_services(), &m_enemy_gun, 2s));
64-
m_spawn_timers.push_back(std::make_unique<TrooperSpawner>(&get_services(), &m_enemy_gun, 5s));
91+
on_start();
92+
}
93+
94+
void EndlessScene::on_start() {
95+
m_elapsed = 0s;
96+
m_spawn_timers.clear();
97+
m_spawner.creep = m_spawn_timers.emplace_back(make_creep_spawner()).get();
98+
m_spawner.gunner = m_spawn_timers.emplace_back(make_gunner_spawner()).get();
99+
m_spawner.trooper = m_spawn_timers.emplace_back(make_trooper_spawner()).get();
65100
}
66101

67102
void EndlessScene::tick(Seconds const dt) {
68103
GameScene::tick(dt);
69104

70105
if (is_game_over()) { return; }
71106

107+
m_elapsed += dt;
108+
m_since_powerup += dt;
109+
110+
ramp_difficulty();
111+
72112
for (auto const& spawn_timer : m_spawn_timers) {
73113
if (auto enemy = spawn_timer->tick(dt)) { push_enemy(std::move(enemy)); }
74114
}
75115
}
76116

77-
void EndlessScene::debug_spawn_powerup(glm::vec2 const position) {
117+
void EndlessScene::ramp_difficulty() {
118+
m_spawner.creep->spawn_rate = get_creep_spawn_rate(m_elapsed);
119+
m_spawner.gunner->spawn_rate = get_gunner_spawn_rate(m_elapsed);
120+
m_spawner.trooper->spawn_rate = get_trooper_spawn_rate(m_elapsed);
121+
}
122+
123+
void EndlessScene::on_player_scored(Enemy const& enemy) {
124+
if (m_since_powerup < 5s) { return; }
125+
126+
if (!roll_check(30)) { return; }
127+
78128
auto powerup = std::unique_ptr<Powerup>{};
79-
if (random_in_range(0, 1) == 0) {
80-
powerup = std::make_unique<powerup::Beam>(get_services());
129+
if (roll_check(20)) {
130+
powerup = make_1up_powerup();
81131
} else {
82-
powerup = std::make_unique<powerup::OneUp>(get_services());
132+
powerup = make_beam_powerup();
83133
}
84-
powerup->shape.transform.position = position;
134+
135+
if (!powerup) { return; }
136+
137+
powerup->shape.transform.position = enemy.get_position();
85138
push_powerup(std::move(powerup));
139+
m_since_powerup = 0s;
140+
}
141+
142+
auto EndlessScene::make_beam_powerup() const -> std::unique_ptr<Powerup> { return std::make_unique<powerup::Beam>(get_services()); }
143+
144+
auto EndlessScene::make_1up_powerup() const -> std::unique_ptr<Powerup> { return std::make_unique<powerup::OneUp>(get_services()); }
145+
146+
auto EndlessScene::make_creep_spawner() const -> std::unique_ptr<SpawnTimer<Enemy>> {
147+
return std::make_unique<CreepSpawner>(&get_services(), get_creep_spawn_rate(0s));
148+
}
149+
150+
auto EndlessScene::make_gunner_spawner() -> std::unique_ptr<SpawnTimer<Enemy>> {
151+
return std::make_unique<GunnerSpawner>(&get_services(), &m_enemy_gun, get_gunner_spawn_rate(0s));
152+
}
153+
154+
auto EndlessScene::make_trooper_spawner() -> std::unique_ptr<SpawnTimer<Enemy>> {
155+
return std::make_unique<TrooperSpawner>(&get_services(), &m_enemy_gun, get_trooper_spawn_rate(0s));
156+
}
157+
158+
void EndlessScene::do_inspect(Seconds const /*dt*/) {
159+
if constexpr (bave::imgui_v) {
160+
auto elapsed = m_elapsed.count();
161+
if (ImGui::DragFloat("elapsed", &elapsed, 1.0f, 0.0f, 2000.0f)) { m_elapsed = Seconds{elapsed}; }
162+
if (ImGui::TreeNode("spawn timers")) {
163+
for (std::size_t i = 0; i < m_spawn_timers.size(); ++i) {
164+
if (ImGui::TreeNode(FixedString{"[{}]", i}.c_str())) {
165+
auto& spawn_timer = *m_spawn_timers.at(i);
166+
auto f = spawn_timer.spawn_rate.count();
167+
if (ImGui::DragFloat("period", &f, 0.25f, 0.25f, 20.0f)) { spawn_timer.spawn_rate = Seconds{f}; }
168+
f = spawn_timer.elapsed.count();
169+
if (ImGui::DragFloat("elapsed", &f, 0.25f, 0.25f, 20.0f)) { spawn_timer.elapsed = Seconds{f}; }
170+
ImGui::TreePop();
171+
}
172+
}
173+
ImGui::TreePop();
174+
}
175+
}
86176
}
87177
} // namespace spaced

src/spaced/spaced/scenes/endless.hpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,33 @@ class EndlessScene : public GameScene {
1111

1212
private:
1313
void on_loaded() final;
14-
1514
void tick(bave::Seconds dt) final;
1615

17-
void debug_spawn_powerup(glm::vec2 position);
16+
void on_start() final;
17+
18+
void ramp_difficulty();
19+
void on_player_scored(Enemy const& enemy);
20+
21+
[[nodiscard]] auto make_beam_powerup() const -> std::unique_ptr<Powerup>;
22+
[[nodiscard]] auto make_1up_powerup() const -> std::unique_ptr<Powerup>;
23+
24+
[[nodiscard]] auto make_creep_spawner() const -> std::unique_ptr<SpawnTimer<Enemy>>;
25+
[[nodiscard]] auto make_gunner_spawner() -> std::unique_ptr<SpawnTimer<Enemy>>;
26+
[[nodiscard]] auto make_trooper_spawner() -> std::unique_ptr<SpawnTimer<Enemy>>;
27+
28+
void do_inspect(bave::Seconds dt) final;
1829

1930
SignalHandle m_on_player_scored{};
2031
GunKinetic m_enemy_gun;
2132

33+
bave::Seconds m_elapsed{};
34+
bave::Seconds m_since_powerup{};
2235
std::vector<std::unique_ptr<SpawnTimer<Enemy>>> m_spawn_timers{};
36+
37+
struct {
38+
bave::Ptr<SpawnTimer<Enemy>> creep{};
39+
bave::Ptr<SpawnTimer<Enemy>> gunner{};
40+
bave::Ptr<SpawnTimer<Enemy>> trooper{};
41+
} m_spawner{};
2342
};
2443
} // namespace spaced

0 commit comments

Comments
 (0)