add collisions

This commit is contained in:
Rokas Puzonas 2025-12-07 12:32:09 +02:00
parent 937693b16e
commit c3a4a06403
9 changed files with 515 additions and 7 deletions

29
src/entity.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include "main.h"
enum EntityType {
ENTITY_TYPE_NIL = 0,
ENTITY_TYPE_PLAYER,
ENTITY_TYPE_WALL
};
struct EntityFlags {
bool has_friction : 1;
bool has_max_speed : 1;
};
struct Entity {
enum EntityType type;
struct EntityFlags flags;
Vector2 position;
Vector2 velocity;
Vector2 accelaration;
float max_speed;
float friction;
// TODO: Remove this, because this is just a temporary value for resolving collisions
Vector2 step;
Rectangle collision_rect;
};

12
src/entity_id_list.c Normal file
View File

@ -0,0 +1,12 @@
#include "entity_id_list.h"
DEFINE_VECTOR_ENSURE_METHOD_CAPACITY(entity_id_list_ensure_capacity, struct EntityIdList, EntityId);
DEFINE_VECTOR_METHOD_FREE(entity_id_list_free, struct EntityIdList);
DEFINE_VECTOR_METHOD_FREE(entity_id_list_clear_retaining_capacity, struct EntityIdList);
void entity_id_list_add(struct EntityIdList *list, EntityId id)
{
entity_id_list_ensure_capacity(list, list->len + 1);
list->items[list->len++] = id;
}

11
src/entity_id_list.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "main.h"
#include "entity_id.h"
#include "generic_vector.h"
DEFINE_VECTOR_STRUCT(EntityIdList, EntityId);
void entity_id_list_add(struct EntityIdList *list, EntityId id);
void entity_id_list_free(struct EntityIdList *list);
void entity_id_list_clear_retaining_capacity(struct EntityIdList *list);

View File

