Compare commits

...

2 Commits

Author SHA1 Message Date
En Yi b5e7d8846f Add collision handling for one-way tile
Internal Changelog:
- Add ONEWAY handling in check_collision
- All collision check now requires a prev_position check due to the
  one-way tile check. Update relevant function signature
- On ground check now does an edge check instead of using the offset
  check
2023-05-04 12:48:32 +08:00
En Yi 36e84d1b75 Add one way tile
Changelog:
- Add new enums for tile and solidity
- Modify check_collsion_and_move function to account for one way
  collision
  - Also remove the collision value as it is not used
- Add spawning for oneway tile
2023-05-02 21:54:29 +08:00
3 changed files with 114 additions and 45 deletions

View File

@ -11,10 +11,11 @@ static Tile_t all_tiles[MAX_N_TILES] = {0};
enum EntitySpawnSelection {
TOGGLE_TILE = 0,
TOGGLE_ONEWAY,
SPAWN_CRATE,
SPAWN_METAL_CRATE,
};
#define MAX_SPAWN_TYPE 3
#define MAX_SPAWN_TYPE 4
static unsigned int current_spawn_selection = 0;
static inline unsigned int get_tile_idx(int x, int y, unsigned int tilemap_width)
@ -38,10 +39,14 @@ static void level_scene_render_func(Scene_t* scene)
int y = (i / tilemap.width) * TILE_SIZE;
sprintf(buffer, "%u", sc_map_size_64(&tilemap.tiles[i].entities_set));
if (tilemap.tiles[i].solid)
if (tilemap.tiles[i].tile_type == SOLID_TILE)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, BLACK);
}
else if (tilemap.tiles[i].tile_type == ONEWAY_TILE)
{
DrawRectangle(x, y, TILE_SIZE, TILE_SIZE, BROWN);
}
else if (tilemap.tiles[i].water_level > 0)
{
// Draw water tile
@ -106,7 +111,7 @@ static void level_scene_render_func(Scene_t* scene)
int y = (i / tilemap.width) * TILE_SIZE;
sprintf(buffer, "%u", sc_map_size_64(&tilemap.tiles[i].entities_set));
if (tilemap.tiles[i].solid)
if (tilemap.tiles[i].solid > 0)
{
DrawText(buffer, x, y, 10, WHITE);
}
@ -233,8 +238,30 @@ static void toggle_block_system(Scene_t* scene)
switch (sel)
{
case TOGGLE_TILE:
tilemap.tiles[tile_idx].solid = !tilemap.tiles[tile_idx].solid;
tilemap.tiles[tile_idx].water_level = 0;
if (tilemap.tiles[tile_idx].tile_type == SOLID_TILE)
{
tilemap.tiles[tile_idx].tile_type = EMPTY_TILE;
tilemap.tiles[tile_idx].solid = NOT_SOLID;
}
else
{
tilemap.tiles[tile_idx].tile_type = SOLID_TILE;
tilemap.tiles[tile_idx].solid = SOLID;
}
tilemap.tiles[tile_idx].water_level = 0;
break;
case TOGGLE_ONEWAY:
if (tilemap.tiles[tile_idx].tile_type == ONEWAY_TILE)
{
tilemap.tiles[tile_idx].tile_type = EMPTY_TILE;
tilemap.tiles[tile_idx].solid = NOT_SOLID;
}
else
{
tilemap.tiles[tile_idx].tile_type = ONEWAY_TILE;
tilemap.tiles[tile_idx].solid = ONE_WAY;
}
tilemap.tiles[tile_idx].water_level = 0;
break;
case SPAWN_CRATE:
spawn_crate(scene, tile_idx, false);
@ -361,13 +388,15 @@ void init_level_scene(LevelScene_t* scene)
scene->data.tilemap.tiles = all_tiles;
for (size_t i = 0; i < MAX_N_TILES;i++)
{
all_tiles[i].solid = 0;
all_tiles[i].solid = NOT_SOLID;
all_tiles[i].tile_type = EMPTY_TILE;
sc_map_init_64(&all_tiles[i].entities_set, 16, 0);
}
for (size_t i = 0; i < scene->data.tilemap.width; ++i)
{
unsigned int tile_idx = (scene->data.tilemap.height - 1) * scene->data.tilemap.width + i;
all_tiles[tile_idx].solid = true; // for testing
all_tiles[tile_idx].solid = SOLID; // for testing
all_tiles[tile_idx].tile_type = SOLID_TILE; // for testing
}
spawn_player(&scene->scene);

View File

@ -36,18 +36,32 @@ typedef struct TileArea {
typedef struct CollideEntity {
unsigned int idx;
Rectangle bbox;
Rectangle prev_bbox;
TileArea_t area;
} CollideEntity_t;
// ------------------------- Collision functions ------------------------------------
static bool check_collision(const CollideEntity_t* ent, TileGrid_t* grid, EntityManager_t* p_manager)
static bool check_collision(const CollideEntity_t* ent, TileGrid_t* grid, EntityManager_t* p_manager, bool check_oneway)
{
for(unsigned int tile_y = ent->area.tile_y1; tile_y <= ent->area.tile_y2; tile_y++)
{
for(unsigned int tile_x = ent->area.tile_x1; tile_x <= ent->area.tile_x2; tile_x++)
{
unsigned int tile_idx = tile_y*grid->width + tile_x;
if (grid->tiles[tile_idx].solid) return true;
if (grid->tiles[tile_idx].solid == SOLID) return true;
Vector2 overlap;
if (check_oneway && grid->tiles[tile_idx].solid == ONE_WAY)
{
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
);
//For one-way platform, check for vectical collision, only return true for up direction
if (overlap.y != 0 && ent->prev_bbox.y + ent->prev_bbox.height - 1 < tile_y * TILE_SIZE) return true;
}
unsigned int ent_idx;
sc_map_foreach_value(&grid->tiles[tile_idx].entities_set, ent_idx)
{
@ -55,7 +69,6 @@ static bool check_collision(const CollideEntity_t* ent, TileGrid_t* grid, Entity
Entity_t * p_ent = get_entity(p_manager, ent_idx);
CTransform_t *p_ctransform = get_component(p_manager, p_ent, CTRANSFORM_COMP_T);
CBBox_t *p_bbox = get_component(p_manager, p_ent, CBBOX_COMP_T);
Vector2 overlap;
if (p_bbox == NULL || p_ctransform == NULL) continue;
//if (p_bbox->solid && !p_bbox->fragile)
if (p_bbox->solid)
@ -77,6 +90,8 @@ static bool check_collision(const CollideEntity_t* ent, TileGrid_t* grid, Entity
return false;
}
// TODO: This should be a point collision check, not an AABB check
static bool check_collision_at(
unsigned int ent_idx, Vector2 pos, Vector2 bbox_sz,
TileGrid_t* grid, Vector2 point, EntityManager_t* p_manager
@ -86,6 +101,7 @@ static bool check_collision_at(
CollideEntity_t ent = {
.idx = ent_idx,
.bbox = (Rectangle){new_pos.x, new_pos.y, bbox_sz.x, bbox_sz.y},
.prev_bbox = (Rectangle){pos.x, pos.y, bbox_sz.x, bbox_sz.y},
.area = (TileArea_t){
.tile_x1 = (new_pos.x) / TILE_SIZE,
.tile_y1 = (new_pos.y) / TILE_SIZE,
@ -94,22 +110,35 @@ static bool check_collision_at(
}
};
return check_collision(&ent, grid, p_manager);
return check_collision(&ent, grid, p_manager, false);
}
static inline bool check_on_ground(
unsigned int ent_idx, Vector2 pos, Vector2 bbox_sz,
unsigned int ent_idx, Vector2 pos, Vector2 prev_pos, Vector2 bbox_sz,
TileGrid_t* grid, EntityManager_t* p_manager
)
{
return check_collision_at(ent_idx, pos, bbox_sz, grid, (Vector2){0, 1}, p_manager);
//return check_collision_at(ent_idx, pos, bbox_sz, grid, (Vector2){0, 1}, p_manager);
Vector2 new_pos = Vector2Add(pos, (Vector2){0, 1});
CollideEntity_t ent = {
.idx = ent_idx,
.bbox = (Rectangle){new_pos.x, new_pos.y + bbox_sz.y - 1, bbox_sz.x, 1},
.prev_bbox = (Rectangle){prev_pos.x, prev_pos.y, bbox_sz.x, bbox_sz.y},
.area = (TileArea_t){
.tile_x1 = (new_pos.x) / TILE_SIZE,
.tile_y1 = (new_pos.y + bbox_sz.y - 1) / TILE_SIZE,
.tile_x2 = (new_pos.x + bbox_sz.x - 1) / TILE_SIZE,
.tile_y2 = (new_pos.y + bbox_sz.y - 1) / TILE_SIZE
}
};
return check_collision(&ent, grid, p_manager, true);
}
static bool check_collision_and_move(
EntityManager_t* p_manager, TileGrid_t* tilemap,
unsigned int ent_idx, CTransform_t* p_ct, Vector2 sz,
Vector2 other_pos, Vector2 other_sz, bool other_solid,
uint32_t* collision_value
Vector2 other_pos, Vector2 other_sz, SolidType_t other_solid
)
{
Vector2 overlap = {0,0};
@ -126,39 +155,37 @@ static bool check_collision_and_move(
if (fabs(prev_overlap.y) > fabs(prev_overlap.x))
{
offset.x = overlap.x;
*collision_value = (((overlap.x > 0?1:0)<< 14) | ( (uint16_t)(fabs(overlap.x)) ));
}
else if (fabs(prev_overlap.x) > fabs(prev_overlap.y))
{
offset.y = overlap.y;
*collision_value = (((overlap.y > 0?3:2)<< 14) | ( (uint16_t)(fabs(overlap.y)) ));
}
else if (fabs(overlap.x) < fabs(overlap.y))
{
offset.x = overlap.x;
*collision_value = (((overlap.x > 0?1:0)<< 14) | ( (uint16_t)(fabs(overlap.x)) ));
}
else
{
offset.y = overlap.y;
*collision_value = (((overlap.y > 0?3:2)<< 14) | ( (uint16_t)(fabs(overlap.y)) ));
}
// Resolve collision via moving player by the overlap amount only if other is solid
// also check for empty to prevent creating new collision. Not fool-proof, but good enough
//if (other_solid && !check_collision_at(ent_idx, p_ct->position, sz, tilemap, offset, p_manager))
if (other_solid)
if ( other_solid == SOLID
|| (other_solid == ONE_WAY && offset.y < 0 && (p_ct->prev_position.y + sz.y - 1 < other_pos.y))
)
{
p_ct->position = Vector2Add(p_ct->position, offset);
return true;
}
return true;
}
return false;
}
static uint8_t check_bbox_edges(
EntityManager_t* p_manager, TileGrid_t* tilemap,
unsigned int ent_idx, Vector2 pos, Vector2 bbox
unsigned int ent_idx, Vector2 pos, Vector2 prev_pos, Vector2 bbox
)
{
uint8_t detected = 0;
@ -166,6 +193,7 @@ static uint8_t check_bbox_edges(
{
.idx = ent_idx,
.bbox = (Rectangle){pos.x - 1, pos.y, bbox.x, bbox.y},
.prev_bbox = (Rectangle){pos.x, pos.y, bbox.x, bbox.y},
.area = (TileArea_t){
.tile_x1 = (pos.x - 1) / TILE_SIZE,
.tile_y1 = (pos.y) / TILE_SIZE,
@ -173,14 +201,17 @@ static uint8_t check_bbox_edges(
.tile_y2 = (pos.y + bbox.y - 1) / TILE_SIZE,
}
};
// TODO: Handle one-way platform
// Left
detected |= (check_collision(&ent, tilemap, p_manager) ? 1 : 0) << 3;
detected |= (check_collision(&ent, tilemap, p_manager, false) ? 1 : 0) << 3;
//Right
ent.bbox.x += 2; // 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, p_manager) ? 1 : 0) << 2;
detected |= (check_collision(&ent, tilemap, p_manager, false) ? 1 : 0) << 2;
// Up
ent.bbox.x -= 2;
@ -189,13 +220,13 @@ static uint8_t check_bbox_edges(
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, p_manager) ? 1 : 0) << 1;
detected |= (check_collision(&ent, tilemap, p_manager, false) ? 1 : 0) << 1;
// Down
ent.bbox.y += 2;
ent.area.tile_y1 = (pos.y + bbox.y) / TILE_SIZE,
ent.area.tile_y2 = ent.area.tile_y1;
detected |= (check_collision(&ent, tilemap, p_manager) ? 1 : 0);
detected |= (check_collision(&ent, tilemap, p_manager, true) ? 1 : 0);
return detected;
}
@ -322,7 +353,7 @@ void player_movement_input_system(Scene_t* scene)
for(unsigned int tile_x = tile_x1; tile_x <= tile_x2; tile_x++)
{
hit |= tilemap.tiles[tile_y * tilemap.width + tile_x].solid;
hit |= tilemap.tiles[tile_y * tilemap.width + tile_x].tile_type == SOLID_TILE;
}
if (hit)
{
@ -442,13 +473,12 @@ void tile_collision_system(Scene_t* scene)
unsigned int tile_x2 = (p_ctransform->position.x + p_bbox->size.x) / TILE_SIZE;
unsigned int tile_y2 = (p_ctransform->position.y + p_bbox->size.y) / TILE_SIZE;
uint32_t collision_value;
for (unsigned int tile_y = tile_y1; tile_y <= tile_y2; tile_y++)
{
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].solid)
if(tilemap.tiles[tile_idx].tile_type != EMPTY_TILE)
{
Vector2 other;
other.x = (tile_idx % tilemap.width) * TILE_SIZE;
@ -457,7 +487,7 @@ void tile_collision_system(Scene_t* scene)
check_collision_and_move(
&scene->ent_manager, &tilemap, ent_idx,
p_ctransform, p_bbox->size, other,
TILE_SZ, true, &collision_value
TILE_SZ, tilemap.tiles[tile_idx].solid
);
}
@ -478,17 +508,11 @@ void tile_collision_system(Scene_t* scene)
if (p_other_bbox == NULL) continue;
CTransform_t *p_other_ct = get_component(&scene->ent_manager, p_other_ent, CTRANSFORM_COMP_T);
if (
check_collision_and_move(
&scene->ent_manager, &tilemap, ent_idx,
p_ctransform, p_bbox->size, p_other_ct->position,
p_other_bbox->size, p_other_bbox->solid, &collision_value
)
)
{
uint32_t collision_key = ((ent_idx << 16) | other_ent_idx);
sc_map_put_32(&data->collision_events, collision_key, collision_value);
}
check_collision_and_move(
&scene->ent_manager, &tilemap, ent_idx,
p_ctransform, p_bbox->size, p_other_ct->position,
p_other_bbox->size, p_other_bbox->solid? SOLID : NOT_SOLID
);
}
}
}
@ -497,7 +521,7 @@ void tile_collision_system(Scene_t* scene)
// Post movement edge check to zero out velocity
uint8_t edges = check_bbox_edges(
&scene->ent_manager, &data->tilemap, ent_idx,
p_ctransform->position, p_bbox->size
p_ctransform->position, p_ctransform->prev_position, p_bbox->size
);
if (edges & (1<<3))
{
@ -659,7 +683,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(
&scene->ent_manager, &data->tilemap, ent_idx,
p_ctransform->position, p_bbox->size
p_ctransform->position, p_ctransform->prev_position, p_bbox->size
);
if (edges & (1<<3))
{
@ -762,7 +786,7 @@ void state_transition_update_system(Scene_t* scene)
if (p_ctransform == NULL || p_bbox == NULL) continue;
bool on_ground = check_on_ground(
ent_idx, p_ctransform->position, p_bbox->size,
ent_idx, p_ctransform->position, p_ctransform->prev_position, p_bbox->size,
&data->tilemap, &scene->ent_manager
);
bool in_water = false;

View File

@ -7,8 +7,24 @@
#include "engine.h"
#include "gui.h"
typedef enum TileType {
EMPTY_TILE = 0,
SOLID_TILE,
ONEWAY_TILE,
LADDER
} TileType_t;
typedef enum SolidType
{
NOT_SOLID = 0,
SOLID,
ONE_WAY,
}SolidType_t;
typedef struct Tile {
bool solid;
TileType_t tile_type;
SolidType_t solid;
unsigned int water_level;
struct sc_map_64 entities_set;
}Tile_t;