better, teleport-based collision resolution
This commit is contained in:
parent
48700ccfa3
commit
4a513cf1f5
|
@ -68,22 +68,18 @@ void mechanical_move(entity_t *e, vec2 direction, fixed_t dt, map_t const *map)
|
|||
if(facing >= 0)
|
||||
p->facing = facing;
|
||||
|
||||
fixed_t new_x = p->x + fmul(m->vx, dt);
|
||||
fixed_t new_y = p->y + fmul(m->vy, dt);
|
||||
rect new_hitbox = rect_translate(p->hitbox, (vec2){ new_x, new_y });
|
||||
vec2 new_pos = { p->x + fmul(m->vx, dt), p->y + fmul(m->vy, dt) };
|
||||
new_pos = map_move(map, (vec2){ p->x, p->y }, new_pos, p->hitbox);
|
||||
|
||||
// TODO ECS: New collision/ejection system based on teleports
|
||||
if(!map_collides(map, new_hitbox)) {
|
||||
if(f && f->current_attack && f->attack_follows_movement) {
|
||||
physical_t *attack = getcomp(f->current_attack, physical);
|
||||
attack->x += (new_x - p->x);
|
||||
attack->y += (new_y - p->y);
|
||||
}
|
||||
|
||||
p->x = new_x;
|
||||
p->y = new_y;
|
||||
if(f && f->current_attack && f->attack_follows_movement) {
|
||||
physical_t *attack = getcomp(f->current_attack, physical);
|
||||
attack->x += (new_pos.x - p->x);
|
||||
attack->y += (new_pos.y - p->y);
|
||||
}
|
||||
|
||||
p->x = new_pos.x;
|
||||
p->y = new_pos.y;
|
||||
|
||||
/* TODO: Without acceleration, the movement model is broken */
|
||||
m->vdx = fmul(m->vdx, fix(0.8));
|
||||
m->vdy = fmul(m->vdy, fix(0.8));
|
||||
|
|
|
@ -86,3 +86,8 @@ static inline fixed_t fease(fixed_t x)
|
|||
return fix(1) - 2 * fmul(x, x);
|
||||
}
|
||||
}
|
||||
|
||||
static inline fixed_t fclamp(fixed_t x, fixed_t min, fixed_t max)
|
||||
{
|
||||
return (x <= min) ? min : (x >= max) ? max : x;
|
||||
}
|
||||
|
|
95
src/map.c
95
src/map.c
|
@ -1,5 +1,6 @@
|
|||
#include "map.h"
|
||||
#include <stdlib.h>
|
||||
#include <gint/defs/util.h>
|
||||
|
||||
rect tile_shape(tileset_t const *tileset, int id)
|
||||
{
|
||||
|
@ -22,6 +23,18 @@ map_cell_t *map_cell(map_t const *m, int x, int y)
|
|||
return &m->cells[y * m->width + x];
|
||||
}
|
||||
|
||||
bool map_cell_walkable(map_t const *m, map_cell_t const *cell)
|
||||
{
|
||||
if(!cell)
|
||||
return false;
|
||||
if(m->tileset->tiles[cell->base].solid)
|
||||
return false;
|
||||
if(cell->decor && m->tileset->tiles[cell->decor].solid)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool map_collides(map_t const *m, rect hitbox)
|
||||
{
|
||||
int y_min = ffloor(hitbox.t);
|
||||
|
@ -52,3 +65,85 @@ bool map_collides(map_t const *m, rect hitbox)
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
vec2 map_move(map_t const *m, vec2 old_pos, vec2 new_pos, rect hitbox)
|
||||
{
|
||||
int x = ffloor(new_pos.x);
|
||||
int y = ffloor(new_pos.y);
|
||||
|
||||
/* If there is no collision, accept immediately */
|
||||
if(!map_collides(m, rect_translate(hitbox, new_pos)))
|
||||
return new_pos;
|
||||
|
||||
/* Find the nearest walkable cell */
|
||||
fixed_t best_d2 = fix(999);
|
||||
fixed_t best_x = fix(-1), best_y = fix(-1);
|
||||
|
||||
for(int cy = y-1; cy <= y+1; cy++)
|
||||
for(int cx = x-1; cx <= x+1; cx++) {
|
||||
map_cell_t *cell = map_cell(m, cx, cy);
|
||||
if(!map_cell_walkable(m, cell))
|
||||
continue;
|
||||
|
||||
fixed_t closest_x = fclamp(new_pos.x, fix(cx), fix(cx+1)-1);
|
||||
fixed_t closest_y = fclamp(new_pos.y, fix(cy), fix(cy+1)-1);
|
||||
|
||||
fixed_t d2 = dist2(new_pos, (vec2){ closest_x, closest_y });
|
||||
if(d2 < best_d2) {
|
||||
best_d2 = d2;
|
||||
best_x = closest_x;
|
||||
best_y = closest_y;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there is no walkable cell, prevent movement */
|
||||
if(best_x < 0 || best_y < 0)
|
||||
return old_pos;
|
||||
|
||||
/* In that cell, clamp to the limits imposed by neighboring cells */
|
||||
x = ffloor(best_x);
|
||||
y = ffloor(best_y);
|
||||
fixed_t nx = fdec(best_x);
|
||||
fixed_t ny = fdec(best_y);
|
||||
|
||||
/* Limits at edges */
|
||||
if(!map_cell_walkable(m, map_cell(m, x-1, y)))
|
||||
nx = max(nx, -hitbox.l);
|
||||
if(!map_cell_walkable(m, map_cell(m, x+1, y)))
|
||||
nx = min(nx, fix(1) - hitbox.r);
|
||||
if(!map_cell_walkable(m, map_cell(m, x, y-1)))
|
||||
ny = max(ny, -hitbox.t);
|
||||
if(!map_cell_walkable(m, map_cell(m, x, y+1)))
|
||||
ny = min(ny, fix(1) - hitbox.b);
|
||||
|
||||
/* Limits at corners */
|
||||
if(!map_cell_walkable(m, map_cell(m, x-1, y-1))
|
||||
&& nx < -hitbox.l && ny < -hitbox.t) {
|
||||
nx = -hitbox.l;
|
||||
ny = -hitbox.t;
|
||||
}
|
||||
if(!map_cell_walkable(m, map_cell(m, x+1, y-1))
|
||||
&& nx > fix(1) - hitbox.r && ny < -hitbox.t) {
|
||||
nx = fix(1) - hitbox.r;
|
||||
ny = -hitbox.t;
|
||||
}
|
||||
if(!map_cell_walkable(m, map_cell(m, x-1, y+1))
|
||||
&& nx < -hitbox.l && ny > fix(1) - hitbox.b) {
|
||||
nx = -hitbox.l;
|
||||
ny = fix(1) - hitbox.b;
|
||||
}
|
||||
if(!map_cell_walkable(m, map_cell(m, x+1, y+1))
|
||||
&& nx > fix(1) - hitbox.r && ny > fix(1) - hitbox.b) {
|
||||
nx = fix(1) - hitbox.r;
|
||||
ny = fix(1) - hitbox.b;
|
||||
}
|
||||
|
||||
/* If the new position is valid, use it, otherwise refuse the movement. We
|
||||
catch here some potential bugs, plus a whole lot of unhandled cases when
|
||||
the hitbox is larger than a tile */
|
||||
new_pos = (vec2){ fix(x) + nx, fix(y) + ny };
|
||||
if(!map_collides(m, rect_translate(hitbox, new_pos)))
|
||||
return new_pos;
|
||||
|
||||
return old_pos;
|
||||
}
|
||||
|
|
|
@ -96,5 +96,12 @@ typedef struct
|
|||
/* Get a pointer to the cell at (x,y) in map; NULL if out of bounds. */
|
||||
map_cell_t *map_cell(map_t const *m, int x, int y);
|
||||
|
||||
/* Check whether a map cell is walkable, only accounting for full hitboxes. */
|
||||
bool map_cell_walkable(map_t const *m, map_cell_t const *cell);
|
||||
|
||||
/* Check whether a hitbox collides with the map. */
|
||||
bool map_collides(map_t const *m, rect hitbox);
|
||||
|
||||
/* Returns the new position of an object with a hitbox trying to move from
|
||||
[old_pos] to [new_pos]. */
|
||||
vec2 map_move(map_t const *m, vec2 old_pos, vec2 new_pos, rect hitbox);
|
||||
|
|
Loading…
Reference in New Issue