@ -2,9 +2,7 @@
#include "main.h" #include "main.h"
#include "entity_id.h" #include "entity_id.h"
#include "entity.h"
struct Entity {
};
struct EntitySlot { struct EntitySlot {
bool used; bool used;

12
src/float_list.c Normal file
View File

@ -0,0 +1,12 @@
#include "float_list.h"
DEFINE_VECTOR_ENSURE_METHOD_CAPACITY(float_list_ensure_capacity, struct FloatList, float);
DEFINE_VECTOR_METHOD_FREE(float_list_free, struct FloatList);
DEFINE_VECTOR_METHOD_FREE(float_list_clear_retaining_capacity, struct FloatList);
void float_list_add(struct FloatList *list, float value)
{
float_list_ensure_capacity(list, list->len + 1);
list->items[list->len++] = value;
}

8
src/float_list.h Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#include "generic_vector.h"
DEFINE_VECTOR_STRUCT(FloatList, float);
void float_list_add(struct FloatList *list, float value);
void float_list_free(struct FloatList *list);

View File

@ -1,14 +1,316 @@
#include "game.h" #include "game.h"
#include "entity.h"
#include "entity_id_list.h"
#include "float_list.h"
#include "raymath.h"
#include <raylib.h>
#include <stdio.h>
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 }
}; };
{
struct Entity *player = entity_list_add(&game->entities);
player->type = ENTITY_TYPE_PLAYER;
player->position = Vector2Scale(game->canvas_size, 0.5);
player->collision_rect = (Rectangle){
.x = -10,
.y = -10,
.width = 20,
.height = 20
};
}
float wall_thickness = 10;
// Canvas bounds
{
game_add_wall(game, (Rectangle){
.x = -wall_thickness,
.y = -wall_thickness,
.width = game->canvas_size.x + 2*wall_thickness,
.height = wall_thickness
});
game_add_wall(game, (Rectangle){
.x = -wall_thickness,
.y = game->canvas_size.y,
.width = game->canvas_size.x + 2*wall_thickness,
.height = wall_thickness
});
game_add_wall(game, (Rectangle){
.x = -wall_thickness,
.y = -wall_thickness,
.width = wall_thickness,
.height = game->canvas_size.y + 2*wall_thickness
});
game_add_wall(game, (Rectangle){
.x = game->canvas_size.x,
.y = -wall_thickness,
.width = 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
});
}
{
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;
}
} }
void game_free(struct Game *game) void game_free(struct Game *game)
{ {
entity_list_free(&game->entities);
}
EntityId game_add_wall(struct Game *game, Rectangle rect)
{
struct Entity *wall = entity_list_add(&game->entities);
wall->type = ENTITY_TYPE_WALL;
wall->position = (Vector2){
.x = rect.x,
.y = rect.y,
};
wall->collision_rect = (Rectangle){
.width = rect.width,
.height = rect.height
};
return entity_list_get_id(&game->entities, wall);
}
static float game_aabb(Rectangle b1, Rectangle b2)
{
return CheckCollisionRecs(b1, b2);
}
struct CollisionResult {
float time;
Vector2 normal;
};
static bool game_swept_aabb(Rectangle b1, Vector2 v1, Rectangle b2, struct CollisionResult *result)
{
float entryX, exitX;
if (v1.x > 0) {
entryX = b2.x - (b1.x + b1.width);
exitX = (b2.x + b2.width) - b1.x;
} else {
entryX = (b2.x + b2.width) - b1.x;
exitX = b2.x - (b1.x + b1.width);
}
float entryY, exitY;
if (v1.y > 0) {
entryY = b2.y - (b1.y + b1.height);
exitY = (b2.y + b2.height) - b1.y;
} else {
entryY = (b2.y + b2.height) - b1.y;
exitY = b2.y - (b1.y + b1.height);
}
float entryXTime, exitXTime;
if (v1.x == 0) {
entryXTime = -INFINITY;
exitXTime = INFINITY;
} else {
entryXTime = entryX / v1.x;
exitXTime = exitX / v1.x;
}
float entryYTime, exitYTime;
if (v1.y == 0) {
entryYTime = -INFINITY;
exitYTime = INFINITY;
} else {
entryYTime = entryY / v1.y;
exitYTime = exitY / v1.y;
}
float entryTime = MAX(entryXTime, entryYTime);
float exitTime = MIN(exitXTime, exitYTime);
if (entryTime > exitTime || (entryXTime < 0 && entryYTime < 0) || entryXTime > 1.0f || entryYTime > 1.0f) {
return false;
}
ASSERT(0 <= entryTime && entryTime <= 1);
*result = (struct CollisionResult){
.time = entryTime,
};
if (entryXTime > entryYTime) {
result->normal.x = entryX < 0.0 ? 1 : -1;
} else {
result->normal.y = entryY < 0.0 ? 1 : -1;
}
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) {
rect.width += step.x;
} else {
rect.x += step.x;
rect.width -= step.x;
}
if (step.y >= 0) {
rect.height += step.y;
} else {
rect.y += step.y;
rect.height -= step.y;
}
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 };
entity_id_list_clear_retaining_capacity(result);
for (size_t i = 0; i < walls->len; i++) {
EntityId wall_id = walls->items[i];
struct Entity *wall = entity_list_get(&game->entities, wall_id);
ASSERT(wall != NULL);
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
if (game_aabb(wall_collider, bounds)) {
float distance_sqr = Vector2DistanceSqr(
rect_get_center(wall_collider),
rect_get_center(bounds)
);
entity_id_list_add(result, wall_id);
float_list_add(&distances, distance_sqr);
}
}
for (int i = 0; i < (int)result->len - 1; i++) {
for (int j = i + 1; j < (int)result->len; j++) {
if (distances.items[i] < distances.items[j]) {
{
float tmp = distances.items[i];
distances.items[i] = distances.items[j];
distances.items[j] = tmp;
}
{
EntityId tmp = result->items[i];
result->items[i] = result->items[j];
result->items[j] = tmp;
}
}
}
}
float_list_free(&distances);
}
static void game_collision_response_slide(Vector2 *step, struct CollisionResult collision)
{
Vector2 new_step = Vector2Scale(*step, collision.time);
float remaining_time = 1 - collision.time;
float dotprod = (step->x * collision.normal.y + step->y * collision.normal.x) * remaining_time;
new_step.x += dotprod * collision.normal.y;
new_step.y += dotprod * collision.normal.x;
*step = new_step;
}
static void game_collide_against_walls(struct Game *game, struct EntityIdList *walls, Rectangle collider, Vector2 *step)
{
TracyCZoneN(game_collide_against_walls, "game_collide_against_walls", true);
struct EntityIdList nearest_walls = { 0 };
game_list_nearest_walls(
game,
walls,
rect_get_step_bounds(
collider,
*step
),
&nearest_walls
);
for (size_t i = 0; i < nearest_walls.len; i++) {
struct Entity *wall = entity_list_get(&game->entities, nearest_walls.items[i]);
ASSERT(wall != NULL);
Rectangle wall_collider = rect_add_position(wall->collision_rect, wall->position);
struct CollisionResult collision = { 0 };
if (!game_swept_aabb(collider, *step, wall_collider, &collision)) {
continue;
}
game_collision_response_slide(step, collision);
}
entity_id_list_free(&nearest_walls);
TracyCZoneEnd(game_collide_against_walls)
}
static int signf(float value) {
if (value > 0) {
return 1;
} else if (value < 0) {
return -1;
} else {
return 0;
}
} }
void game_tick(struct Game *game) void game_tick(struct Game *game)
@ -28,10 +330,109 @@ void game_tick(struct Game *game)
if (IsKeyDown(KEY_A)) { if (IsKeyDown(KEY_A)) {
dir.x -= 1; dir.x -= 1;
} }
dir = Vector2Normalize(dir);
game->player_position = Vector2Add(game->player_position, Vector2Scale(dir, 50 * game->input.dt)); float dt = game->input.dt;
DrawRectangleV(game->player_position, (Vector2){ 50, 50 }, RED); struct Entity *entity = NULL;
entity_list_foreach(&game->entities, entity) {
if (entity->type == ENTITY_TYPE_PLAYER) {
entity->velocity = Vector2Scale(dir, 200);
}
entity->velocity = Vector2Add(entity->velocity, Vector2Scale(entity->accelaration, dt));
if (entity->flags.has_max_speed) {
entity->velocity = Vector2ClampValue(entity->velocity, 0, entity->max_speed);
}
if (entity->flags.has_friction) {
float friction_force = pow(1 - entity->friction, dt);
entity->velocity = Vector2Scale(entity->velocity, friction_force);
}
entity->step = Vector2Scale(entity->velocity, dt);
}
struct EntityIdList walls = { 0 };
struct EntityIdList players = { 0 };
entity_list_foreach(&game->entities, entity) {
EntityId entity_id = entity_list_get_id(&game->entities, entity);
if (entity->type == ENTITY_TYPE_WALL) {
entity_id_list_add(&walls, entity_id);
} else if (entity->type == ENTITY_TYPE_PLAYER) {
entity_id_list_add(&players, entity_id);
} else {
entity->position = Vector2Add(entity->position, entity->step);
}
}
for (size_t i = 0; i < walls.len; i++) {
struct Entity *wall = entity_list_get(&game->entities, walls.items[i]);
ASSERT(wall != NULL);
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 (!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);
player->step.x -= collision.normal.x * 0.001;
player->step.y -= collision.normal.y * 0.001;
}
}
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]);
ASSERT(player != NULL);
game_collide_against_walls(
game,
&walls,
rect_add_position(player->collision_rect, player->position),
&player->step
);
player->position = Vector2Add(player->position, player->step);
}
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);
DrawCircleV(entity->position, 4, BLACK);
}
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));
}
}
entity_id_list_free(&walls);
entity_id_list_free(&players);
TracyCZoneEnd(game_tick) TracyCZoneEnd(game_tick)
} }

