Compare commits
4 Commits
fdf30d0446
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5abfedb205 | |||
| eec22cd791 | |||
| 8e89f1bf70 | |||
| d4f69278d9 |
145
main.cpp
145
main.cpp
@@ -9,38 +9,28 @@
|
|||||||
#include "spawner.h"
|
#include "spawner.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
float screen_width = 800;
|
Viewport viewport{800, 600};
|
||||||
float screen_height = 600;
|
|
||||||
|
|
||||||
float padding = 50;
|
|
||||||
float level_width = screen_width - padding * 2;
|
|
||||||
float level_height = screen_height - padding * 2;
|
|
||||||
float level_left = padding;
|
|
||||||
float level_top = padding;
|
|
||||||
float level_right = level_width + padding;
|
|
||||||
float level_bottom = level_height + padding;
|
|
||||||
|
|
||||||
Rectangle walls[] = {};
|
Rectangle walls[] = {};
|
||||||
|
|
||||||
float player_width = 50;
|
float player_width = 50;
|
||||||
Player player = {
|
Player player = {
|
||||||
.rect = {
|
.rect = {
|
||||||
.x = level_width / 2 - player_width / 2,
|
.x = viewport.level_width / 2 - player_width / 2,
|
||||||
.y = level_height / 2 - player_width / 2,
|
.y = viewport.level_height / 2 - player_width / 2,
|
||||||
.width = player_width,
|
.width = player_width,
|
||||||
.height = player_width,
|
.height = player_width,
|
||||||
},
|
},
|
||||||
.lives = 3,
|
.lives = 3,
|
||||||
|
.speed = 5.0,
|
||||||
|
.invulnerability_secs = 1,
|
||||||
.last_hit = -10,
|
.last_hit = -10,
|
||||||
.tear_speed = 10,
|
.tear_speed = 10,
|
||||||
.tear_range = 300,
|
.tear_range = 300,
|
||||||
.tear_radius = 10.0f,
|
.tear_radius = 10.0f,
|
||||||
.last_fired = 0,
|
.last_fired = 0,
|
||||||
.fire_rate = .5,
|
.fire_rate = .5,
|
||||||
|
|
||||||
};
|
};
|
||||||
auto player_speed = 5.0f;
|
|
||||||
auto player_invulnerability = 1.0f;
|
|
||||||
|
|
||||||
uint32_t score;
|
uint32_t score;
|
||||||
|
|
||||||
@@ -56,28 +46,26 @@ Vector2 Player::center() const noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Player::is_invulnerable(const double now) const noexcept {
|
bool Player::is_invulnerable(const double now) const noexcept {
|
||||||
return now < this->last_hit + player_invulnerability;
|
return now < last_hit + invulnerability_secs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::move(const float delta_x, const float delta_y) {
|
void Player::move(const Vector2 delta) {
|
||||||
const auto x = std::min(
|
const auto [next_x, next_y] = Vector2Clamp(
|
||||||
std::max(this->rect.x + delta_x, 0.0f),
|
Vector2Add({this->rect.x, this->rect.y}, delta),
|
||||||
level_right - this->rect.width);
|
{viewport.level_left, viewport.level_top},
|
||||||
|
Vector2Subtract({viewport.level_right, viewport.level_bottom}, {this->rect.width, this->rect.height})
|
||||||
const auto y = std::min(
|
);
|
||||||
std::max(this->rect.y + delta_y, 0.0f),
|
|
||||||
level_bottom - this->rect.height);
|
|
||||||
|
|
||||||
bool collided = false;
|
bool collided = false;
|
||||||
const Rectangle next_position = {
|
const Rectangle next_rect = {
|
||||||
.x = x,
|
.x = next_x,
|
||||||
.y = y,
|
.y = next_y,
|
||||||
.width = this->rect.width,
|
.width = this->rect.width,
|
||||||
.height = this->rect.height
|
.height = this->rect.height
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const auto wall: walls) {
|
for (const auto wall: walls) {
|
||||||
if (CheckCollisionRecs(wall, next_position)) {
|
if (CheckCollisionRecs(wall, next_rect)) {
|
||||||
collided = true;
|
collided = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -85,8 +73,8 @@ void Player::move(const float delta_x, const float delta_y) {
|
|||||||
|
|
||||||
if (collided) {
|
if (collided) {
|
||||||
} else {
|
} else {
|
||||||
this->rect.x = x;
|
this->rect.x = next_x;
|
||||||
this->rect.y = y;
|
this->rect.y = next_y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,35 +85,38 @@ struct EnemySpawner final : Spawner<Enemy, 100> {
|
|||||||
|
|
||||||
void spawn() override {
|
void spawn() override {
|
||||||
const auto num = Rng::generate();
|
const auto num = Rng::generate();
|
||||||
const auto starting_x = Rng::generate(static_cast<int>(level_left), static_cast<int>(level_right));
|
const auto starting_x = Rng::generate(static_cast<int>(viewport.level_left),
|
||||||
const auto starting_y = Rng::generate(static_cast<int>(level_top), static_cast<int>(level_bottom));
|
static_cast<int>(viewport.level_right));
|
||||||
|
const auto starting_y = Rng::generate(static_cast<int>(viewport.level_top),
|
||||||
|
static_cast<int>(viewport.level_bottom));
|
||||||
|
|
||||||
for (auto &[center, radius, speed, alive]: this->values) {
|
for (auto &enemy: this->values) {
|
||||||
if (alive) continue;
|
if (enemy.alive()) continue;
|
||||||
alive = true;
|
enemy.radius = enemy_radius;
|
||||||
radius = enemy_radius;
|
auto hp = Rng::generate(1, 4);
|
||||||
|
enemy.hp = static_cast<uint32_t>(hp);
|
||||||
if (num < 0.25) {
|
if (num < 0.25) {
|
||||||
center = {.x = starting_x, .y = level_top};
|
enemy.center = {.x = starting_x, .y = viewport.level_top};
|
||||||
} else if (0.25 <= num && num < 0.5) {
|
} else if (0.25 <= num && num < 0.5) {
|
||||||
center = {
|
enemy.center = {
|
||||||
.x = starting_x, .y = level_bottom
|
.x = starting_x, .y = viewport.level_bottom
|
||||||
};
|
};
|
||||||
} else if (0.5 <= num && num < 0.75) {
|
} else if (0.5 <= num && num < 0.75) {
|
||||||
center = {.x = level_left, .y = starting_y};
|
enemy.center = {.x = viewport.level_left, .y = starting_y};
|
||||||
} else {
|
} else {
|
||||||
center = {
|
enemy.center = {
|
||||||
.x = level_right, .y = starting_y
|
.x = viewport.level_right, .y = starting_y
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
speed = Rng::generate(static_cast<uint32_t>(enemy_max_speed));
|
enemy.speed = Rng::generate(static_cast<uint32_t>(enemy_max_speed));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
EnemySpawner enemy_spawner{2};
|
EnemySpawner enemy_spawner{1};
|
||||||
|
|
||||||
struct ItemSpawner final : Spawner<Item, 100> {
|
struct ItemSpawner final : Spawner<Item, 100> {
|
||||||
explicit ItemSpawner(const long long rate_secs) : Spawner<Item, 100>(rate_secs) {
|
explicit ItemSpawner(const long long rate_secs) : Spawner<Item, 100>(rate_secs) {
|
||||||
@@ -133,8 +124,8 @@ struct ItemSpawner final : Spawner<Item, 100> {
|
|||||||
|
|
||||||
void spawn() override {
|
void spawn() override {
|
||||||
const auto item_type = static_cast<ItemType>(Rng::generate(ITEM_TYPE_COUNT));
|
const auto item_type = static_cast<ItemType>(Rng::generate(ITEM_TYPE_COUNT));
|
||||||
const auto x = Rng::generate(static_cast<int>(level_left), static_cast<int>(level_right));
|
const auto x = Rng::generate(static_cast<int>(viewport.level_left), static_cast<int>(viewport.level_right));
|
||||||
const auto y = Rng::generate(static_cast<int>(level_top), static_cast<int>(level_bottom));
|
const auto y = Rng::generate(static_cast<int>(viewport.level_top), static_cast<int>(viewport.level_bottom));
|
||||||
|
|
||||||
for (auto &item: this->values) {
|
for (auto &item: this->values) {
|
||||||
if (item.active) continue;
|
if (item.active) continue;
|
||||||
@@ -173,11 +164,11 @@ void update_tears() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enemy_spawner.for_each([¢er, &active](auto &enemy) {
|
enemy_spawner.for_each([¢er, &active](auto &enemy, auto& cancel) {
|
||||||
if (enemy.alive && CheckCollisionCircles(center, player.tear_radius, enemy.center, enemy.radius)) {
|
if (enemy.alive() && CheckCollisionCircles(center, player.tear_radius, enemy.center, enemy.radius)) {
|
||||||
active = false;
|
active = false;
|
||||||
enemy.alive = false;
|
if (--enemy.hp == 0) score++;
|
||||||
score++;
|
cancel = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -220,27 +211,21 @@ int main() {
|
|||||||
std::string item_pickup_text_buffer;
|
std::string item_pickup_text_buffer;
|
||||||
item_pickup_text_buffer.reserve(64);
|
item_pickup_text_buffer.reserve(64);
|
||||||
double item_last_picked_up = 0;
|
double item_last_picked_up = 0;
|
||||||
double item_pickup_message_duration = 3;
|
|
||||||
|
|
||||||
while (!WindowShouldClose()) {
|
while (!WindowShouldClose()) {
|
||||||
|
constexpr double item_pickup_message_duration = 3;
|
||||||
|
// float dt_secs = GetFrameTime();
|
||||||
if (IsWindowResized()) {
|
if (IsWindowResized()) {
|
||||||
screen_height = static_cast<float>(GetScreenHeight());
|
viewport = {static_cast<float>(GetScreenWidth()), static_cast<float>(GetScreenHeight())};
|
||||||
screen_width = static_cast<float>(GetScreenWidth());
|
|
||||||
level_width = screen_width - padding * 2;
|
|
||||||
level_height = screen_height - padding * 2;
|
|
||||||
level_left = padding;
|
|
||||||
level_top = padding;
|
|
||||||
level_right = level_width + padding;
|
|
||||||
level_bottom = level_height + padding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Vector2 delta{};
|
Vector2 delta{};
|
||||||
if (IsKeyDown(KEY_W)) delta.y -= player_speed;
|
if (IsKeyDown(KEY_W)) delta.y -= player.speed;
|
||||||
if (IsKeyDown(KEY_S)) delta.y += player_speed;
|
if (IsKeyDown(KEY_S)) delta.y += player.speed;
|
||||||
if (IsKeyDown(KEY_A)) delta.x -= player_speed;
|
if (IsKeyDown(KEY_A)) delta.x -= player.speed;
|
||||||
if (IsKeyDown(KEY_D)) delta.x += player_speed;
|
if (IsKeyDown(KEY_D)) delta.x += player.speed;
|
||||||
player.move(delta.x, delta.y);
|
player.move(delta);
|
||||||
|
|
||||||
|
|
||||||
std::optional<Direction> tear_direction;
|
std::optional<Direction> tear_direction;
|
||||||
@@ -267,7 +252,8 @@ int main() {
|
|||||||
|
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
ClearBackground(GRAY);
|
ClearBackground(GRAY);
|
||||||
DrawRectangle(50, 50, static_cast<int>(level_width), static_cast<int>(level_height), RAYWHITE);
|
DrawRectangle(50, 50, static_cast<int>(viewport.level_width), static_cast<int>(viewport.level_height),
|
||||||
|
RAYWHITE);
|
||||||
DrawRectangleRec(player.rect, player.is_invulnerable(now) ? GOLD : BLUE);
|
DrawRectangleRec(player.rect, player.is_invulnerable(now) ? GOLD : BLUE);
|
||||||
|
|
||||||
for (const auto wall: walls) {
|
for (const auto wall: walls) {
|
||||||
@@ -280,25 +266,27 @@ int main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enemy_spawner.for_each([now](Enemy &enemy) {
|
enemy_spawner.for_each([now](Enemy &enemy, auto& cancel) {
|
||||||
if (enemy.alive) {
|
if (enemy.alive()) {
|
||||||
enemy.center = Vector2MoveTowards(enemy.center, player.center(), enemy.speed);
|
enemy.center = Vector2MoveTowards(enemy.center, player.center(), enemy.speed);
|
||||||
DrawCircleV(enemy.center, enemy.radius, RED);
|
const Color enemy_color = enemy.hp == 1 ? PINK : enemy.hp == 2 ? ORANGE : RED;
|
||||||
|
DrawCircleV(enemy.center, enemy.radius, enemy_color);
|
||||||
|
|
||||||
if (enemy.center.x < level_left || level_right < enemy.center.x ||
|
if (enemy.center.x < viewport.level_left || viewport.level_right < enemy.center.x ||
|
||||||
enemy.center.y < level_top || level_bottom < enemy.center.y) {
|
enemy.center.y < viewport.level_top || viewport.level_bottom < enemy.center.y) {
|
||||||
enemy.alive = false;
|
// Basically removing the enemy from the pool;
|
||||||
|
enemy.hp = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CheckCollisionCircleRec(enemy.center, enemy.radius, player.rect) &&
|
if (CheckCollisionCircleRec(enemy.center, enemy.radius, player.rect) &&
|
||||||
player.last_hit + player_invulnerability < now) {
|
player.last_hit + player.invulnerability_secs < now) {
|
||||||
player.lives--;
|
player.lives--;
|
||||||
player.last_hit = now;
|
player.last_hit = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
item_spawner.for_each([&item_pickup_text_buffer, now, &item_last_picked_up](Item &item) {
|
item_spawner.for_each([&item_pickup_text_buffer, now, &item_last_picked_up](Item &item, auto& cancel) {
|
||||||
if (!item.active) return;
|
if (!item.active) return;
|
||||||
|
|
||||||
DrawCircleV(item.center, item.radius, item.color());
|
DrawCircleV(item.center, item.radius, item.color());
|
||||||
@@ -330,21 +318,22 @@ int main() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const int text_left = static_cast<int>(level_left) + 10;
|
const int text_left = static_cast<int>(viewport.level_left) + 10;
|
||||||
|
|
||||||
DrawRectangle(static_cast<int>(level_left), static_cast<int>(level_height) - 110, 200, 110,
|
DrawRectangle(static_cast<int>(viewport.level_left), static_cast<int>(viewport.level_height) - 110, 200, 110,
|
||||||
{130, 130, 130, 100});
|
{130, 130, 130, 100});
|
||||||
|
|
||||||
std::format_to(std::back_inserter(score_text_buffer), "Score: {}", score);
|
std::format_to(std::back_inserter(score_text_buffer), "Score: {}", score);
|
||||||
DrawText(score_text_buffer.c_str(), text_left, static_cast<int>(level_height) - 100, 20, BLACK);
|
DrawText(score_text_buffer.c_str(), text_left, static_cast<int>(viewport.level_height) - 100, 20, BLACK);
|
||||||
score_text_buffer.clear();
|
score_text_buffer.clear();
|
||||||
|
|
||||||
std::format_to(std::back_inserter(lives_text_buffer), "Lives: {}", player.lives);
|
std::format_to(std::back_inserter(lives_text_buffer), "Lives: {}", player.lives);
|
||||||
DrawText(lives_text_buffer.c_str(), text_left, static_cast<int>(level_height) - 75, 20, BLACK);
|
DrawText(lives_text_buffer.c_str(), text_left, static_cast<int>(viewport.level_height) - 75, 20, BLACK);
|
||||||
lives_text_buffer.clear();
|
lives_text_buffer.clear();
|
||||||
|
|
||||||
if (now < item_last_picked_up + item_pickup_message_duration && !item_pickup_text_buffer.empty()) {
|
if (now < item_last_picked_up + item_pickup_message_duration && !item_pickup_text_buffer.empty()) {
|
||||||
DrawText(item_pickup_text_buffer.c_str(), text_left, static_cast<int>(level_height) - 50, 20, BLACK);
|
DrawText(item_pickup_text_buffer.c_str(), text_left, static_cast<int>(viewport.level_height) - 50, 20,
|
||||||
|
BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item_last_picked_up + item_pickup_message_duration <= now && !item_pickup_text_buffer.empty()) {
|
if (item_last_picked_up + item_pickup_message_duration <= now && !item_pickup_text_buffer.empty()) {
|
||||||
|
|||||||
2
rng.cpp
2
rng.cpp
@@ -13,5 +13,5 @@ float Rng::generate(const uint32_t max) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float Rng::generate(const int min, const int max) {
|
float Rng::generate(const int min, const int max) {
|
||||||
return generate() * (static_cast<float>(max) - static_cast<float>(min)) + static_cast<float>(min);
|
return generate(max - min) + static_cast<float>(min);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ struct Spawner {
|
|||||||
void for_each(F&& func) {
|
void for_each(F&& func) {
|
||||||
std::lock_guard lock{mutex};
|
std::lock_guard lock{mutex};
|
||||||
for (auto& value : values) {
|
for (auto& value : values) {
|
||||||
func(value);
|
bool cancel = false;
|
||||||
|
func(value, cancel);
|
||||||
|
if (cancel) break;;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
types.h
37
types.h
@@ -11,6 +11,8 @@
|
|||||||
struct Player {
|
struct Player {
|
||||||
Rectangle rect;
|
Rectangle rect;
|
||||||
uint8_t lives;
|
uint8_t lives;
|
||||||
|
float speed;
|
||||||
|
float invulnerability_secs;
|
||||||
double last_hit;
|
double last_hit;
|
||||||
float tear_speed;
|
float tear_speed;
|
||||||
float tear_range;
|
float tear_range;
|
||||||
@@ -22,7 +24,7 @@ struct Player {
|
|||||||
|
|
||||||
[[nodiscard]] bool is_invulnerable(double now) const noexcept;
|
[[nodiscard]] bool is_invulnerable(double now) const noexcept;
|
||||||
|
|
||||||
void move(float delta_x, float delta_y);
|
void move(Vector2 delta);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Direction {
|
enum Direction {
|
||||||
@@ -47,7 +49,8 @@ struct Enemy {
|
|||||||
Vector2 center;
|
Vector2 center;
|
||||||
float radius;
|
float radius;
|
||||||
float speed;
|
float speed;
|
||||||
bool alive;
|
uint32_t hp;
|
||||||
|
[[nodiscard]] bool alive() const noexcept { return hp != 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ItemType {
|
enum ItemType {
|
||||||
@@ -120,3 +123,33 @@ struct Item {
|
|||||||
assert(0 && "Unreachable.");
|
assert(0 && "Unreachable.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Viewport {
|
||||||
|
Viewport() = default;
|
||||||
|
|
||||||
|
Viewport(const float screen_width, const float screen_height)
|
||||||
|
: screen_width(screen_width),
|
||||||
|
screen_height(screen_height),
|
||||||
|
padding(50),
|
||||||
|
level_width(screen_width - padding * 2),
|
||||||
|
level_height(screen_height - padding * 2),
|
||||||
|
level_left(padding),
|
||||||
|
level_top(padding),
|
||||||
|
level_right(level_width + padding),
|
||||||
|
level_bottom(level_height + padding) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Viewport& operator=(const Viewport &other) = default;
|
||||||
|
|
||||||
|
float screen_width;
|
||||||
|
float screen_height;
|
||||||
|
|
||||||
|
float padding;
|
||||||
|
|
||||||
|
float level_width;
|
||||||
|
float level_height;
|
||||||
|
float level_left;
|
||||||
|
float level_top;
|
||||||
|
float level_right;
|
||||||
|
float level_bottom;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user