improve collision stability

This commit is contained in:
Rokas Puzonas 2025-12-07 14:48:36 +02:00
parent c3a4a06403
commit 2230bab37a
3 changed files with 140 additions and 48 deletions

View File

@ -1,10 +1,6 @@
#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)
{
@ -66,6 +62,13 @@ void game_init(struct Game *game)
});
}
game_add_wall(game, (Rectangle){
.x = 300,
.y = 10,
.width = 40,
.height = 40
});
{
EntityId wall_id = game_add_wall(game, (Rectangle){
.x = 10,
@ -87,6 +90,28 @@ void game_init(struct Game *game)
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)
@ -116,6 +141,26 @@ 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 {
float time;
Vector2 normal;
@ -123,59 +168,61 @@ struct CollisionResult {
static bool game_swept_aabb(Rectangle b1, Vector2 v1, Rectangle b2, struct CollisionResult *result)
{
float entryX, exitX;
float entry_x, exit_x;
if (v1.x > 0) {
entryX = b2.x - (b1.x + b1.width);
exitX = (b2.x + b2.width) - b1.x;
entry_x = rect_get_left(b2) - rect_get_right(b1);
exit_x = rect_get_right(b2) - rect_get_left(b1);
} else {
entryX = (b2.x + b2.width) - b1.x;
exitX = b2.x - (b1.x + b1.width);
entry_x = rect_get_right(b2) - rect_get_left(b1);
exit_x = rect_get_left(b2) - rect_get_right(b1);
}
float entryY, exitY;
float entry_y, exit_y;
if (v1.y > 0) {
entryY = b2.y - (b1.y + b1.height);
exitY = (b2.y + b2.height) - b1.y;
entry_y = rect_get_top(b2) - rect_get_bottom(b1);
exit_y = rect_get_bottom(b2) - rect_get_top(b1);
} else {
entryY = (b2.y + b2.height) - b1.y;
exitY = b2.y - (b1.y + b1.height);
entry_y = rect_get_bottom(b2) - rect_get_top(b1);
exit_y = rect_get_top(b2) - rect_get_bottom(b1);
}
float entryXTime, exitXTime;
float entry_x_time, exit_x_time;
if (v1.x == 0) {
entryXTime = -INFINITY;
exitXTime = INFINITY;
entry_x_time = -INFINITY;
exit_x_time = INFINITY;
} else {
entryXTime = entryX / v1.x;
exitXTime = exitX / v1.x;
entry_x_time = entry_x / v1.x;
exit_x_time = exit_x / v1.x;
}
float entryYTime, exitYTime;
float entry_y_time, exit_y_time;
if (v1.y == 0) {
entryYTime = -INFINITY;
exitYTime = INFINITY;
entry_y_time = -INFINITY;
exit_y_time = INFINITY;
} else {
entryYTime = entryY / v1.y;
exitYTime = exitY / v1.y;
entry_y_time = entry_y / v1.y;
exit_y_time = exit_y / v1.y;
}
float entryTime = MAX(entryXTime, entryYTime);
float exitTime = MIN(exitXTime, exitYTime);
float entry_time = MAX(entry_x_time, entry_y_time);
float exit_time = MIN(exit_x_time, exit_y_time);
if (entryTime > exitTime || (entryXTime < 0 && entryYTime < 0) || entryXTime > 1.0f || entryYTime > 1.0f) {
if (entry_time > exit_time ||
(entry_x_time < 0 && entry_y_time < 0) ||
entry_x_time > 1.0f ||
entry_y_time > 1.0f
) {
return false;
}
ASSERT(0 <= entryTime && entryTime <= 1);
*result = (struct CollisionResult){
.time = entryTime,
.time = entry_time,
};
if (entryXTime > entryYTime) {
result->normal.x = entryX < 0.0 ? 1 : -1;
if (entry_x_time > entry_y_time) {
result->normal.x = (exit_x - entry_x) < 0.0 ? 1 : -1;
} else {
result->normal.y = entryY < 0.0 ? 1 : -1;
result->normal.y = (exit_y - entry_y) < 0.0 ? 1 : -1;
}
return true;
@ -303,16 +350,6 @@ static void game_collide_against_walls(struct Game *game, struct EntityIdList *w
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)
{
TracyCZoneN(game_tick, "game_tick", true);
@ -369,8 +406,8 @@ void game_tick(struct Game *game)
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]);
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);
@ -391,9 +428,6 @@ void game_tick(struct Game *game)
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;
}
}
@ -411,9 +445,55 @@ 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);
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
}
}
}
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);
}
entity_list_foreach(&game->entities, entity) {
if (entity->type == ENTITY_TYPE_PLAYER) {
Vector2 size = (Vector2){ 20, 20 };

View File

@ -36,3 +36,4 @@ extern struct Resources g_resources;
Color rgb(uint8_t r, uint8_t g, uint8_t b);
Color hex(const char *str);
int signf(float value);

View File

@ -37,3 +37,14 @@ Color hex(const char *str)
uint8_t b = parse_hex_byte(&str[5]);
return rgb(r, g, b);
}
int signf(float value)
{
if (value > 0) {
return 1;
} else if (value < 0) {
return -1;
} else {
return 0;
}
}