Skip to content
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ conan/test_package/build
CMakeSettings.json
cpp.hint

# Local builds
/build/
/build-*/

# Bazel
/bazel-*
/test/bazel-*
Expand Down
1 change: 1 addition & 0 deletions testbed/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ target_sources(
application/application.cpp
application/context.cpp
meta/meta.cpp
system/gameplay_system.cpp
system/hud_system.cpp
system/imgui_system.cpp
system/input_system.cpp
Expand Down
41 changes: 30 additions & 11 deletions testbed/application/application.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
#include <cstdint>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL.h>
#include <application/application.h>
#include <application/context.h>
#include <backends/imgui_impl_sdl3.h>
#include <backends/imgui_impl_sdlrenderer3.h>
#include <component/collectible_component.h>
#include <component/color_component.h>
#include <component/game_state_component.h>
#include <component/input_listener_component.h>
#include <component/player_component.h>
#include <component/position_component.h>
#include <component/rect_component.h>
#include <component/renderable_component.h>
#include <entt/entity/registry.hpp>
#include <imgui.h>
#include <meta/meta.h>
#include <system/gameplay_system.h>
#include <system/hud_system.h>
#include <system/imgui_system.h>
#include <system/input_system.h>
Expand All @@ -23,8 +29,12 @@ void application::update(entt::registry &registry) {
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();

// update...
static_cast<void>(registry);
static Uint64 last_tick = SDL_GetTicks();
const Uint64 current_tick = SDL_GetTicks();
const float delta_time = static_cast<float>(current_tick - last_tick) / 1000.f;
last_tick = current_tick;

gameplay_system(registry, delta_time);
}

void application::draw(entt::registry &registry, const context &context) const {
Expand All @@ -44,7 +54,6 @@ void application::draw(entt::registry &registry, const context &context) const {
}

void application::input(entt::registry &registry) {
ImGuiIO &io = ImGui::GetIO();
SDL_Event event{};

while(SDL_PollEvent(&event)) {
Expand All @@ -62,21 +71,31 @@ application::~application() {
SDL_Quit();
}

static void static_setup_for_dev_purposes(entt::registry &registry) {
const auto entt = registry.create();

registry.emplace<input_listener_component>(entt);
registry.emplace<position_component>(entt, SDL_FPoint{400.f, 400.f});
registry.emplace<rect_component>(entt, SDL_FRect{0.f, 0.f, 20.f, 20.f});
registry.emplace<renderable_component>(entt);
void application::setup(entt::registry &registry) const {
registry.ctx().emplace<game_state_component>();

const auto player = registry.create();
registry.emplace<input_listener_component>(player);
registry.emplace<player_component>(player, 420.f);
registry.emplace<position_component>(player, SDL_FPoint{240.f, 240.f});
registry.emplace<rect_component>(player, SDL_FRect{0.f, 0.f, 72.f, 72.f});
registry.emplace<color_component>(player, color_component{99u, 179u, 237u});
registry.emplace<renderable_component>(player);

const auto collectible = registry.create();
registry.emplace<collectible_component>(collectible, 1);
registry.emplace<position_component>(collectible, SDL_FPoint{920.f, 420.f});
registry.emplace<rect_component>(collectible, SDL_FRect{0.f, 0.f, 48.f, 48.f});
registry.emplace<color_component>(collectible, color_component{255u, 211u, 92u});
registry.emplace<renderable_component>(collectible);
}

int application::run() {
entt::registry registry{};
context context{};

meta_setup();
static_setup_for_dev_purposes(registry);
setup(registry);

quit = false;

Expand Down
1 change: 1 addition & 0 deletions testbed/application/application.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class application {
int run();

private:
void setup(entt::registry &) const;
bool quit;
};

Expand Down
9 changes: 9 additions & 0 deletions testbed/component/collectible_component.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

namespace testbed {

struct collectible_component {
int value;
};

} // namespace testbed
13 changes: 13 additions & 0 deletions testbed/component/color_component.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

#include <cstdint>

namespace testbed {

struct color_component {
std::uint8_t red;
std::uint8_t green;
std::uint8_t blue;
};

} // namespace testbed
12 changes: 12 additions & 0 deletions testbed/component/game_state_component.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma once

namespace testbed {

struct game_state_component {
int score{};
int best_score{};
float remaining_time{30.f};
bool running{true};
};

} // namespace testbed
12 changes: 4 additions & 8 deletions testbed/component/input_listener_component.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,10 @@
namespace testbed {

struct input_listener_component {
enum class type {
UP,
DOWN,
LEFT,
RIGHT
};

type command;
bool up{};
bool down{};
bool left{};
bool right{};
};

} // namespace testbed
9 changes: 9 additions & 0 deletions testbed/component/player_component.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

namespace testbed {

struct player_component {
float speed;
};

} // namespace testbed
37 changes: 29 additions & 8 deletions testbed/meta/meta.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#include <SDL3/SDL_rect.h>
#include <component/collectible_component.h>
#include <component/color_component.h>
#include <component/game_state_component.h>
#include <component/input_listener_component.h>
#include <component/player_component.h>
#include <component/position_component.h>
#include <component/rect_component.h>
#include <component/renderable_component.h>
Expand All @@ -21,16 +25,12 @@ void meta_setup() {
.data<&SDL_FRect::w>("w")
.data<&SDL_FRect::h>("h");

entt::meta_factory<input_listener_component::type>()
.type("command type")
.data<input_listener_component::type::UP>("up")
.data<input_listener_component::type::DOWN>("down")
.data<input_listener_component::type::LEFT>("left")
.data<input_listener_component::type::RIGHT>("right");

entt::meta_factory<input_listener_component>()
.type("input listener")
.data<&input_listener_component::command>("command");
.data<&input_listener_component::up>("up")
.data<&input_listener_component::down>("down")
.data<&input_listener_component::left>("left")
.data<&input_listener_component::right>("right");

entt::meta_factory<SDL_FPoint>()
.type("point");
Expand All @@ -45,6 +45,27 @@ void meta_setup() {

entt::meta_factory<renderable_component>()
.type("renderable");

entt::meta_factory<player_component>()
.type("player")
.data<&player_component::speed>("speed");

entt::meta_factory<collectible_component>()
.type("collectible")
.data<&collectible_component::value>("value");

entt::meta_factory<color_component>()
.type("color")
.data<&color_component::red>("red")
.data<&color_component::green>("green")
.data<&color_component::blue>("blue");

entt::meta_factory<game_state_component>()
.type("game state")
.data<&game_state_component::score>("score")
.data<&game_state_component::best_score>("best_score")
.data<&game_state_component::remaining_time>("remaining_time")
.data<&game_state_component::running>("running");
}

} // namespace testbed
103 changes: 103 additions & 0 deletions testbed/system/gameplay_system.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <SDL3/SDL_rect.h>
#include <component/collectible_component.h>
#include <component/game_state_component.h>
#include <component/input_listener_component.h>
#include <component/player_component.h>
#include <component/position_component.h>
#include <component/rect_component.h>
#include <entt/entity/registry.hpp>
#include <system/gameplay_system.h>

namespace testbed {

namespace {

[[nodiscard]] bool intersects(const SDL_FRect &lhs, const SDL_FRect &rhs) {
return lhs.x < (rhs.x + rhs.w) && (lhs.x + lhs.w) > rhs.x && lhs.y < (rhs.y + rhs.h) && (lhs.y + lhs.h) > rhs.y;
}

[[nodiscard]] SDL_FRect world_rect(const position_component &pos, const rect_component &rect) {
return SDL_FRect{pos.x + rect.x, pos.y + rect.y, rect.w, rect.h};
}

void clamp_player(position_component &position, const rect_component &rect) {
constexpr float world_width = 1920.f;
constexpr float world_height = 1080.f;

position.x = std::clamp(position.x, 0.f, world_width - rect.w);
position.y = std::clamp(position.y, 0.f, world_height - rect.h);
}

void respawn_collectible(position_component &position, int score) {
constexpr float min_x = 80.f;
constexpr float min_y = 120.f;
constexpr float x_span = 1680.f;
constexpr float y_span = 780.f;

const auto seed = static_cast<float>((score * 131) % 997);
position.x = min_x + std::fmod(seed * 73.f, x_span);
position.y = min_y + std::fmod(seed * 37.f, y_span);
}

} // namespace

void gameplay_system(entt::registry &registry, float delta_time) {
auto &state = registry.ctx().get<game_state_component>();

if(state.running) {
state.remaining_time = std::max(0.f, state.remaining_time - delta_time);
state.running = state.remaining_time > 0.f;
}

auto player_view = registry.view<player_component, input_listener_component, position_component, rect_component>();

for(auto [entity, player, input, position, rect]: player_view.each()) {
static_cast<void>(entity);

if(state.running) {
float x_axis = 0.f;
float y_axis = 0.f;

if(input.left) {
x_axis -= 1.f;
}

if(input.right) {
x_axis += 1.f;
}

if(input.up) {
y_axis -= 1.f;
}

if(input.down) {
y_axis += 1.f;
}

position.x += x_axis * player.speed * delta_time;
position.y += y_axis * player.speed * delta_time;
clamp_player(position, rect);
}

const auto player_rect = world_rect(position, rect);
auto collectible_view = registry.view<collectible_component, position_component, rect_component>();

for(auto [collectible_entity, collectible, collectible_position, collectible_rect]: collectible_view.each()) {
if(state.running && intersects(player_rect, world_rect(collectible_position, collectible_rect))) {
state.score += collectible.value;
state.best_score = std::max(state.best_score, state.score);
respawn_collectible(collectible_position, state.score);

collectible.value = 1 + (state.score / 5);
break;
}

static_cast<void>(collectible_entity);
}
}
}

} // namespace testbed
9 changes: 9 additions & 0 deletions testbed/system/gameplay_system.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#include <entt/entity/fwd.hpp>

namespace testbed {

void gameplay_system(entt::registry &, float);

} // namespace testbed
24 changes: 22 additions & 2 deletions testbed/system/hud_system.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
#include <application/context.h>
#include <component/game_state_component.h>
#include <entt/entity/registry.hpp>
#include <imgui.h>
#include <system/hud_system.h>

namespace testbed {

void hud_system(entt::registry &registry, const context &ctx) {
// render...
static_cast<void>(registry);
const auto &state = registry.ctx().get<game_state_component>();
static_cast<void>(ctx);

ImGui::SetNextWindowPos(ImVec2{20.f, 20.f}, ImGuiCond_Always);
ImGui::SetNextWindowBgAlpha(0.8f);

if(ImGui::Begin("Collector HUD", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
ImGui::Text("Arrow keys / WASD to move");
ImGui::Text("Collect the gold square before time runs out");
ImGui::Separator();
ImGui::Text("Score: %d", state.score);
ImGui::Text("Best score: %d", state.best_score);
ImGui::Text("Time left: %.1f s", state.remaining_time);

if(!state.running) {
ImGui::Separator();
ImGui::TextUnformatted("Round complete. Restart the app to play again.");
}
}

ImGui::End();
}

} // namespace testbed
Loading