View File

@ -12,11 +12,10 @@ struct Game {
Vector2 canvas_size; Vector2 canvas_size;
struct EntityList entities; struct EntityList entities;
Vector2 player_position;
struct Input input; struct Input input;
}; };
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);

38
src/generic_vector.h Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include "main.h"
#define DEFINE_VECTOR_STRUCT(vector_name, item_t) \
struct vector_name { \
item_t *items; \
size_t len; \
size_t capacity; \
};
#define DEFINE_VECTOR_ENSURE_METHOD_CAPACITY(func_name, list_t, item_t) \
void func_name(list_t *list, size_t expected_capacity) \
{ \
if (list->capacity < expected_capacity) { \
size_t larger_capacity = MAX(MAX(list->capacity*2, expected_capacity), 4); \
item_t *larger_items = realloc(list->items, larger_capacity * sizeof(item_t)); \
if (larger_items == NULL) { \
PANIC_OOM(); \
} \
list->items = larger_items; \
list->capacity = larger_capacity; \
} \
}
#define DEFINE_VECTOR_METHOD_FREE(func_name, list_t) \
void func_name(list_t *list) \
{ \
free(list->items); \
list->len = 0; \
list->capacity = 0; \
}
#define DEFINE_VECTOR_METHOD_CLEAR_RETAINING_CAPACITY(func_name, list_t) \
void func_name(list_t *list) \
{ \
list->len = 0; \
}