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
|
||||
|
||||
#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;
|
||||
};
|
||||
|
||||
498
src/game.c
498
src/game.c
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
37
src/game.h
37
src/game.h
@ -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
|
||||
);
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
93
src/main.c
93
src/main.c
@ -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
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