add damage
This commit is contained in:
parent
2230bab37a
commit
c20a4b6cad
10
src/entity.h
10
src/entity.h
@ -1,16 +1,20 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "timers.h"
|
||||||
|
|
||||||
enum EntityType {
|
enum EntityType {
|
||||||
ENTITY_TYPE_NIL = 0,
|
ENTITY_TYPE_NIL = 0,
|
||||||
ENTITY_TYPE_PLAYER,
|
ENTITY_TYPE_PLAYER,
|
||||||
ENTITY_TYPE_WALL
|
ENTITY_TYPE_DAMAGE,
|
||||||
|
ENTITY_TYPE_WALL,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EntityFlags {
|
struct EntityFlags {
|
||||||
bool has_friction : 1;
|
bool has_friction : 1;
|
||||||
bool has_max_speed : 1;
|
bool has_max_speed : 1;
|
||||||
|
|
||||||
|
bool remove_offscsreen : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
@ -25,5 +29,9 @@ struct Entity {
|
|||||||
// TODO: Remove this, because this is just a temporary value for resolving collisions
|
// TODO: Remove this, because this is just a temporary value for resolving collisions
|
||||||
Vector2 step;
|
Vector2 step;
|
||||||
|
|
||||||
|
uint32_t health;
|
||||||
|
|
||||||
|
TimerId invincibility_timer;
|
||||||
|
|
||||||
Rectangle collision_rect;
|
Rectangle collision_rect;
|
||||||
};
|
};
|
||||||
|
|||||||
494
src/game.c
494
src/game.c
@ -1,17 +1,26 @@
|
|||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "entity_id_list.h"
|
#include "entity_id_list.h"
|
||||||
#include "float_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)
|
void game_init(struct Game *game)
|
||||||
{
|
{
|
||||||
*game = (struct Game){
|
*game = (struct Game){
|
||||||
.canvas_size = { 800, 600 }
|
.canvas_size = { 800, 600 },
|
||||||
|
.show_damage_rects = true
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
struct Entity *player = entity_list_add(&game->entities);
|
struct Entity *player = entity_list_add(&game->entities);
|
||||||
player->type = ENTITY_TYPE_PLAYER;
|
player->type = ENTITY_TYPE_PLAYER;
|
||||||
player->position = Vector2Scale(game->canvas_size, 0.5);
|
player->position = Vector2Scale(game->canvas_size, 0.5);
|
||||||
|
player->health = MAX_PLAYER_HEALTH;
|
||||||
player->collision_rect = (Rectangle){
|
player->collision_rect = (Rectangle){
|
||||||
.x = -10,
|
.x = -10,
|
||||||
.y = -10,
|
.y = -10,
|
||||||
@ -20,10 +29,10 @@ void game_init(struct Game *game)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
float wall_thickness = 10;
|
float wall_thickness = 50;
|
||||||
|
|
||||||
// Canvas bounds
|
// Canvas bounds
|
||||||
{
|
if (false) {
|
||||||
game_add_wall(game, (Rectangle){
|
game_add_wall(game, (Rectangle){
|
||||||
.x = -wall_thickness,
|
.x = -wall_thickness,
|
||||||
.y = -wall_thickness,
|
.y = -wall_thickness,
|
||||||
@ -52,74 +61,15 @@ void game_init(struct Game *game)
|
|||||||
.height = game->canvas_size.y + 2*wall_thickness
|
.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)
|
void game_free(struct Game *game)
|
||||||
{
|
{
|
||||||
entity_list_free(&game->entities);
|
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);
|
struct Entity *wall = entity_list_add(&game->entities);
|
||||||
|
|
||||||
@ -133,32 +83,7 @@ EntityId game_add_wall(struct Game *game, Rectangle rect)
|
|||||||
.height = rect.height
|
.height = rect.height
|
||||||
};
|
};
|
||||||
|
|
||||||
return entity_list_get_id(&game->entities, wall);
|
return 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CollisionResult {
|
struct CollisionResult {
|
||||||
@ -228,13 +153,6 @@ static bool game_swept_aabb(Rectangle b1, Vector2 v1, Rectangle b2, struct Colli
|
|||||||
return true;
|
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)
|
static Rectangle rect_get_step_bounds(Rectangle rect, Vector2 step)
|
||||||
{
|
{
|
||||||
if (step.x >= 0) {
|
if (step.x >= 0) {
|
||||||
@ -254,14 +172,6 @@ static Rectangle rect_get_step_bounds(Rectangle rect, Vector2 step)
|
|||||||
return rect;
|
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)
|
static void game_list_nearest_walls(struct Game *game, struct EntityIdList *walls, Rectangle bounds, struct EntityIdList *result)
|
||||||
{
|
{
|
||||||
struct FloatList distances = { 0 };
|
struct FloatList distances = { 0 };
|
||||||
@ -273,7 +183,7 @@ static void game_list_nearest_walls(struct Game *game, struct EntityIdList *wall
|
|||||||
ASSERT(wall != NULL);
|
ASSERT(wall != NULL);
|
||||||
|
|
||||||
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
|
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(
|
float distance_sqr = Vector2DistanceSqr(
|
||||||
rect_get_center(wall_collider),
|
rect_get_center(wall_collider),
|
||||||
rect_get_center(bounds)
|
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)
|
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 };
|
Vector2 dir = { 0 };
|
||||||
if (IsKeyDown(KEY_W)) {
|
if (IsKeyDown(KEY_W)) {
|
||||||
@ -369,12 +442,42 @@ void game_tick(struct Game *game)
|
|||||||
}
|
}
|
||||||
dir = Vector2Normalize(dir);
|
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;
|
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;
|
struct Entity *entity = NULL;
|
||||||
entity_list_foreach(&game->entities, entity) {
|
entity_list_foreach(&game->entities, entity) {
|
||||||
if (entity->type == ENTITY_TYPE_PLAYER) {
|
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));
|
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->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 walls = { 0 };
|
||||||
struct EntityIdList players = { 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]);
|
struct Entity *wall = entity_list_get(&game->entities, walls.items[i]);
|
||||||
ASSERT(wall != NULL);
|
ASSERT(wall != NULL);
|
||||||
|
|
||||||
for (size_t j = 0; j < players.len; j++) {
|
game_collide_push_players(
|
||||||
struct Entity *player = entity_list_get(&game->entities, players.items[j]);
|
game,
|
||||||
ASSERT(player != NULL);
|
wall,
|
||||||
|
&players
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wall->position = Vector2Add(wall->position, wall->step);
|
wall->position = Vector2Add(wall->position, wall->step);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < players.len; i++) {
|
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);
|
ASSERT(player != NULL);
|
||||||
|
|
||||||
game_collide_against_walls(
|
game_collide_against_walls(
|
||||||
@ -445,69 +550,98 @@ void game_tick(struct Game *game)
|
|||||||
&player->step
|
&player->step
|
||||||
);
|
);
|
||||||
|
|
||||||
for (size_t j = 0; j < walls.len; j++) {
|
bool apply_damage = false;
|
||||||
struct Entity *wall = entity_list_get(&game->entities, walls.items[j]);
|
for (int j = 0; j < 2; j++) {
|
||||||
ASSERT(wall != NULL);
|
if (game_collide_nudge_out_of_walls(
|
||||||
|
game,
|
||||||
Rectangle player_collider = rect_add_position(player->collision_rect, Vector2Add(player->position, player->step));
|
rect_add_position(player->collision_rect, player->position),
|
||||||
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
|
&player->step,
|
||||||
|
&walls,
|
||||||
if (game_aabb(player_collider, wall_collider)) {
|
10
|
||||||
Vector2 player_center = rect_get_center(player_collider);
|
)) {
|
||||||
Vector2 wall_center = rect_get_center(wall_collider);
|
apply_damage = true;
|
||||||
|
}
|
||||||
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) {
|
Rectangle player_rect = rect_add_position(player->collision_rect, Vector2Add(player->position, player->step));
|
||||||
y_overlap = MIN(rect_get_top(wall_collider) - rect_get_bottom(player_collider), 0);
|
Rectangle canvas_rect = {
|
||||||
} else {
|
.width = game->canvas_size.x,
|
||||||
y_overlap = MAX(rect_get_bottom(wall_collider) - rect_get_top(player_collider), 0);
|
.height = game->canvas_size.y,
|
||||||
}
|
};
|
||||||
|
if (!rect_is_inside_rect(canvas_rect, player_rect)) {
|
||||||
float overlap_size = 0;
|
Vector2 nudge = game_get_nudge_to_inside(canvas_rect, player_rect);
|
||||||
if (fabs(x_overlap) < fabs(y_overlap)) {
|
player->step = Vector2Add(player->step, nudge);
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
player->position = Vector2Add(player->position, player->step);
|
player->position = Vector2Add(player->position, player->step);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < walls.len; i++) {
|
if (apply_damage) {
|
||||||
struct Entity *wall = entity_list_get(&game->entities, walls.items[i]);
|
if (timers_finished(&game->timers, player->invincibility_timer)) {
|
||||||
ASSERT(wall != NULL);
|
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) {
|
entity_list_foreach(&game->entities, entity) {
|
||||||
if (entity->type == ENTITY_TYPE_PLAYER) {
|
if (entity->type == ENTITY_TYPE_PLAYER) {
|
||||||
Vector2 size = (Vector2){ 20, 20 };
|
Vector2 size = (Vector2){ 20, 20 };
|
||||||
DrawRectangleV(Vector2Subtract(entity->position, Vector2Scale(size, 0.5)), size, RED);
|
Color color = GREEN;
|
||||||
DrawCircleV(entity->position, 4, BLACK);
|
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 (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) {
|
if (entity->type == ENTITY_TYPE_WALL || entity->type == ENTITY_TYPE_PLAYER) {
|
||||||
Rectangle rect = entity->collision_rect;
|
game_draw_rect_outlined(
|
||||||
rect.x += entity->position.x;
|
rect_add_position(entity->collision_rect, entity->position),
|
||||||
rect.y += entity->position.y;
|
BLUE
|
||||||
Color color = BLUE;
|
);
|
||||||
DrawRectangleRec(rect, ColorAlpha(color, 0.5));
|
}
|
||||||
DrawRectangleLinesEx(rect, 2, ColorAlpha(color, 0.5));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
src/game.h
37
src/game.h
@ -2,20 +2,55 @@
|
|||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "entity_list.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 {
|
struct Input {
|
||||||
float dt;
|
float dt;
|
||||||
Vector2 mouse;
|
Vector2 mouse;
|
||||||
|
Vector2 move_dir;
|
||||||
|
bool focus;
|
||||||
|
bool restart;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Game {
|
struct Game {
|
||||||
Vector2 canvas_size;
|
Vector2 canvas_size;
|
||||||
struct EntityList entities;
|
struct EntityList entities;
|
||||||
|
struct Timers timers;
|
||||||
|
|
||||||
struct Input input;
|
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_init(struct Game *game);
|
||||||
void game_free(struct Game *game);
|
void game_free(struct Game *game);
|
||||||
void game_tick(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
|
||||||
|
);
|
||||||
|
|||||||
@ -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);
|
snprintf(label, sizeof(label), "FPS: %.1f (%.2fms)", 1.0f / game->input.dt, game->input.dt * 1000);
|
||||||
igTextEx(label, NULL, ImGuiTextFlags_None);
|
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();
|
igEnd();
|
||||||
|
|
||||||
|
|||||||
93
src/main.c
93
src/main.c
@ -2,86 +2,10 @@
|
|||||||
#include "resources.h"
|
#include "resources.h"
|
||||||
#include "game.h"
|
#include "game.h"
|
||||||
#include "game_debug.h"
|
#include "game_debug.h"
|
||||||
|
#include "window_scaler.h"
|
||||||
|
|
||||||
struct Resources g_resources = { 0 };
|
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)
|
int main(void)
|
||||||
{
|
{
|
||||||
InitWindow(800, 600, "gaem");
|
InitWindow(800, 600, "gaem");
|
||||||
@ -107,18 +31,13 @@ int main(void)
|
|||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
|
|
||||||
struct WindowTransform transform = { 0 };
|
struct WindowScaler scaler = { 0 };
|
||||||
begin_window_scaling(&transform, game.canvas_size);
|
window_scaler_begin(&scaler, game.canvas_size);
|
||||||
Vector2 mouse = GetMousePosition();
|
|
||||||
mouse = Vector2Subtract(mouse, transform.offset);
|
|
||||||
mouse = Vector2Scale(mouse, 1/transform.scale);
|
|
||||||
|
|
||||||
game.input = (struct Input){
|
game_get_input(&game, &scaler, &game.input);
|
||||||
.dt = dt,
|
|
||||||
.mouse = mouse
|
|
||||||
};
|
|
||||||
game_tick(&game);
|
game_tick(&game);
|
||||||
end_window_scaling(&transform, BLACK);
|
|
||||||
|
window_scaler_end(&scaler, BLACK);
|
||||||
|
|
||||||
game_debug_show(&game);
|
game_debug_show(&game);
|
||||||
|
|
||||||
|
|||||||
81
src/rect.c
Normal file
81
src/rect.c
Normal 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
17
src/rect.h
Normal 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
159
src/timers.c
Normal 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
41
src/timers.h
Normal 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
73
src/window_scaler.c
Normal 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
11
src/window_scaler.h
Normal 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);
|
||||||
Loading…
Reference in New Issue
Block a user