Skip to content

Add foam particles to player and for enemy explosions. #21

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 6 commits into from
Mar 11, 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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
**/.vscode/*
build/*
out/*
ext/*
/ext
screenshots
.cache
.DS_Store
Expand Down
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(
bave
GIT_REPOSITORY https://github.com/karnkaul/bave
GIT_TAG 5002fc271533a3268592c47c5bc8653ae094dc6b # v0.4.6
GIT_TAG 2b1b7ce206a031aa2c2c957f67c8bf92cc031b10 # v0.4.6
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ext/bave"
)

Expand Down
Binary file added assets/images/foam_bubble.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion assets/styles.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"grey": "#535151ff",
"mocha": "#6f5a48ff",
"milk": "#e5cdaeff",
"ice": "#0xd6dbe1e1"
"ice": "#0xd6dbe1e1",
"orange": "#f75c03ff"
},
"buttons": {
"default": {
Expand Down
1 change: 0 additions & 1 deletion ext/bave
Submodule bave deleted from 80bcd8
48 changes: 48 additions & 0 deletions src/spaced/spaced/game/asset_loader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#include <bave/logger.hpp>
#include <spaced/game/asset_loader.hpp>
#include <mutex>

namespace spaced {
using bave::Loader;
using bave::Logger;
using bave::NotNull;

struct AssetLoader::Impl {
Logger log{"AssetLoader"};
Loader loader;
NotNull<Resources*> resources;
std::mutex mutex{};
};

AssetLoader::AssetLoader(Loader loader, NotNull<Resources*> resources) : m_impl(new Impl{.loader = std::move(loader), .resources = resources}) {}

auto AssetLoader::make_load_font(std::string uri, bool reload) -> LoadTask {
auto const load = [](Loader const& loader, std::string_view const uri) { return loader.load_font(uri); };
return make_load_task(std::move(uri), reload, load);
}

auto AssetLoader::make_load_texture(std::string uri, bool mip_map, bool reload) -> LoadTask {
auto const load = [mip_map](Loader const& loader, std::string_view const uri) { return loader.load_texture(uri, mip_map); };
return make_load_task(std::move(uri), reload, load);
}

auto AssetLoader::make_load_texture_atlas(std::string uri, bool mip_map, bool reload) -> LoadTask {
auto const load = [mip_map](Loader const& loader, std::string_view const uri) { return loader.load_texture_atlas(uri, mip_map); };
return make_load_task(std::move(uri), reload, load);
}

template <typename FuncT>
auto AssetLoader::make_load_task(std::string uri, bool reload, FuncT load) const -> LoadTask {
return [impl = m_impl, uri = std::move(uri), reload, load] {
auto lock = std::unique_lock{impl->mutex};
if (!reload && impl->resources->contains(uri)) { return; }

lock.unlock();
auto asset = load(impl->loader, uri);
if (!asset) { return; }

lock.lock();
impl->resources->add(uri, std::move(asset));
};
}
} // namespace spaced
26 changes: 26 additions & 0 deletions src/spaced/spaced/game/asset_loader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once
#include <bave/core/ptr.hpp>
#include <bave/loader.hpp>
#include <spaced/services/resources.hpp>
#include <functional>
#include <memory>

namespace spaced {
class AssetLoader {
public:
using LoadTask = std::function<void()>;

explicit AssetLoader(bave::Loader loader, bave::NotNull<Resources*> resources);

[[nodiscard]] auto make_load_font(std::string uri, bool reload = false) -> LoadTask;
[[nodiscard]] auto make_load_texture(std::string uri, bool mip_map = false, bool reload = false) -> LoadTask;
[[nodiscard]] auto make_load_texture_atlas(std::string uri, bool mip_map = false, bool reload = false) -> LoadTask;

private:
template <typename FuncT>
auto make_load_task(std::string uri, bool reload, FuncT load) const -> LoadTask;

struct Impl;
std::shared_ptr<Impl> m_impl{};
};
} // namespace spaced
52 changes: 52 additions & 0 deletions src/spaced/spaced/game/enemy_spawner.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#include <imgui.h>
#include <bave/core/fixed_string.hpp>
#include <spaced/game/enemy_spawner.hpp>

namespace spaced {
using bave::FixedString;
using bave::ParticleEmitter;
using bave::Seconds;
using bave::Shader;

EnemySpawner::EnemySpawner(Spawn spawn, ParticleEmitter explode) : m_spawn(std::move(spawn)), m_explode(std::move(explode)) {
m_explode.config.respawn = false;
}

void EnemySpawner::tick(Seconds const dt) {
for (auto const& enemy : m_enemies) {
enemy->tick(dt);
if (enemy->is_destroyed()) { explode_at(enemy->get_bounds().centre()); }
}

for (auto& emitter : m_explodes) { emitter.tick(dt); }

std::erase_if(m_enemies, [](auto const& enemy) { return enemy->is_destroyed(); });
std::erase_if(m_explodes, [](ParticleEmitter const& emitter) { return emitter.active_particles() == 0; });
}

void EnemySpawner::draw(Shader& shader) const {
for (auto const& enemy : m_enemies) { enemy->draw(shader); }
for (auto const& emitter : m_explodes) { emitter.draw(shader); }
}

void EnemySpawner::append_targets(std::vector<bave::NotNull<IDamageable*>>& out) const {
out.reserve(out.size() + m_enemies.size());
for (auto const& enemy : m_enemies) { out.push_back(enemy.get()); }
}

void EnemySpawner::explode_at(glm::vec2 const position) {
auto& emitter = m_explodes.emplace_back(m_explode);
emitter.set_position(position);
}

void EnemySpawner::do_inspect() {
if constexpr (bave::imgui_v) {
for (std::size_t i = 0; i < m_enemies.size(); ++i) {
if (ImGui::TreeNode(FixedString{"{}", i}.c_str())) {
m_enemies.at(i)->inspect();
ImGui::TreePop();
}
}
}
}
} // namespace spaced
34 changes: 34 additions & 0 deletions src/spaced/spaced/game/enemy_spawner.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once
#include <bave/graphics/particle_system.hpp>
#include <spaced/game/enemy.hpp>
#include <functional>

namespace spaced {
class EnemySpawner {
public:
using Spawn = std::function<std::unique_ptr<Enemy>()>;

explicit EnemySpawner(Spawn spawn, bave::ParticleEmitter explode);

void tick(bave::Seconds dt);
void draw(bave::Shader& shader) const;

void spawn() { m_enemies.push_back(m_spawn()); }

[[nodiscard]] auto get_enemies() const -> std::span<std::unique_ptr<Enemy> const> { return m_enemies; }
void append_targets(std::vector<bave::NotNull<IDamageable*>>& out) const;

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

private:
void explode_at(glm::vec2 position);
void do_inspect();

Spawn m_spawn{};
bave::ParticleEmitter m_explode{};
std::vector<std::unique_ptr<Enemy>> m_enemies{};
std::vector<bave::ParticleEmitter> m_explodes{};
};
} // namespace spaced
45 changes: 38 additions & 7 deletions src/spaced/spaced/game/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,26 @@
#include <bave/imgui/im_text.hpp>
#include <bave/platform.hpp>
#include <spaced/game/player.hpp>
#include <spaced/services/resources.hpp>

// temp for testing
#include <spaced/game/weapons/gun_beam.hpp>
#include <spaced/game/weapons/gun_kinetic.hpp>

namespace spaced {
using bave::Degrees;
using bave::im_text;
using bave::NotNull;
using bave::ParticleEmitter;
using bave::PointerMove;
using bave::PointerTap;
using bave::RoundedQuad;
using bave::Seconds;
using bave::Shader;

Player::Player(Services const& services, std::unique_ptr<IController> controller) : m_services(&services), m_controller(std::move(controller)) {
auto const x = services.get<ILayout>().get_player_x();
ship.transform.position.x = x;
auto rounded_quad = RoundedQuad{};
rounded_quad.size = glm::vec2{100.0f};
rounded_quad.corner_radius = 20.0f;
ship.set_shape(rounded_quad);
setup_ship();
setup_foam();

debug_switch_weapon();
}
Expand All @@ -37,7 +36,7 @@ void Player::tick(std::span<NotNull<IDamageable*> const> targets, Seconds const
auto const y_position = m_controller->tick(dt);
set_y(y_position);

auto const muzzle_position = ship.transform.position + 0.5f * glm::vec2{ship.get_shape().size.x, 0.0f};
auto const muzzle_position = get_muzzle_position();
if (m_controller->is_firing() && m_debug.shots_remaining > 0) {
if (auto round = m_weapon->fire(muzzle_position)) {
m_weapon_rounds.push_back(std::move(round));
Expand All @@ -51,17 +50,26 @@ void Player::tick(std::span<NotNull<IDamageable*> const> targets, Seconds const

m_weapon->tick(dt);

foam_particles.set_position(get_exhaust_position());

foam_particles.tick(dt);

if (m_debug.shots_remaining <= 0) { debug_switch_weapon(); }
}

void Player::draw(Shader& shader) const {
foam_particles.draw(shader);
ship.draw(shader);

for (auto const& round : m_weapon_rounds) { round->draw(shader); }
}

void Player::set_y(float const y) { ship.transform.position.y = y; }

auto Player::get_muzzle_position() const -> glm::vec2 { return ship.transform.position + 0.5f * glm::vec2{ship.get_shape().size.x, 0.0f}; }

auto Player::get_exhaust_position() const -> glm::vec2 { return ship.transform.position - 0.5f * glm::vec2{ship.get_shape().size.x, 0.0f}; }

void Player::set_controller(std::unique_ptr<IController> controller) {
if (!controller) { return; }
m_controller = std::move(controller);
Expand All @@ -85,6 +93,29 @@ void Player::do_inspect() {
}
}

void Player::setup_ship() {
auto const x = m_services->get<ILayout>().get_player_x();
ship.transform.position.x = x;
auto rounded_quad = RoundedQuad{};
rounded_quad.size = glm::vec2{100.0f};
rounded_quad.corner_radius = 20.0f;
ship.set_shape(rounded_quad);
}

void Player::setup_foam() {
using Modifier = ParticleEmitter::Modifier;
foam_particles.config.quad_size = glm::vec2{20.0f};
foam_particles.config.velocity.linear.angle = {Degrees{80.0f}, Degrees{100.0f}};
foam_particles.config.velocity.linear.speed = {-100.0f, -200.0f};
foam_particles.config.ttl = {1s, 2s};
foam_particles.config.lerp.tint.hi.channels.w = 0x0;
foam_particles.config.lerp.scale.hi = {};
foam_particles.config.count = 500;
foam_particles.modifiers = {Modifier::eTranslate, Modifier::eTint, Modifier::eScale};
foam_particles.set_position(get_exhaust_position());
foam_particles.pre_warm();
}

void Player::debug_switch_weapon() {
if (m_weapon && !m_weapon->is_idle()) { return; }

Expand Down
8 changes: 8 additions & 0 deletions src/spaced/spaced/game/player.hpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#pragma once
#include <bave/graphics/particle_system.hpp>
#include <bave/graphics/shape.hpp>
#include <bave/logger.hpp>
#include <spaced/game/controller.hpp>
Expand All @@ -24,13 +25,20 @@ class Player : public bave::IDrawable {
void set_y(float y);
[[nodiscard]] auto get_y() const -> float { return ship.transform.position.y; }

[[nodiscard]] auto get_muzzle_position() const -> glm::vec2;
[[nodiscard]] auto get_exhaust_position() const -> glm::vec2;

void set_controller(std::unique_ptr<IController> controller);
[[nodiscard]] auto get_controller() const -> IController const& { return *m_controller; }

bave::RoundedQuadShape ship{};
bave::ParticleEmitter foam_particles{};
Health health{};

private:
void setup_ship();
void setup_foam();

void do_inspect();
void debug_switch_weapon();

Expand Down
5 changes: 4 additions & 1 deletion src/spaced/spaced/game/weapons/gun_beam.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ class LaserCharge : public IWeaponRound {
};
} // namespace

GunBeam::GunBeam(Services const& services) : Weapon(services, "GunBeam") { config.beam_tint = services.get<Styles>().rgbas["grey"]; }
GunBeam::GunBeam(Services const& services) : Weapon(services, "GunBeam") {
auto const& rgbas = services.get<Styles>().rgbas;
config.beam_tint = rgbas.get_or("gun_beam", rgbas["grey"]);
}

auto GunBeam::fire(glm::vec2 const muzzle_position) -> std::unique_ptr<Round> {
if (!is_idle() || m_reload_remain > 0s) { return {}; }
Expand Down
50 changes: 50 additions & 0 deletions src/spaced/spaced/resource_map.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once
#include <bave/core/polymorphic.hpp>
#include <spaced/string_map.hpp>
#include <memory>
#include <typeindex>

namespace spaced {
class ResourceMap {
public:
template <typename Type>
auto add(std::string uri, std::shared_ptr<Type> resource) {
if (uri.empty() || !resource) { return; }
m_resources.insert_or_assign(std::move(uri), std::make_unique<Model<Type>>(std::move(resource)));
}

[[nodiscard]] auto contains(std::string_view const uri) const -> bool { return m_resources.contains(uri); }

template <typename Type>
[[nodiscard]] auto contains(std::string_view const uri) const -> bool {
auto const it = m_resources.find(uri);
if (it == m_resources.end()) { return false; }
return it->second->type_index == typeid(Type);
}

template <typename Type>
[[nodiscard]] auto get(std::string_view const uri, std::shared_ptr<Type> const& fallback = {}) const -> std::shared_ptr<Type> {
auto const it = m_resources.find(uri);
if (it == m_resources.end()) { return fallback; }
auto const& resource = it->second;
if (resource->type_index != typeid(Type)) { return fallback; }
return static_cast<Model<Type> const&>(*it->second).resource;
}

void clear() { m_resources.clear(); }

private:
struct Base : bave::Polymorphic {
std::type_index type_index;
explicit Base(std::type_index type_index) : type_index(type_index) {}
};

template <typename T>
struct Model : Base {
std::shared_ptr<T> resource;
explicit Model(std::shared_ptr<T> t) : Base(typeid(T)), resource(std::move(t)) {}
};

StringMap<std::unique_ptr<Base>> m_resources{};
};
} // namespace spaced
3 changes: 3 additions & 0 deletions src/spaced/spaced/scene.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once
#include <bave/app.hpp>
#include <bave/core/polymorphic.hpp>
#include <bave/loader.hpp>
#include <spaced/async_exec.hpp>
#include <spaced/services/services.hpp>
#include <spaced/ui/loading_screen.hpp>
Expand All @@ -25,6 +26,8 @@ class Scene : public bave::PolyPinned {
[[nodiscard]] auto is_loading() const -> bool { return m_loading_screen.has_value(); }
[[nodiscard]] auto is_ui_blocking_input() const -> bool;

[[nodiscard]] auto make_loader() const -> bave::Loader { return bave::Loader{&m_app.get_data_store(), &m_app.get_render_device()}; }

void push_view(std::unique_ptr<ui::View> view);

bave::Rgba clear_colour{bave::black_v};
Expand Down
Loading