Skip to content

Multiple lives #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ include(FetchContent)
FetchContent_Declare(
bgf
GIT_REPOSITORY https://github.com/karnkaul/bgf
GIT_TAG v0.1.3
GIT_TAG v0.1.4
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/bgf"
)

Expand Down
Binary file modified assets/images/player_ship.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/player_ship_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/shield.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions assets/particles/exhaust.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"lerp": {
"tint": {
"lo": "#f48018ff",
"lo": "#36bbf5ff",
"hi": "#00000000"
},
"scale": {
Expand All @@ -58,4 +58,4 @@
"count": 200,
"respawn": true
}
}
}
4 changes: 3 additions & 1 deletion src/android/app/src/main/res/values-night/themes.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Spaced" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.Spaced" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
Expand All @@ -12,5 +12,7 @@
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style>
</resources>
4 changes: 3 additions & 1 deletion src/android/app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Spaced" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.Spaced" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
Expand All @@ -12,5 +12,7 @@
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style>
</resources>
42 changes: 42 additions & 0 deletions src/spaced/spaced/game/hud.cpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,60 @@
#include <fmt/format.h>
#include <bave/services/resources.hpp>
#include <bave/services/styles.hpp>
#include <bave/ui/button.hpp>
#include <spaced/game/hud.hpp>
#include <spaced/services/layout.hpp>

