Compare commits

...

7 Commits

Author SHA1 Message Date
En Yi 2b878ae784 Add player collision with spikes 2023-06-21 22:32:14 +08:00
En Yi 451b241460 Correct AABB tests
Changelog:
- Figure out why the previous AABB behaviour was correct: continuum
    [0, 5) does not collide with [5, 10)
    For tile check however, need to subtract one to avoid extra tile
    check. Exception is the tile collision
2023-06-21 22:11:32 +08:00
En Yi c8d2ee5408 Improve crushing using edge testing
Changelog:
- Add len reduction to edge checking to give leeway in crushing
2023-06-21 21:27:13 +08:00
En Yi 4b91ab5667 Integrate line check into edge check function
Internal Changelog:
- Weird issue when using the new AABB functions, revert the off-by-one
  fix
2023-06-21 21:13:02 +08:00
En Yi 7767b38221 Add line-AABB collision check function 2023-06-20 22:23:32 +08:00
En Yi c27fa632a2 Add spikes spawning
Changelog:
- Add new tile: spikes
- Change bbox of tile of spikes depending on solid tiles position
2023-06-19 22:32:18 +08:00
En Yi 81da536e8e Add bounding box info in tiles
Changelog:
- Use more memory, but should help out in implementing spikes
2023-06-19 21:08:42 +08:00
7 changed files with 270 additions and 72 deletions

View File

