add damage

This commit is contained in:
Rokas Puzonas 2025-12-07 21:27:25 +02:00
parent 2230bab37a
commit c20a4b6cad
11 changed files with 757 additions and 272 deletions

View File

@ -1,16 +1,20 @@
#pragma once
#include "main.h"
#include "timers.h"
enum EntityType {
ENTITY_TYPE_NIL = 0,
ENTITY_TYPE_PLAYER,
ENTITY_TYPE_WALL
ENTITY_TYPE_DAMAGE,
ENTITY_TYPE_WALL,
};
struct EntityFlags {
bool has_friction : 1;
bool has_max_speed : 1;
bool remove_offscsreen : 1;
};
struct Entity {
@ -25,5 +29,9 @@ struct Entity {
// TODO: Remove this, because this is just a temporary value for resolving collisions
Vector2 step;
uint32_t health;
TimerId invincibility_timer;
Rectangle collision_rect;
};

View File

@ -1,17 +1,26 @@
#include "game.h"
#include "entity_id_list.h"
#include "float_list.h"
#include "raylib.h"
#include "rect.h"
#include <stdio.h>
#include <sys/param.h>
#define MAX_PLAYER_HEALTH 5
#define PLAYER_INVINCIBILITY 1.5
void game_init(struct Game *game)
{
*game = (struct Game){
.canvas_size = { 800, 600 }
.canvas_size = { 800, 600 },
.show_damage_rects = true
};
{
struct Entity *player = entity_list_add(&game->entities);
player->type = ENTITY_TYPE_PLAYER;
player->position = Vector2Scale(game->canvas_size, 0.5);
player->health = MAX_PLAYER_HEALTH;
player->collision_rect = (Rectangle){
.x = -10,
.y = -10,
@ -20,10 +29,10 @@ void game_init(struct Game *game)
};
}
float wall_thickness = 10;
float wall_thickness = 50;
// Canvas bounds
{
if (false) {
game_add_wall(game, (Rectangle){
.x = -wall_thickness,
.y = -wall_thickness,
@ -52,74 +61,15 @@ void game_init(struct Game *game)
.height = game->canvas_size.y + 2*wall_thickness
});
}
for (int i = 0; i < 5; i++) {
game_add_wall(game, (Rectangle){
.x = 10 + 45 * i,
.y = 10,
.width = 40,
.height = 40
});
}
game_add_wall(game, (Rectangle){
.x = 300,
.y = 10,
.width = 40,
.height = 40
});
{
EntityId wall_id = game_add_wall(game, (Rectangle){
.x = 10,
.y = 10,
.width = 40,
.height = 200
});
struct Entity *wall = entity_list_get(&game->entities, wall_id);
wall->velocity.x = 100;
}
{
EntityId wall_id = game_add_wall(game, (Rectangle){
.x = 700,
.y = 10,
.width = 40,
.height = 200
});
struct Entity *wall = entity_list_get(&game->entities, wall_id);
wall->velocity.x = -100;
}
{
EntityId wall_id = game_add_wall(game, (Rectangle){
.x = 10,
.y = 00,
.width = 200,
.height = 40
});
struct Entity *wall = entity_list_get(&game->entities, wall_id);
wall->velocity.y = 100;
}
{
EntityId wall_id = game_add_wall(game, (Rectangle){
.x = 10,
.y = 500,
.width = 200,
.height = 40
});
struct Entity *wall = entity_list_get(&game->entities, wall_id);
wall->velocity.y = -100;
}
}
void game_free(struct Game *game)
{
entity_list_free(&game->entities);
timers_free(&game->timers);
}
EntityId game_add_wall(struct Game *game, Rectangle rect)
struct Entity *game_add_wall(struct Game *game, Rectangle rect)
{
struct Entity *wall = entity_list_add(&game->entities);
@ -133,32 +83,7 @@ EntityId game_add_wall(struct Game *game, Rectangle rect)
.height = rect.height
};
return entity_list_get_id(&game->entities, wall);
}
static float game_aabb(Rectangle b1, Rectangle b2)
{
return CheckCollisionRecs(b1, b2);
}
static float rect_get_left(Rectangle rect)
{
return rect.x;
}
static float rect_get_right(Rectangle rect)
{
return rect.x + rect.width;
}
static float rect_get_top(Rectangle rect)
{
return rect.y;
}
static float rect_get_bottom(Rectangle rect)
{
return rect.y + rect.height;
return wall;
}
struct CollisionResult {
@ -228,13 +153,6 @@ static bool game_swept_aabb(Rectangle b1, Vector2 v1, Rectangle b2, struct Colli
return true;
}
static Rectangle rect_add_position(Rectangle rect, Vector2 pos)
{
rect.x += pos.x;
rect.y += pos.y;
return rect;
}
static Rectangle rect_get_step_bounds(Rectangle rect, Vector2 step)
{
if (step.x >= 0) {
@ -254,14 +172,6 @@ static Rectangle rect_get_step_bounds(Rectangle rect, Vector2 step)
return rect;
}
static Vector2 rect_get_center(Rectangle rect)
{
return (Vector2){
.x = rect.x + rect.width / 2,
.y = rect.y + rect.height / 2,
};
}
static void game_list_nearest_walls(struct Game *game, struct EntityIdList *walls, Rectangle bounds, struct EntityIdList *result)
{
struct FloatList distances = { 0 };
@ -273,7 +183,7 @@ static void game_list_nearest_walls(struct Game *game, struct EntityIdList *wall
ASSERT(wall != NULL);
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
if (game_aabb(wall_collider, bounds)) {
if (rect_is_overlap(wall_collider, bounds)) {
float distance_sqr = Vector2DistanceSqr(
rect_get_center(wall_collider),
rect_get_center(bounds)
@ -350,9 +260,172 @@ static void game_collide_against_walls(struct Game *game, struct EntityIdList *w
TracyCZoneEnd(game_collide_against_walls)
}
void game_tick(struct Game *game)
static void game_collide_push_players(struct Game *game, struct Entity *wall, struct EntityIdList *players)
{
TracyCZoneN(game_tick, "game_tick", true);
for (size_t i = 0; i < players->len; i++) {
struct Entity *player = entity_list_get(&game->entities, players->items[i]);
ASSERT(player != NULL);
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
Rectangle player_collider = rect_add_position(player->collision_rect, player->position);
if (!rect_is_overlap(player_collider, rect_get_step_bounds(wall_collider, wall->step))) {
continue;
}
struct CollisionResult collision = { 0 };
if (game_swept_aabb(wall_collider, wall->step, player_collider, &collision)) {
if (collision.normal.x != 0 && signf(collision.normal.x) == signf(player->step.x)) {
player->step.x = 0;
}
if (collision.normal.y != 0 && signf(collision.normal.y) == signf(player->step.y)) {
player->step.y = 0;
}
player->step.x += wall->step.x * (1 - collision.time);
player->step.y += wall->step.y * (1 - collision.time);
}
}
}
static Vector2 game_get_nudge_to_outside(Rectangle wall_collider, Rectangle player_collider)
{
Vector2 player_center = rect_get_center(player_collider);
Vector2 wall_center = rect_get_center(wall_collider);
float x_overlap = 0;
if (player_center.x < wall_center.x) {
x_overlap = MIN(rect_get_left(wall_collider) - rect_get_right(player_collider), 0);
} else {
x_overlap = MAX(rect_get_right(wall_collider) - rect_get_left(player_collider), 0);
}
float y_overlap = 0;
if (player_center.y < wall_center.y) {
y_overlap = MIN(rect_get_top(wall_collider) - rect_get_bottom(player_collider), 0);
} else {
y_overlap = MAX(rect_get_bottom(wall_collider) - rect_get_top(player_collider), 0);
}
if (fabs(x_overlap) < fabs(y_overlap)) {
return (Vector2){
.x = x_overlap
};
} else {
return (Vector2){
.y = y_overlap
};
}
}
static Vector2 game_get_nudge_to_inside(Rectangle container, Rectangle player_collider)
{
Vector2 player_center = rect_get_center(player_collider);
Vector2 cotainer_center = rect_get_center(container);
float x_overlap = 0;
if (player_center.x < cotainer_center.x) {
x_overlap = MAX(rect_get_left(container) - rect_get_left(player_collider), 0);
} else {
x_overlap = MIN(rect_get_right(container) - rect_get_right(player_collider), 0);
}
float y_overlap = 0;
if (player_center.y < cotainer_center.y) {
y_overlap = MAX(rect_get_top(container) - rect_get_top(player_collider), 0);
} else {
y_overlap = MIN(rect_get_bottom(container) - rect_get_bottom(player_collider), 0);
}
return (Vector2){
.x = x_overlap,
.y = y_overlap
};
}
static bool game_collide_nudge_out_of_walls(
struct Game *game,
Rectangle player_rect,
Vector2 *player_step,
struct EntityIdList *walls,
float max_nudge_amount
)
{
bool max_nudge_reached = false;
for (size_t i = 0; i < walls->len; i++) {
struct Entity *wall = entity_list_get(&game->entities, walls->items[i]);
ASSERT(wall != NULL);
Rectangle player_collider = rect_add_position(player_rect, *player_step);
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
if (rect_is_overlap(player_collider, wall_collider)) {
Vector2 nudge = game_get_nudge_to_outside(wall_collider, player_collider);
if (Vector2Length(nudge) > max_nudge_amount) {
max_nudge_reached = true;
}
*player_step = Vector2Add(*player_step, Vector2ClampValue(nudge, 0, max_nudge_amount));
}
}
return max_nudge_reached;
}
static void game_start_attack(struct Game *game, struct Attack attack)
{
timers_stop_by_attack(&game->timers);
game->attack = attack;
}
void game_attack_sweep_wave_tick(struct Game *game, struct AttackSweepWave *attack)
{
float thickness = 100;
float speed = 300;
int wall_count = 20;
float time_between_walls = 0.2;
if (timers_finished(&game->timers, attack->timer) && !attack->finished) {
float width = game->canvas_size.x / wall_count;
struct Entity *damage = entity_list_add(&game->entities);
damage->type = ENTITY_TYPE_DAMAGE;
damage->flags.remove_offscsreen = true;
damage->position.x = attack->wall_index * width;
damage->collision_rect = (Rectangle){
.width = width,
.height = thickness
};
if (attack->wall_index % 2 == 0) {
damage->position.y = -thickness;
damage->velocity.y = speed;
} else {
damage->position.y = game->canvas_size.y;
damage->velocity.y = -speed;
}
attack->wall_index++;
if (attack->wall_index < wall_count) {
attack->timer = timers_start(&game->timers, time_between_walls);
timers_set_attack(&game->timers, attack->timer);
} else {
attack->finished = true;
}
}
}
void game_draw_rect_outlined(Rectangle rect, Color color)
{
DrawRectangleRec(rect, ColorAlpha(color, 0.5));
DrawRectangleLinesEx(rect, 2, ColorAlpha(color, 0.5));
}
void game_get_input(struct Game *game, struct WindowScaler *scaler, struct Input *result)
{
Vector2 mouse = GetMousePosition();
mouse = Vector2Subtract(mouse, scaler->offset);
mouse = Vector2Scale(mouse, 1/scaler->scale);
Vector2 dir = { 0 };
if (IsKeyDown(KEY_W)) {
@ -369,12 +442,42 @@ void game_tick(struct Game *game)
}
dir = Vector2Normalize(dir);
*result = (struct Input){
.dt = GetFrameTime(),
.mouse = mouse,
.move_dir = dir,
.focus = IsKeyDown(KEY_SPACE),
.restart = IsKeyPressed(KEY_R)
};
}
void game_tick(struct Game *game)
{
TracyCZoneN(game_tick, "game_tick", true);
if (game->input.restart) {
game_free(game);
game_init(game);
}
float dt = game->input.dt;
game->timers.now += dt;
if (game->attack.type == ATTACK_TYPE_NIL) {
game_start_attack(game, (struct Attack){
.type = ATTACK_TYPE_SWEEP_WAVE,
});
}
if (game->attack.type == ATTACK_TYPE_SWEEP_WAVE) {
game_attack_sweep_wave_tick(game, &game->attack.sweep_wave);
}
struct Entity *entity = NULL;
entity_list_foreach(&game->entities, entity) {
if (entity->type == ENTITY_TYPE_PLAYER) {
entity->velocity = Vector2Scale(dir, 200);
entity->velocity = Vector2Scale(game->input.move_dir, 200);
}
entity->velocity = Vector2Add(entity->velocity, Vector2Scale(entity->accelaration, dt));
@ -388,6 +491,26 @@ void game_tick(struct Game *game)
entity->step = Vector2Scale(entity->velocity, dt);
}
entity_list_foreach(&game->entities, entity) {
if (entity->type != ENTITY_TYPE_WALL) {
continue;
}
if (!entity->flags.remove_offscsreen) {
continue;
}
Rectangle bounding_box = rect_add_position(entity->collision_rect, entity->position);
Rectangle onscreen_area = {
.x = -0.5*game->canvas_size.x,
.y = -0.5*game->canvas_size.y,
.width = 2*game->canvas_size.x,
.height = 2*game->canvas_size.y,
};
if (!rect_is_overlap(onscreen_area, bounding_box)) {
entity_list_remove(&game->entities, entity);
}
}
struct EntityIdList walls = { 0 };
struct EntityIdList players = { 0 };
@ -406,36 +529,18 @@ void game_tick(struct Game *game)
struct Entity *wall = entity_list_get(&game->entities, walls.items[i]);
ASSERT(wall != NULL);
for (size_t j = 0; j < players.len; j++) {
struct Entity *player = entity_list_get(&game->entities, players.items[j]);
ASSERT(player != NULL);
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
Rectangle player_collider = rect_add_position(player->collision_rect, player->position);
if (!game_aabb(player_collider, rect_get_step_bounds(wall_collider, wall->step))) {
continue;
}
struct CollisionResult collision = { 0 };
if (game_swept_aabb(wall_collider, wall->step, player_collider, &collision)) {
if (collision.normal.x != 0 && signf(collision.normal.x) == signf(player->step.x)) {
player->step.x = 0;
}
if (collision.normal.y != 0 && signf(collision.normal.y) == signf(player->step.y)) {
player->step.y = 0;
}
player->step.x += wall->step.x * (1 - collision.time);
player->step.y += wall->step.y * (1 - collision.time);
}
}
game_collide_push_players(
game,
wall,
&players
);
wall->position = Vector2Add(wall->position, wall->step);
}
for (size_t i = 0; i < players.len; i++) {
struct Entity *player = entity_list_get(&game->entities, players.items[i]);
EntityId player_id = players.items[i];
struct Entity *player = entity_list_get(&game->entities, player_id);
ASSERT(player != NULL);
game_collide_against_walls(
@ -445,69 +550,98 @@ void game_tick(struct Game *game)
&player->step
);
for (size_t j = 0; j < walls.len; j++) {
struct Entity *wall = entity_list_get(&game->entities, walls.items[j]);
ASSERT(wall != NULL);
bool apply_damage = false;
for (int j = 0; j < 2; j++) {
if (game_collide_nudge_out_of_walls(
game,
rect_add_position(player->collision_rect, player->position),
&player->step,
&walls,
10
)) {
apply_damage = true;
}
}
Rectangle player_collider = rect_add_position(player->collision_rect, Vector2Add(player->position, player->step));
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
if (game_aabb(player_collider, wall_collider)) {
Vector2 player_center = rect_get_center(player_collider);
Vector2 wall_center = rect_get_center(wall_collider);
float x_overlap = 0;
if (player_center.x < wall_center.x) {
x_overlap = MIN(rect_get_left(wall_collider) - rect_get_right(player_collider), 0);
} else {
x_overlap = MAX(rect_get_right(wall_collider) - rect_get_left(player_collider), 0);
}
float y_overlap = 0;
if (player_center.y < wall_center.y) {
y_overlap = MIN(rect_get_top(wall_collider) - rect_get_bottom(player_collider), 0);
} else {
y_overlap = MAX(rect_get_bottom(wall_collider) - rect_get_top(player_collider), 0);
}
float overlap_size = 0;
if (fabs(x_overlap) < fabs(y_overlap)) {
overlap_size = fabs(x_overlap);
player->step.x += x_overlap;
} else {
overlap_size = fabs(y_overlap);
player->step.y += y_overlap;
}
if (overlap_size > 1) {
// TODO: apply damage to player
}
{
Rectangle player_rect = rect_add_position(player->collision_rect, Vector2Add(player->position, player->step));
Rectangle canvas_rect = {
.width = game->canvas_size.x,
.height = game->canvas_size.y,
};
if (!rect_is_inside_rect(canvas_rect, player_rect)) {
Vector2 nudge = game_get_nudge_to_inside(canvas_rect, player_rect);
player->step = Vector2Add(player->step, nudge);
}
}
player->position = Vector2Add(player->position, player->step);
}
for (size_t i = 0; i < walls.len; i++) {
struct Entity *wall = entity_list_get(&game->entities, walls.items[i]);
ASSERT(wall != NULL);
Rectangle player_rect = rect_add_position(player->collision_rect, player->position);
entity_list_foreach(&game->entities, entity) {
if (entity->type == ENTITY_TYPE_DAMAGE) {
Rectangle damage_rect = rect_add_position(entity->collision_rect, entity->position);
if (rect_is_overlap(damage_rect, player_rect)) {
apply_damage = true;
}
}
}
if (apply_damage) {
if (timers_finished(&game->timers, player->invincibility_timer)) {
printf("apply_damage!\n");
player->health -= 1;
player->invincibility_timer = timers_start_with_entity(&game->timers, PLAYER_INVINCIBILITY, player_id);
}
}
}
entity_list_foreach(&game->entities, entity) {
if (entity->type == ENTITY_TYPE_PLAYER) {
Vector2 size = (Vector2){ 20, 20 };
DrawRectangleV(Vector2Subtract(entity->position, Vector2Scale(size, 0.5)), size, RED);
Color color = GREEN;
float time_passed = timers_time_since_start(&game->timers, entity->invincibility_timer);
if (!timers_finished(&game->timers, entity->invincibility_timer)) {
if (fmod(time_passed, 0.2) < 0.1) {
color = WHITE;
}
}
DrawRectangleV(Vector2Subtract(entity->position, Vector2Scale(size, 0.5)), size, color);
DrawCircleV(entity->position, 4, BLACK);
float healthbar_width = 50;
Rectangle healthbar = {
.x = entity->position.x - healthbar_width/2,
.y = entity->position.y + 15,
.width = healthbar_width,
.height = 10
};
DrawRectangleRec(healthbar, BLACK);
float health_percent = (float)entity->health / MAX_PLAYER_HEALTH;
Rectangle healthbar_filled = rect_shrink_all(healthbar, 1);
healthbar_filled.width *= health_percent;
DrawRectangleRec(healthbar_filled, GREEN);
}
if (entity->type == ENTITY_TYPE_WALL || entity->type == ENTITY_TYPE_PLAYER) {
Rectangle rect = entity->collision_rect;
rect.x += entity->position.x;
rect.y += entity->position.y;
Color color = BLUE;
DrawRectangleRec(rect, ColorAlpha(color, 0.5));
DrawRectangleLinesEx(rect, 2, ColorAlpha(color, 0.5));
if (game->show_damage_rects) {
if (entity->type == ENTITY_TYPE_DAMAGE) {
game_draw_rect_outlined(
rect_add_position(entity->collision_rect, entity->position),
RED
);
}
}
if (game->show_collider) {
if (entity->type == ENTITY_TYPE_WALL || entity->type == ENTITY_TYPE_PLAYER) {
game_draw_rect_outlined(
rect_add_position(entity->collision_rect, entity->position),
BLUE
);
}
}
}

View File

@ -2,20 +2,55 @@
#include "main.h"
#include "entity_list.h"
#include "timers.h"
#include "window_scaler.h"
enum AttackType {
ATTACK_TYPE_NIL = 0,
ATTACK_TYPE_SWEEP_WAVE
};
struct AttackSweepWave {
bool finished;
TimerId timer;
size_t wall_index;
};
struct Attack {
enum AttackType type;
union {
struct AttackSweepWave sweep_wave;
};
};
struct Input {
float dt;
Vector2 mouse;
Vector2 move_dir;
bool focus;
bool restart;
};
struct Game {
Vector2 canvas_size;
struct EntityList entities;
struct Timers timers;
struct Input input;
// TODO: Add support for multiple attack running at the same time
struct Attack attack;
bool show_collider;
bool show_damage_rects;
};
void game_init(struct Game *game);
void game_free(struct Game *game);
void game_tick(struct Game *game);
EntityId game_add_wall(struct Game *game, Rectangle rect);
struct Entity *game_add_wall(struct Game *game, Rectangle rect);
void game_get_input(
struct Game *game,
struct WindowScaler *scaler,
struct Input *result
);

View File

@ -34,7 +34,14 @@ void game_debug_show(struct Game *game)
snprintf(label, sizeof(label), "FPS: %.1f (%.2fms)", 1.0f / game->input.dt, game->input.dt * 1000);
igTextEx(label, NULL, ImGuiTextFlags_None);
igTextEx("Hello, World", NULL, ImGuiTextFlags_None);
snprintf(label, sizeof(label), "Entity count: %zu", game->entities.used_count);
igTextEx(label, NULL, ImGuiTextFlags_None);
snprintf(label, sizeof(label), "Timer count: %zu", game->timers.len);
igTextEx(label, NULL, ImGuiTextFlags_None);
igCheckbox("Show colliders", &game->show_collider);
igCheckbox("Show damage", &game->show_damage_rects);
}
igEnd();

View File

@ -2,86 +2,10 @@
#include "resources.h"
#include "game.h"
#include "game_debug.h"
#include "window_scaler.h"
struct Resources g_resources = { 0 };
struct WindowTransform {
Vector2 offset;
float scale;
};
static void begin_window_scaling(struct WindowTransform *transform, Vector2 virtual_screen_size)
{
Vector2 screen_size = {
GetScreenWidth(),
GetScreenHeight()
};
float scale = MIN(
screen_size.x / virtual_screen_size.x,
screen_size.y / virtual_screen_size.y
);
transform->scale = scale;
Vector2 filler_size = Vector2Scale(Vector2Subtract(screen_size, Vector2Scale(virtual_screen_size, transform->scale)), 0.5);
transform->offset = filler_size;
rlPushMatrix();
rlTranslatef(
filler_size.x,
filler_size.y,
0
);
rlScalef(scale, scale, 1);
}
static void end_window_scaling(struct WindowTransform *transform, Color color)
{
rlPopMatrix();
Vector2 filler_size = transform->offset;
DrawRectangleRec(
(struct Rectangle){
.x = 0,
.y = 0,
.width = GetScreenWidth(),
.height = filler_size.y
},
color
);
DrawRectangleRec(
(struct Rectangle){
.x = 0,
.y = GetScreenHeight() - filler_size.y,
.width = GetScreenWidth(),
.height = filler_size.y
},
color
);
DrawRectangleRec(
(struct Rectangle){
.x = 0,
.y = 0,
.width = filler_size.x,
.height = GetScreenHeight()
},
color
);
DrawRectangleRec(
(struct Rectangle){
.x = GetScreenWidth() - filler_size.x,
.y = 0,
.width = filler_size.x,
.height = GetScreenHeight()
},
color
);
}
int main(void)
{
InitWindow(800, 600, "gaem");
@ -107,18 +31,13 @@ int main(void)
BeginDrawing();
ClearBackground(RAYWHITE);
struct WindowTransform transform = { 0 };
begin_window_scaling(&transform, game.canvas_size);
Vector2 mouse = GetMousePosition();
mouse = Vector2Subtract(mouse, transform.offset);
mouse = Vector2Scale(mouse, 1/transform.scale);
struct WindowScaler scaler = { 0 };
window_scaler_begin(&scaler, game.canvas_size);
game.input = (struct Input){
.dt = dt,
.mouse = mouse
};
game_get_input(&game, &scaler, &game.input);
game_tick(&game);
end_window_scaling(&transform, BLACK);
window_scaler_end(&scaler, BLACK);
game_debug_show(&game);

81
src/rect.c Normal file
View File

@ -0,0 +1,81 @@
#include "rect.h"
float rect_get_left(Rectangle rect)
{
return rect.x;
}
float rect_get_right(Rectangle rect)
{
return rect.x + rect.width;
}
float rect_get_top(Rectangle rect)
{
return rect.y;
}
float rect_get_bottom(Rectangle rect)
{
return rect.y + rect.height;
}
Rectangle rect_add_position(Rectangle rect, Vector2 pos)
{
rect.x += pos.x;
rect.y += pos.y;
return rect;
}
Vector2 rect_get_center(Rectangle rect)
{
return (Vector2){
.x = rect.x + rect.width / 2,
.y = rect.y + rect.height / 2,
};
}
Rectangle rect_grow(Rectangle rect, float x_amount, float y_amount)
{
return rect_grow_y(rect_grow_x(rect, x_amount), y_amount);
}
Rectangle rect_shrink_all(Rectangle rect, float amount)
{
return rect_grow(rect, -amount, -amount);
}
Rectangle rect_grow_x(Rectangle rect, float amount)
{
rect.x -= amount;
rect.width += 2*amount;
return rect;
}
Rectangle rect_grow_y(Rectangle rect, float amount)
{
rect.y -= amount;
rect.height += 2*amount;
return rect;
}
bool rect_is_overlap(Rectangle rect1, Rectangle rect2)
{
return CheckCollisionRecs(rect1, rect2);
}
bool rect_is_inside_rect(Rectangle outer_rect, Rectangle inner_rect)
{
return rect_get_left(outer_rect) <= rect_get_left(inner_rect) &&
rect_get_right(outer_rect) >= rect_get_right(inner_rect) &&
rect_get_top(outer_rect) <= rect_get_top(inner_rect) &&
rect_get_bottom(outer_rect) >= rect_get_bottom(inner_rect);
}
bool rect_is_inside_point(Rectangle outer_rect, Vector2 inner_point)
{
bool is_x_inside = rect_get_left(outer_rect) <= inner_point.x && inner_point.x <= rect_get_right(outer_rect);
bool is_y_inside = rect_get_top(outer_rect) <= inner_point.y && inner_point.y <= rect_get_bottom(outer_rect);
return is_x_inside && is_y_inside;
}

17
src/rect.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include "main.h"
float rect_get_left(Rectangle rect);
float rect_get_right(Rectangle rect);
float rect_get_top(Rectangle rect);
float rect_get_bottom(Rectangle rect);
Rectangle rect_add_position(Rectangle rect, Vector2 pos);
Vector2 rect_get_center(Rectangle rect);
Rectangle rect_grow_x(Rectangle rect, float amount);
Rectangle rect_grow_y(Rectangle rect, float amount);
Rectangle rect_grow(Rectangle rect, float x_amount, float y_amount);
bool rect_is_inside_point(Rectangle outer_rect, Vector2 inner_point);
bool rect_is_inside_rect(Rectangle outer_rect, Rectangle inner_rect);
bool rect_is_overlap(Rectangle rect1, Rectangle rect2);
Rectangle rect_shrink_all(Rectangle rect, float amount);

159
src/timers.c Normal file
View File

@ -0,0 +1,159 @@
#include "timers.h"
static void timers_ensure_capacity(struct Timers *timers, size_t expected_capacity)
{
if (timers->capacity < expected_capacity) {
size_t larger_capacity = MAX(MAX(timers->capacity*2, expected_capacity), 8);
struct Timer *larger_items = realloc(timers->items, larger_capacity * sizeof(*larger_items));
if (larger_items == NULL) {
PANIC_OOM();
}
timers->items = larger_items;
timers->capacity = larger_capacity;
}
}
static TimerId timers_next_id(struct Timers *timers)
{
// Make sure that TIMER_ID_NIL is not used
if (timers->last_id.value == 0) {
timers->last_id.value++;
}
TimerId id = (TimerId){
.value = timers->last_id.value++
};
ASSERT(id.value != TIMER_ID_NIL.value);
return id;
}
static int timers_find_index(struct Timers *timers, TimerId id)
{
for (size_t i = 0; i < timers->len; i++) {
if (timers->items[i].id.value == id.value) {
return i;
}
}
return -1;
}
static void timers_remove(struct Timers *timers, int timer_index)
{
ASSERT(0 <= timer_index && timer_index < (int)timers->len);
ASSERT(timers->len > 0);
timers->len--;
if (timers->len > 0) {
timers->items[timer_index] = timers->items[timers->len];
}
}
TimerId timers_start(struct Timers *timers, double duration)
{
timers_ensure_capacity(timers, timers->len + 1);
struct Timer *timer = &timers->items[timers->len++];
*timer = (struct Timer){
.id = timers_next_id(timers),
.started_at = timers->now,
.finishes_at = timers->now + duration
};
return timer->id;
}
TimerId timers_start_with_entity(struct Timers *timers, double duration, EntityId entity_id)
{
TimerId timer_id = timers_start(timers, duration);
timers_set_entity(timers, timer_id, entity_id);
return timer_id;
}
void timers_set_entity(struct Timers *timers, TimerId timer_id, EntityId entity_id)
{
int timer_index = timers_find_index(timers, timer_id);
ASSERT(timer_index != -1);
struct Timer *timer = &timers->items[timer_index];
timer->entity_id = entity_id;
}
void timers_set_attack(struct Timers *timers, TimerId timer_id)
{
int timer_index = timers_find_index(timers, timer_id);
ASSERT(timer_index != -1);
struct Timer *timer = &timers->items[timer_index];
timer->attack = true;
}
void timers_stop(struct Timers *timers, TimerId timer_id)
{
int timer_index = timers_find_index(timers, timer_id);
if (timer_index == -1) {
return;
}
timers_remove(timers, timer_index);
}
void timers_stop_by_entity(struct Timers *timers, EntityId entity_id)
{
ASSERT(!entity_id_is_nil(entity_id));
size_t index = 0;
while (index < timers->len) {
if (entity_id_eql(timers->items[index].entity_id, entity_id)) {
timers_remove(timers, index);
} else {
index++;
}
}
}
void timers_stop_by_attack(struct Timers *timers)
{
size_t index = 0;
while (index < timers->len) {
if (timers->items[index].attack) {
timers_remove(timers, index);
} else {
index++;
}
}
}
bool timers_finished(struct Timers *timers, TimerId timer_id)
{
int timer_index = timers_find_index(timers, timer_id);
if (timer_index == -1) {
return true;
}
struct Timer *timer = &timers->items[timer_index];
if (timers->now < timer->finishes_at) {
return false;
}
timers_remove(timers, timer_index);
return true;
}
double timers_time_since_start(struct Timers *timers, TimerId id)
{
int timer_index = timers_find_index(timers, id);
if (timer_index == -1) {
return 0;
}
struct Timer *timer = &timers->items[timer_index];
return timers->now - timer->started_at;
}
void timers_free(struct Timers *timers)
{
free(timers->items);
*timers = (struct Timers){ 0 };
}

41
src/timers.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "main.h"
#include "entity_id.h"
typedef struct {
uint32_t value;
} TimerId;
#define TIMER_ID_NIL (TimerId){ 0 }
struct Timer {
TimerId id;
double started_at;
double finishes_at;
EntityId entity_id;
bool attack;
};
struct Timers {
TimerId last_id;
struct Timer *items;
size_t len;
size_t capacity;
double now;
};
TimerId timers_start(struct Timers *timers, double duration);
void timers_stop(struct Timers *timers, TimerId id);
void timers_free(struct Timers *timers);
bool timers_finished(struct Timers *timers, TimerId id);
double timers_time_since_start(struct Timers *timers, TimerId id);
void timers_set_entity(struct Timers *timers, TimerId timer_id, EntityId entity_id);
void timers_stop_by_entity(struct Timers *timers, EntityId entity_id);
TimerId timers_start_with_entity(struct Timers *timers, double duration, EntityId entity_id);
void timers_set_attack(struct Timers *timers, TimerId timer_id);
void timers_stop_by_attack(struct Timers *timers);

73
src/window_scaler.c Normal file
View File

@ -0,0 +1,73 @@
#include "window_scaler.h"
void window_scaler_begin(struct WindowScaler *transform, Vector2 virtual_screen_size)
{
Vector2 screen_size = {
GetScreenWidth(),
GetScreenHeight()
};
float scale = MIN(
screen_size.x / virtual_screen_size.x,
screen_size.y / virtual_screen_size.y
);
transform->scale = scale;
Vector2 filler_size = Vector2Scale(Vector2Subtract(screen_size, Vector2Scale(virtual_screen_size, transform->scale)), 0.5);
transform->offset = filler_size;
rlPushMatrix();
rlTranslatef(
filler_size.x,
filler_size.y,
0
);
rlScalef(scale, scale, 1);
}
void window_scaler_end(struct WindowScaler *transform, Color color)
{
rlPopMatrix();
Vector2 filler_size = transform->offset;
DrawRectangleRec(
(struct Rectangle){
.x = 0,
.y = 0,
.width = GetScreenWidth(),
.height = filler_size.y
},
color
);
DrawRectangleRec(
(struct Rectangle){
.x = 0,
.y = GetScreenHeight() - filler_size.y,
.width = GetScreenWidth(),
.height = filler_size.y
},
color
);
DrawRectangleRec(
(struct Rectangle){
.x = 0,
.y = 0,
.width = filler_size.x,
.height = GetScreenHeight()
},
color
);
DrawRectangleRec(
(struct Rectangle){
.x = GetScreenWidth() - filler_size.x,
.y = 0,
.width = filler_size.x,
.height = GetScreenHeight()
},
color
);
}

11
src/window_scaler.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "main.h"
struct WindowScaler {
Vector2 offset;
float scale;
};
void window_scaler_begin(struct WindowScaler *transform, Vector2 virtual_screen_size);
void window_scaler_end(struct WindowScaler *transform, Color color);