namespace spaced {
using bave::IDisplay;
using bave::Resources;
using bave::Seconds;
using bave::Services;
using bave::Shader;
using bave::Styles;
using bave::TextHeight;
using bave::Texture;

namespace ui = bave::ui;

Hud::Hud(Services const& services)
: ui::View(services), m_display(&services.get<IDisplay>()), m_layout(&services.get<Layout>()), m_styles(&services.get<Styles>()) {
create_background();
create_score(services);
create_lives_icon(services);

block_input_events = false;
render_view = m_display->get_world_view();
}

void Hud::set_lives(int const lives) {
if (lives <= 0) {
m_lives_icon.instances.clear();
return;
}

m_lives_icon.instances.resize(static_cast<std::size_t>(lives));
auto x_offset = 0.0f;
for (auto& instance : m_lives_icon.instances) {
instance.transform.position.x += x_offset;
x_offset += 2.0f * m_lives_icon.get_shape().size.x;
}
}

void Hud::on_death() {
if (m_lives_icon.instances.empty()) { return; }
m_lives_icon.instances.pop_back();
}

void Hud::set_score(std::int64_t const score) { m_score->text.set_string(fmt::format("{}", score)); }

void Hud::set_hi_score(std::int64_t const score) { m_hi_score->text.set_string(fmt::format("HI {}", score)); }

void Hud::render(Shader& shader) const {
View::render(shader);
m_lives_icon.draw(shader);
}

void Hud::create_background() {
auto background = std::make_unique<ui::OutlineQuad>();
m_background = background.get();
Expand Down Expand Up @@ -65,4 +94,17 @@ void Hud::create_score(Services const& services) {

push(std::move(text));
}

void Hud::create_lives_icon(Services const& services) {
auto quad = m_lives_icon.get_shape();
quad.size = glm::vec2{20.0f};
auto const& resources = services.get<Resources>();
if (auto const texture = resources.get<Texture>("images/player_ship_icon.png")) {
quad.size = texture->get_size();
m_lives_icon.set_texture(texture);
}
m_lives_icon.set_shape(quad);
m_lives_icon.transform.position = m_layout->hud_area.centre();
m_lives_icon.transform.position.x = m_layout->hud_area.lt.x + 100.0f;
}
} // namespace spaced
8 changes: 8 additions & 0 deletions src/spaced/spaced/game/hud.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include <bave/graphics/instanced.hpp>
#include <bave/services/styles.hpp>
#include <bave/ui/outline_quad.hpp>
#include <bave/ui/text.hpp>
Expand All @@ -11,12 +12,17 @@ class Hud : public bave::ui::View {
public:
explicit Hud(bave::Services const& services);

void set_lives(int lives);
void on_death();
void set_score(std::int64_t score);
void set_hi_score(std::int64_t score);

private:
void render(bave::Shader& shader) const final;

void create_background();
void create_score(bave::Services const& services);
void create_lives_icon(bave::Services const& services);

bave::NotNull<bave::IDisplay const*> m_display;
bave::NotNull<Layout const*> m_layout;
Expand All @@ -26,5 +32,7 @@ class Hud : public bave::ui::View {
bave::Ptr<bave::ui::OutlineQuad> m_background{};
bave::Ptr<bave::ui::Text> m_score{};
bave::Ptr<bave::ui::Text> m_hi_score{};

bave::Instanced<bave::QuadShape> m_lives_icon{};
};
} // namespace spaced
73 changes: 53 additions & 20 deletions src/spaced/spaced/game/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,20 @@ using bave::Shader;
using bave::Texture;

Player::Player(Services const& services, std::unique_ptr<IController> controller)
: m_services(&services), m_stats(&services.get<Stats>()), m_controller(std::move(controller)) {
: m_services(&services), m_stats(&services.get<Stats>()), m_controller(std::move(controller)), m_shield(services) {
auto const& layout = services.get<Layout>();
ship.transform.position.x = layout.player_x;

auto const& resources = services.get<Resources>();

if (auto const texture = services.get<Resources>().get<Texture>("images/player_ship.png")) { ship.set_texture(texture); }
ship.set_auto_size(ship_size);
if (auto const texture = services.get<Resources>().get<Texture>("images/player_ship.png")) {
ship.set_texture(texture);
ship.set_size(texture->get_size());
}

if (auto const exhaust = resources.get<ParticleEmitter>("particles/exhaust.json")) { m_exhaust = *exhaust; }
m_exhaust.set_position(get_exhaust_position());
m_exhaust.config.respawn = true;
m_exhaust.pre_warm();

if (auto const death = resources.get<ParticleEmitter>("particles/explode.json")) { m_death_source = *death; }
Expand All @@ -45,7 +48,7 @@ void Player::on_move(PointerMove const& pointer_move) { m_controller->on_move(po

void Player::on_tap(PointerTap const& pointer_tap) { m_controller->on_tap(pointer_tap); }

void Player::tick(State const& state, Seconds const dt) {
auto Player::tick(State const& state, Seconds const dt) -> bool {
if (m_death) {
m_death->tick(dt);
if (m_death->active_particles() == 0) { m_death.reset(); }
Expand All @@ -54,39 +57,41 @@ void Player::tick(State const& state, Seconds const dt) {
auto const round_state = IWeaponRound::State{
.targets = state.targets,
.muzzle_position = get_muzzle_position(),
.in_play = !health.is_dead(),
.in_play = !m_health.is_dead(),
};
m_arsenal.tick(round_state, m_controller->is_firing(), dt);

if (health.is_dead()) { return; }
m_shield.set_position(ship.transform.position);
m_shield.tick(dt);

m_exhaust.tick(dt);

if (m_health.is_dead()) { return false; }

auto const y_position = m_controller->tick(dt);
set_y(y_position);

auto const hitbox = Rect<>::from_size(hitbox_size, ship.transform.position);
for (auto const& target : state.targets) {
if (is_intersecting(target->get_bounds(), hitbox)) {
on_death(dt);
target->force_death();
return;
}
}

m_exhaust.set_position(get_exhaust_position());
m_exhaust.tick(dt);

auto ret = false;
auto const hitbox = Rect<>::from_size(hitbox_size, ship.transform.position);
for (auto const& target : state.targets) { ret |= check_hit(*target, hitbox, dt); }

for (auto const& powerup : state.powerups) {
if (is_intersecting(powerup->get_bounds(), ship.get_bounds())) {
powerup->activate(*this);
++m_stats->player.powerups_collected;
}
}

return ret;
}

void Player::draw(Shader& shader) const {
if (!health.is_dead()) {
m_exhaust.draw(shader);
m_exhaust.draw(shader);
if (!m_health.is_dead()) {
ship.draw(shader);
m_shield.draw(shader);
}
m_arsenal.draw(shader);
if (m_death) { m_death->draw(shader); }
Expand All @@ -103,14 +108,37 @@ void Player::set_controller(std::unique_ptr<IController> controller) {
m_controller = std::move(controller);
}

void Player::set_shield(Seconds const ttl) {
m_shield.ttl = ttl;
m_shield.set_position(ship.transform.position);
}

void Player::on_death(Seconds const dt) {
health = 0.0f;
m_health = 0.0f;
m_death = m_death_source;
m_death->set_position(ship.transform.position);
m_death->tick(dt);

m_exhaust.config.respawn = false;

++m_stats->player.death_count;
}

auto Player::check_hit(IDamageable& out, Rect<> const& hitbox, Seconds const dt) -> bool {
if (m_shield.is_active()) {
if (is_intersecting(out.get_bounds(), m_shield.get_bounds())) { out.force_death(); }
return false;
}

if (is_intersecting(out.get_bounds(), hitbox)) {
out.force_death();
on_death(dt);
return true;
}

return false;
}

void Player::do_inspect() {
if constexpr (bave::imgui_v) {
if (ImGui::TreeNodeEx("Controller", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
Expand All @@ -126,8 +154,13 @@ void Player::do_inspect() {
m_arsenal.get_weapon().inspect();
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Shield", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
auto ttl = m_shield.ttl.count();
if (ImGui::DragFloat("ttl", &ttl, 0.25f, 0.0f, 60.0f, "%.2f")) { m_shield.ttl = Seconds{ttl}; }
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Status", ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_DefaultOpen)) {
health.inspect();
m_health.inspect();
ImGui::TreePop();
}
}
Expand Down
15 changes: 12 additions & 3 deletions src/spaced/spaced/game/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <spaced/game/controller.hpp>
#include <spaced/game/health.hpp>
#include <spaced/game/powerup.hpp>
#include <spaced/game/shield.hpp>

namespace spaced {
struct Stats;
Expand All @@ -23,7 +24,7 @@ class Player : public bave::IDrawable {
void on_move(bave::PointerMove const& pointer_move);
void on_tap(bave::PointerTap const& pointer_tap);

void tick(State const& state, bave::Seconds dt);
auto tick(State const& state, bave::Seconds dt) -> bool;
void draw(bave::Shader& shader) const final;

void set_y(float y);
Expand All @@ -37,28 +38,36 @@ class Player : public bave::IDrawable {

void set_special_weapon(std::unique_ptr<Weapon> weapon) { m_arsenal.set_special(std::move(weapon)); }

void set_shield(bave::Seconds ttl);

[[nodiscard]] auto is_dead() const -> bool { return m_health.is_dead(); }
[[nodiscard]] auto is_idle() const -> bool { return m_exhaust.active_particles() == 0; }

void on_death(bave::Seconds dt);

void inspect() {
if constexpr (bave::debug_v) { do_inspect(); }
}

bave::Sprite ship{};
glm::vec2 ship_size{100.0f};
glm::vec2 hitbox_size{75.0f};
Health health{};

private:
auto check_hit(IDamageable& out, bave::Rect<> const& hitbox, bave::Seconds dt) -> bool;

void do_inspect();

bave::Logger m_log{"Player"};
bave::NotNull<bave::Services const*> m_services;
bave::NotNull<Stats*> m_stats;
std::unique_ptr<IController> m_controller;
bave::ParticleEmitter m_exhaust{};
Shield m_shield;

bave::ParticleEmitter m_death_source{};
std::optional<bave::ParticleEmitter> m_death{};

Arsenal m_arsenal{*m_services};
Health m_health{};
};
} // namespace spaced
Loading