@ -14,12 +14,13 @@ enum EntitySpawnSelection {
TOGGLE_TILE = 0,
TOGGLE_ONEWAY,
TOGGLE_LADDER,
TOGGLE_SPIKE,
SPAWN_CRATE,
SPAWN_METAL_CRATE,
SPAWN_BOULDER,
};
#define MAX_SPAWN_TYPE 6
#define MAX_SPAWN_TYPE 7
static unsigned int current_spawn_selection = 0;
static inline unsigned int get_tile_idx(int x, int y, const TileGrid_t* tilemap)
@ -68,6 +69,13 @@ static void level_scene_render_func(Scene_t* scene)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, ORANGE);
}
else if (tilemap.tiles[i].tile_type == SPIKES)
{
DrawRectangle(
x + tilemap.tiles[i].offset.x, y + tilemap.tiles[i].offset.y,
tilemap.tiles[i].size.x, tilemap.tiles[i].size.y, RED
);
}
if (tilemap.tiles[i].water_level > 0)
{
@ -317,6 +325,17 @@ static void toggle_block_system(Scene_t* scene)
tilemap.tiles[down_tile].solid = (tilemap.tiles[tile_idx].tile_type != LADDER)? ONE_WAY : NOT_SOLID;
}
break;
case TOGGLE_SPIKE:
if (tilemap.tiles[tile_idx].tile_type == SPIKES)
{
tilemap.tiles[tile_idx].tile_type = EMPTY_TILE;
}
else
{
tilemap.tiles[tile_idx].tile_type = SPIKES;
}
tilemap.tiles[tile_idx].solid = NOT_SOLID;
break;
case SPAWN_CRATE:
spawn_crate(scene, tile_idx, false);
break;
@ -327,6 +346,34 @@ static void toggle_block_system(Scene_t* scene)
spawn_boulder(scene, tile_idx);
break;
}
if (tilemap.tiles[tile_idx].tile_type == SPIKES)
{
if (tile_idx - tilemap.width >= 0 && tilemap.tiles[tile_idx-tilemap.width].tile_type == SOLID_TILE)
{
tilemap.tiles[tile_idx].offset = (Vector2){0,0};
tilemap.tiles[tile_idx].size = (Vector2){32,16};
}
else if (tile_idx % tilemap.width != 0 && tile_idx > 0 && tilemap.tiles[tile_idx-1].tile_type == SOLID_TILE)
{
tilemap.tiles[tile_idx].offset = (Vector2){0,0};
tilemap.tiles[tile_idx].size = (Vector2){16,32};
}
else if (tile_idx % tilemap.width + 1 != 0 && tile_idx < MAX_N_TILES - 1 && tilemap.tiles[tile_idx+1].tile_type == SOLID_TILE)
{
tilemap.tiles[tile_idx].offset = (Vector2){16,0};
tilemap.tiles[tile_idx].size = (Vector2){16,32};
}
else
{
tilemap.tiles[tile_idx].offset = (Vector2){0,16};
tilemap.tiles[tile_idx].size = (Vector2){32,16};
}
}
else
{
tilemap.tiles[tile_idx].offset = (Vector2){0,0};
tilemap.tiles[tile_idx].size = (Vector2){32,32};
}
last_tile_idx = tile_idx;
}
}
@ -431,6 +478,7 @@ void init_level_scene(LevelScene_t* scene)
sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &hitbox_update_system);
sc_array_add(&scene->scene.systems, &player_crushing_system);
sc_array_add(&scene->scene.systems, &player_spike_collision_system);
sc_array_add(&scene->scene.systems, &state_transition_update_system);
sc_array_add(&scene->scene.systems, &player_ground_air_transition_system);
sc_array_add(&scene->scene.systems, &sprite_animation_system);
@ -462,6 +510,7 @@ void init_level_scene(LevelScene_t* scene)
all_tiles[i].solid = NOT_SOLID;
all_tiles[i].tile_type = EMPTY_TILE;
sc_map_init_64v(&all_tiles[i].entities_set, 16, 0);
all_tiles[i].size = (Vector2){TILE_SIZE, TILE_SIZE};
}
for (size_t i = 0; i < scene->data.tilemap.width; ++i)
{

View File

@ -3,7 +3,7 @@
uint8_t find_1D_overlap(Vector2 l1, Vector2 l2, float* overlap)
{
// No Overlap
if (l1.y < l2.x || l2.y < l1.x) return 0;
if (l1.y <= l2.x || l2.y <= l1.x) return 0;
if (l1.x >= l2.x && l1.y <= l2.y)
{
@ -14,7 +14,7 @@ uint8_t find_1D_overlap(Vector2 l1, Vector2 l2, float* overlap)
}
//Partial overlap
// x is p1, y is p2
*overlap = (l2.y >= l1.y)? l2.x - l1.y - 1 : l2.y - l1.x + 1;
*overlap = (l2.y > l1.y)? l2.x - l1.y : l2.y - l1.x;
return 1;
}
@ -25,15 +25,15 @@ uint8_t find_AABB_overlap(const Vector2 tl1, const Vector2 sz1, const Vector2 tl
Vector2 l1, l2;
uint8_t overlap_x, overlap_y;
l1.x = tl1.x;
l1.y = tl1.x + sz1.x - 1;
l1.y = tl1.x + sz1.x;
l2.x = tl2.x;
l2.y = tl2.x + sz2.x - 1;
l2.y = tl2.x + sz2.x;
overlap_x = find_1D_overlap(l1, l2, &overlap->x);
l1.x = tl1.y;
l1.y = tl1.y + sz1.y - 1;
l1.y = tl1.y + sz1.y;
l2.x = tl2.y;
l2.y = tl2.y + sz2.y - 1;
l2.y = tl2.y + sz2.y;
overlap_y = find_1D_overlap(l1, l2, &overlap->y);
if (overlap_x == 2 && overlap_y == 2) return 2;
@ -49,3 +49,45 @@ bool point_in_AABB(Vector2 point, Rectangle box)
&& point.y < box.y + box.height
);
}
bool line_in_AABB(Vector2 p1, Vector2 p2, Rectangle box)
{
float A = p2.y - p1.y;
float B = p1.x - p2.x;
float C = (p2.x * p1.y) - (p1.x * p2.y);
Vector2 corners[3] =
{
{box.x + box.width - 1, box.y},
{box.x + box.width - 1, box.y + box.height - 1},
{box.x, box.y + box.height - 1},
};
float F = (A * box.x + B * box.y + C);
uint8_t last_mode = 0;
if (fabs(F) < 1e-3)
{
last_mode = 0;
}
else
{
last_mode = (F > 0) ? 1 : 2;
}
for (uint8_t i = 0; i < 3; ++i)
{
F = (A * corners[i].x + B * corners[i].y + C);
uint8_t mode = 0;
if (fabs(F) < 1e-3)
{
mode = 0;
}
else
{
mode = (F > 0) ? 1 : 2;
}
if (mode != last_mode) return true;
last_mode = mode;
}
return false;
}

View File

@ -6,4 +6,5 @@
uint8_t find_1D_overlap(Vector2 l1, Vector2 l2, float* overlap);
uint8_t find_AABB_overlap(const Vector2 tl1, const Vector2 sz1, const Vector2 tl2, const Vector2 sz2, Vector2* overlap);
bool point_in_AABB(Vector2 point, Rectangle box);
bool line_in_AABB(Vector2 p1, Vector2 p2, Rectangle box);
#endif // __AABB_H

View File

@ -5,7 +5,6 @@
#include "constants.h"
#include <stdio.h>
static const Vector2 TILE_SZ = {TILE_SIZE, TILE_SIZE};
static const Vector2 GRAVITY = {0, GRAV_ACCEL};
static const Vector2 UPTHRUST = {0, -GRAV_ACCEL * 1.1};
typedef enum AnchorPoint {
@ -60,7 +59,9 @@ static uint8_t check_collision(const CollideEntity_t* ent, TileGrid_t* grid, boo
find_AABB_overlap(
(Vector2){ent->bbox.x, ent->bbox.y},
(Vector2){ent->bbox.width, ent->bbox.height},
(Vector2){tile_x * TILE_SIZE, tile_y * TILE_SIZE}, TILE_SZ, &overlap
(Vector2){tile_x * TILE_SIZE + grid->tiles[tile_idx].offset.x, tile_y * TILE_SIZE + grid->tiles[tile_idx].offset.y},
grid->tiles[tile_idx].size,
&overlap
);
//For one-way platform, check for vectical collision, only return true for up direction
@ -95,6 +96,62 @@ static uint8_t check_collision(const CollideEntity_t* ent, TileGrid_t* grid, boo
return 0;
}
static uint8_t check_collision_line(const CollideEntity_t* ent, TileGrid_t* grid, bool check_oneway)
{
Vector2 p1 = {ent->bbox.x, ent->bbox.y};
Vector2 p2 = {ent->bbox.x + ent->bbox.width - 1, ent->bbox.y + ent->bbox.height - 1};
for(unsigned int tile_y = ent->area.tile_y1; tile_y <= ent->area.tile_y2; tile_y++)
{
if (tile_y >= grid->height) return 0;
for(unsigned int tile_x = ent->area.tile_x1; tile_x <= ent->area.tile_x2; tile_x++)
{
if (tile_x >= grid->width) return 0;
unsigned int tile_idx = tile_y*grid->width + tile_x;
if (grid->tiles[tile_idx].solid == SOLID) return 1;
if (check_oneway && grid->tiles[tile_idx].solid == ONE_WAY)
{
Rectangle tile_rec = {
.x = tile_x * TILE_SIZE + grid->tiles[tile_idx].offset.x,
.y = tile_y * TILE_SIZE + grid->tiles[tile_idx].offset.y,
.width = grid->tiles[tile_idx].size.x,
.height = grid->tiles[tile_idx].size.y
};
bool collide = line_in_AABB(p1, p2, tile_rec);
//For one-way platform, check for vectical collision, only return true for up direction
if (collide && ent->prev_bbox.y + ent->prev_bbox.height - 1 < tile_y * TILE_SIZE) return 1;
}
Entity_t* p_other_ent;
sc_map_foreach_value(&grid->tiles[tile_idx].entities_set, p_other_ent)
{
if (ent->p_ent->m_id == p_other_ent->m_id) continue;
if (!ent->p_ent->m_alive) continue;
CTransform_t *p_ctransform = get_component(p_other_ent, CTRANSFORM_COMP_T);
CBBox_t *p_bbox = get_component(p_other_ent, CBBOX_COMP_T);
if (p_bbox == NULL || p_ctransform == NULL) continue;
//if (p_bbox->solid && !p_bbox->fragile)
if (p_bbox->solid)
{
Rectangle box = {
.x = p_ctransform->position.x,
.y = p_ctransform->position.y,
.width = p_bbox->size.x,
.height = p_bbox->size.y,
};
if ( line_in_AABB(p1, p2, box) )
{
return (p_bbox->fragile) ? 2 : 1;
}
}
}
}
}
return 0;
}
// TODO: This should be a point collision check, not an AABB check
static bool check_collision_offset(
Entity_t* p_ent, Vector2 pos, Vector2 bbox_sz,
@ -241,14 +298,19 @@ collision_end:
static uint8_t check_bbox_edges(
TileGrid_t* tilemap,
Entity_t* p_ent, Vector2 pos, Vector2 prev_pos, Vector2 bbox
Entity_t* p_ent, Vector2 pos, Vector2 prev_pos, Vector2 bbox,
int8_t len_reduction
)
{
uint8_t detected = 0;
bbox.x -= 2 * len_reduction;
bbox.y -= 2 * len_reduction;
CollideEntity_t ent =
{
.p_ent = p_ent,
.bbox = (Rectangle){pos.x - 1, pos.y, bbox.x, bbox.y},
.bbox = (Rectangle){pos.x - 1, pos.y + len_reduction, 1, bbox.y},
.prev_bbox = (Rectangle){pos.x, pos.y, bbox.x, bbox.y},
.area = (TileArea_t){
.tile_x1 = (pos.x - 1) / TILE_SIZE,
@ -261,28 +323,30 @@ static uint8_t check_bbox_edges(
// TODO: Handle one-way platform
// Left
detected |= (check_collision(&ent, tilemap, false) ? 1 : 0) << 3;
detected |= (check_collision_line(&ent, tilemap, false) ? 1 : 0) << 3;
//Right
ent.bbox.x += 2; // 2 to account for the previous subtraction
ent.bbox.x = pos.x + bbox.x; // 2 to account for the previous subtraction
ent.area.tile_x1 = (pos.x + bbox.x) / TILE_SIZE;
ent.area.tile_x2 = ent.area.tile_x1;
detected |= (check_collision(&ent, tilemap, false) ? 1 : 0) << 2;
detected |= (check_collision_line(&ent, tilemap, false) ? 1 : 0) << 2;
// Up
ent.bbox.x -= 2;
ent.bbox.y--;
ent.bbox.x = pos.x + len_reduction;
ent.bbox.y = pos.y - 1;
ent.bbox.width = bbox.x;
ent.bbox.height = 1;
ent.area.tile_x1 = (pos.x) / TILE_SIZE,
ent.area.tile_x2 = (pos.x + bbox.x - 1) / TILE_SIZE,
ent.area.tile_y1 = (pos.y - 1) / TILE_SIZE,
ent.area.tile_y2 = ent.area.tile_y1;
detected |= (check_collision(&ent, tilemap, false) ? 1 : 0) << 1;
detected |= (check_collision_line(&ent, tilemap, false) ? 1 : 0) << 1;
// Down
ent.bbox.y += 2;
ent.bbox.y = pos.y + bbox.y;
ent.area.tile_y1 = (pos.y + bbox.y) / TILE_SIZE,
ent.area.tile_y2 = ent.area.tile_y1;
detected |= (check_collision(&ent, tilemap, true) ? 1 : 0);
detected |= (check_collision_line(&ent, tilemap, true) ? 1 : 0);
return detected;
}
@ -613,60 +677,61 @@ void player_bbox_update_system(Scene_t* scene)
void player_crushing_system(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
TileGrid_t tilemap = data->tilemap;
Entity_t* p_player;
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
{
CTransform_t* p_ctransform = get_component(p_player, CTRANSFORM_COMP_T);
CBBox_t* p_bbox = get_component(p_player, CBBOX_COMP_T);
CollideEntity_t ent =
{
.p_ent = p_player,
.bbox = (Rectangle){p_ctransform->position.x, p_ctransform->position.y, p_bbox->size.x, p_bbox->size.y},
.prev_bbox = (Rectangle){p_ctransform->prev_velocity.x, p_ctransform->prev_position.y, p_bbox->size.x, p_bbox->size.y},
.area = (TileArea_t){
.tile_x1 = (p_ctransform->position.x) / TILE_SIZE,
.tile_y1 = (p_ctransform->position.y) / TILE_SIZE,
.tile_x2 = (p_ctransform->position.x) / TILE_SIZE,
.tile_y2 = (p_ctransform->position.y + p_bbox->size.y - 1) / TILE_SIZE,
},
};
// Mostly identical to edge check function
// Except we want collision instead of just touching
uint8_t detected = 0;
// Left
detected |= (check_collision(&ent, &tilemap, false) ? 1 : 0);
uint8_t edges = check_bbox_edges(
&data->tilemap, p_player,
p_ctransform->position, p_ctransform->prev_position, p_bbox->size, 2
);
//Right
ent.area.tile_x1 = (p_ctransform->position.x + p_bbox->size.x - 1) / TILE_SIZE;
ent.area.tile_x2 = ent.area.tile_x1;
detected |= (check_collision(&ent, &tilemap, false) ? 1 : 0) << 1;
if (detected == 0b11)
if ((edges & 0b1100) == 0b1100 || (edges & 0b0011) == 0b0011)
{
p_player->m_alive = false;
return;
}
}
}
detected = 0;
// Up
ent.area.tile_x1 = (p_ctransform->position.x) / TILE_SIZE,
ent.area.tile_y1 = (p_ctransform->position.y - 1) / TILE_SIZE,
ent.area.tile_y2 = ent.area.tile_y1;
detected |= (check_collision(&ent, &tilemap, false) ? 1 : 0) << 1;
// Down
ent.area.tile_y1 = (p_ctransform->position.y + p_bbox->size.y - 1) / TILE_SIZE,
ent.area.tile_y2 = ent.area.tile_y1;
detected |= (check_collision(&ent, &tilemap, true) ? 1 : 0);
//if (check_collision(&ent, &tilemap, false) == 1)
if (detected == 0b11)
void player_spike_collision_system(Scene_t* scene)
{
LevelSceneData_t* data = &(CONTAINER_OF(scene, LevelScene_t, scene)->data);
TileGrid_t tilemap = data->tilemap;
Entity_t* p_player;
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
{
CTransform_t* p_ctransform = get_component(p_player, CTRANSFORM_COMP_T);
CBBox_t* p_bbox = get_component(p_player, CBBOX_COMP_T);
unsigned int tile_x1 = (p_ctransform->position.x) / TILE_SIZE;
unsigned int tile_y1 = (p_ctransform->position.y) / TILE_SIZE;
unsigned int tile_x2 = (p_ctransform->position.x + p_bbox->size.x - 1) / TILE_SIZE;
unsigned int tile_y2 = (p_ctransform->position.y + p_bbox->size.y - 1) / TILE_SIZE;
Vector2 overlap;
for (unsigned int tile_y = tile_y1; tile_y <= tile_y2; tile_y++)
{
p_player->m_alive = false;
return;
for (unsigned int tile_x = tile_x1; tile_x <= tile_x2; tile_x++)
{
unsigned int tile_idx = tile_y * tilemap.width + tile_x;
if(tilemap.tiles[tile_idx].tile_type == SPIKES)
{
if (find_AABB_overlap(
p_ctransform->position, p_bbox->size,
(Vector2){
tile_x * TILE_SIZE + tilemap.tiles[tile_idx].offset.x,
tile_y * TILE_SIZE + tilemap.tiles[tile_idx].offset.y
},
tilemap.tiles[tile_idx].size,
&overlap))
{
p_player->m_alive = false;
return;
}
}
}
}
}
}
@ -704,12 +769,12 @@ void tile_collision_system(Scene_t* scene)
if(tilemap.tiles[tile_idx].tile_type != EMPTY_TILE)
{
Vector2 other;
other.x = (tile_idx % tilemap.width) * TILE_SIZE;
other.y = (tile_idx / tilemap.width) * TILE_SIZE; // Precision loss is intentional
other.x = (tile_idx % tilemap.width) * TILE_SIZE + tilemap.tiles[tile_idx].offset.x;
other.y = (tile_idx / tilemap.width) * TILE_SIZE + tilemap.tiles[tile_idx].offset.y; // Precision loss is intentional
check_collision_and_move(
&tilemap, p_ent,
&other, TILE_SZ, tilemap.tiles[tile_idx].solid
&other, tilemap.tiles[tile_idx].size, tilemap.tiles[tile_idx].solid
);
}
@ -742,7 +807,7 @@ void tile_collision_system(Scene_t* scene)
// Post movement edge check to zero out velocity
uint8_t edges = check_bbox_edges(
&data->tilemap, p_ent,
p_ctransform->position, p_ctransform->prev_position, p_bbox->size
p_ctransform->position, p_ctransform->prev_position, p_bbox->size, 0
);
if (edges & (1<<3))
{
@ -868,7 +933,7 @@ void global_external_forces_system(Scene_t* scene)
// Zero out acceleration for contacts with sturdy entites and tiles
uint8_t edges = check_bbox_edges(
&data->tilemap, p_ent,
p_ctransform->position, p_ctransform->prev_position, p_bbox->size
p_ctransform->position, p_ctransform->prev_position, p_bbox->size, 0
);
if (edges & (1<<3))
{

View File

@ -22,4 +22,5 @@ void boulder_destroy_wooden_tile_system(Scene_t* scene);
void camera_update_system(Scene_t* scene);
void player_dir_reset_system(Scene_t* scene);
void player_respawn_system(Scene_t* scene);
void player_spike_collision_system(Scene_t* scene);
#endif // __GAME_SYSTEMS_H

View File

@ -15,9 +15,10 @@ typedef enum TileType {
EMPTY_TILE = 0,
SOLID_TILE,
ONEWAY_TILE,
LADDER
LADDER,
SPIKES,
} TileType_t;
#define MAX_TILE_TYPES 4
#define MAX_TILE_TYPES 5
typedef enum SolidType
{
NOT_SOLID = 0,
@ -31,6 +32,8 @@ typedef struct Tile {
SolidType_t solid;
unsigned int water_level;
struct sc_map_64v entities_set;
Vector2 offset;
Vector2 size;
}Tile_t;
typedef struct TileGrid {

View File

@ -6,6 +6,44 @@
#include <setjmp.h>
#include <cmocka.h>
static void test_line_AABB(void **state)
{
(void) state;
Vector2 p1 = {0, 0};
Vector2 p2 = {20, 20};
Rectangle box = {5, 0, 10, 20};
assert_true(line_in_AABB(p1, p2, box));
p1.y = 20;
assert_false(line_in_AABB(p1, p2, box));
p1.y = 19;
p2 = (Vector2){19, 19};
assert_true(line_in_AABB(p1, p2, box));
p1.y = 0;
p2.y = 0;
assert_true(line_in_AABB(p1, p2, box));
p1 = (Vector2){5, 0};
p2 = (Vector2){5, 10};
assert_true(line_in_AABB(p1, p2, box));
p1 = (Vector2){14, 0};
p2 = (Vector2){14, 10};
assert_true(line_in_AABB(p1, p2, box));
p1 = (Vector2){15, 0};
p2 = (Vector2){15, 10};
assert_false(line_in_AABB(p1, p2, box));
p1 = (Vector2){0, 30};
p2 = (Vector2){6, 35};
assert_false(line_in_AABB(p1, p2, box));
}
static void test_point_AABB(void **state)
{
(void) state;
@ -64,16 +102,14 @@ static void test_1D_overlap(void **state)
assert_int_equal(find_1D_overlap(a, b, &overlap), 0);
a.y = 6;
assert_int_equal(find_1D_overlap(a, b, &overlap), 0);
assert_int_equal(find_1D_overlap(b, a, &overlap), 0);
a.y = 7;
assert_int_equal(find_1D_overlap(a, b, &overlap), 1);
assert_float_equal(overlap, -1, 1e-5);
assert_int_equal(find_1D_overlap(b, a, &overlap), 1);
assert_float_equal(overlap, 1, 1e-5);
a.y = 7;
assert_int_equal(find_1D_overlap(a, b, &overlap), 1);
assert_float_equal(overlap, -2, 1e-5);
assert_int_equal(find_1D_overlap(b, a, &overlap), 1);
assert_float_equal(overlap, 2, 1e-5);
a.x = 7;
a.y = 9;
@ -86,6 +122,7 @@ int main(void)
cmocka_unit_test(test_1D_overlap),
cmocka_unit_test(test_AABB_overlap),
cmocka_unit_test(test_point_AABB),
cmocka_unit_test(test_line_AABB),
};
return cmocka_run_group_tests(tests, NULL, NULL);