Implement hitbox and hurtbox system

Changelog:
- Add new components: hitbox and hurtbox
- Update player and crates with hitbox and hurtbox
- Add printing of mempool stats
- FIX CRITICAL BUG: use entity idx when removing from component map
    - Previously, it incorrectly uses the component idx
- Check for entity aliveness before removal and collision checks
- KWOWN ISSUE: Player can get stopped by metal crates when walking on
  them
scene_man
En Yi 2023-02-16 22:44:05 +08:00
parent b06d25f328
commit a8567e2666
8 changed files with 173 additions and 56 deletions

View File

@ -4,7 +4,7 @@
#include <stdint.h>
// TODO: Look at sc to use macros to auto generate functions
#define N_COMPONENTS 7
#define N_COMPONENTS 9
enum ComponentEnum
{
CBBOX_COMP_T,
@ -14,6 +14,8 @@ enum ComponentEnum
CJUMP_COMP_T,
CPLAYERSTATE_T,
CCONTAINER_T,
CHITBOX_T,
CHURTBOX_T,
};
typedef enum ComponentEnum ComponentEnum_t;
@ -95,6 +97,20 @@ typedef struct _CContainer_t
ContainerItem_t item;
}CContainer_t;
typedef struct _CHitBox_t
{
Vector2 offset;
Vector2 size;
bool strong;
}CHitBox_t;
typedef struct _CHurtbox_t
{
Vector2 offset;
Vector2 size;
bool fragile;
}CHurtbox_t;
static inline void set_bbox(CBBox_t* p_bbox, unsigned int x, unsigned int y)
{
p_bbox->size.x = x;

View File

@ -31,9 +31,10 @@ void update_entity_manager(EntityManager_t *p_manager)
sc_map_foreach (&p_entity->components, comp_type_idx, comp_idx)
{
free_component_to_mempool((ComponentEnum_t)comp_type_idx, comp_idx);
sc_map_del_64v(&p_manager->component_map[comp_type_idx], comp_idx);
sc_map_del_64v(&p_manager->component_map[comp_type_idx], e_idx);
sc_map_del_64v(&p_manager->entities_map[p_entity->m_tag], e_idx);
}
sc_map_clear_64(&p_entity->components);
free_entity_to_mempool(e_idx);
sc_map_del_64v(&p_manager->entities, e_idx);
}
@ -93,8 +94,11 @@ void remove_entity(EntityManager_t *p_manager, unsigned long id)
if(!sc_map_found(&p_manager->entities)) return;
// This only marks the entity for deletion
// Does not free entity. This is done during the update
p_entity->m_alive = false;
sc_queue_add_last(&p_manager->to_remove, id);
if (p_entity->m_alive)
{
p_entity->m_alive = false;
sc_queue_add_last(&p_manager->to_remove, id);
}
}
Entity_t *get_entity(EntityManager_t *p_manager, unsigned long id)

View File

@ -7,9 +7,9 @@
typedef struct EntityManager
{
// All fields are Read-Only
struct sc_map_64v entities; // id : entity
struct sc_map_64v entities_map[N_TAGS]; // [{id: ent}]
struct sc_map_64v component_map[N_COMPONENTS]; // [{id: comp}, ...]
struct sc_map_64v entities; // ent id : entity
struct sc_map_64v entities_map[N_TAGS]; // [{ent id: ent}]
struct sc_map_64v component_map[N_COMPONENTS]; // [{ent id: comp}, ...]
struct sc_queue_uint to_add;
struct sc_queue_uint to_remove;
}EntityManager_t;

View File

@ -1,6 +1,7 @@
#include "mempool.h"
#include "sc/queue/sc_queue.h"
#include <stdlib.h>
#include <stdio.h>
// Static allocate buffers
static Entity_t entity_buffer[MAX_COMP_POOL_SIZE];
@ -11,6 +12,8 @@ static CMovementState_t cmstate_buffer[MAX_COMP_POOL_SIZE];
static CJump_t cjump_buffer[1]; // Only player is expected to have this
static CPlayerState_t cplayerstate_buffer[1]; // Only player is expected to have this
static CContainer_t ccontainer_buffer[MAX_COMP_POOL_SIZE];
static CHitBox_t chitbox_buffer[MAX_COMP_POOL_SIZE];
static CHurtbox_t churtbox_buffer[MAX_COMP_POOL_SIZE];
// Use hashmap as a Set
// Use list will be used to check if an object exist
@ -37,6 +40,8 @@ static MemPool_t comp_mempools[N_COMPONENTS] =
{cjump_buffer, 1, sizeof(CJump_t), NULL, {0}},
{cplayerstate_buffer, 1, sizeof(CPlayerState_t), NULL, {0}},
{ccontainer_buffer, MAX_COMP_POOL_SIZE, sizeof(CContainer_t), NULL, {0}},
{chitbox_buffer, MAX_COMP_POOL_SIZE, sizeof(CHitBox_t), NULL, {0}},
{churtbox_buffer, MAX_COMP_POOL_SIZE, sizeof(CHurtbox_t), NULL, {0}},
};
static MemPool_t ent_mempool = {entity_buffer, MAX_COMP_POOL_SIZE, sizeof(Entity_t), NULL, {0}};
@ -150,6 +155,15 @@ void free_component_to_mempool(ComponentEnum_t comp_type, unsigned long idx)
if (comp_mempools[comp_type].use_list[idx])
{
comp_mempools[comp_type].use_list[idx] = false;
sc_queue_add_first(&comp_mempools[comp_type].free_list, idx);
sc_queue_add_last(&comp_mempools[comp_type].free_list, idx);
}
}
void print_mempool_stats(char* buffer)
{
int written = 0;
for (size_t i=0; i<N_COMPONENTS; ++i)
{
written += sprintf(buffer+written, "%lu: %lu/%lu\n", i, sc_queue_size(&comp_mempools[i].free_list), comp_mempools[i].max_size);
}
}

View File

@ -13,4 +13,6 @@ void free_entity_to_mempool(unsigned long idx);
void* new_component_from_mempool(ComponentEnum_t comp_type, unsigned long *idx);
void* get_component_wtih_id(ComponentEnum_t comp_type, unsigned long idx);
void free_component_to_mempool(ComponentEnum_t comp_type, unsigned long idx);
void print_mempool_stats(char* buffer);
#endif //__MEMPOOL_H

View File

@ -49,6 +49,7 @@ static void level_scene_render_func(Scene_t* scene)
}
}
char buffer[64] = {0};
sc_map_foreach_value(&scene->ent_manager.entities, p_ent)
{
CTransform_t* p_ct = get_component(&scene->ent_manager, p_ent, CTRANSFORM_COMP_T);
@ -66,11 +67,32 @@ static void level_scene_render_func(Scene_t* scene)
colour = BLACK;
}
DrawRectangle(p_ct->position.x, p_ct->position.y, p_bbox->size.x, p_bbox->size.y, colour);
CHurtbox_t* p_hurtbox = get_component(&scene->ent_manager, p_ent, CHURTBOX_T);
CHitBox_t* p_hitbox = get_component(&scene->ent_manager, p_ent, CHITBOX_T);
if (p_hitbox != NULL)
{
Rectangle rec = {
.x = p_ct->position.x + p_hitbox->offset.x,
.y = p_ct->position.y + p_hitbox->offset.y,
.width = p_hitbox->size.x,
.height = p_hitbox->size.y,
};
DrawRectangleLinesEx(rec, 1.5, ORANGE);
}
if (p_hurtbox != NULL)
{
Rectangle rec = {
.x = p_ct->position.x + p_hurtbox->offset.x,
.y = p_ct->position.y + p_hurtbox->offset.y,
.width = p_hurtbox->size.x,
.height = p_hurtbox->size.y,
};
DrawRectangleLinesEx(rec, 1.5, PURPLE);
}
}
for (size_t i=0; i<tilemap.n_tiles;++i)
{
char buffer[6] = {0};
int x = (i % tilemap.width) * TILE_SIZE;
int y = (i / tilemap.width) * TILE_SIZE;
sprintf(buffer, "%u", sc_map_size_64(&tilemap.tiles[i].entities_set));
@ -99,7 +121,6 @@ static void level_scene_render_func(Scene_t* scene)
}
// For DEBUG
char buffer[64];
sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_ent)
{
CTransform_t* p_ct = get_component(&scene->ent_manager, p_ent, CTRANSFORM_COMP_T);
@ -121,6 +142,10 @@ static void level_scene_render_func(Scene_t* scene)
DrawText(buffer, tilemap.width * TILE_SIZE + 1, 270, 12, BLACK);
sprintf(buffer, "FPS: %u", GetFPS());
DrawText(buffer, tilemap.width * TILE_SIZE + 1, 320, 12, BLACK);
static char mempool_stats[512];
print_mempool_stats(mempool_stats);
DrawText(mempool_stats, tilemap.width * TILE_SIZE + 1, 350, 12, BLACK);
}
static void spawn_crate(Scene_t *scene, unsigned int tile_idx, bool metal)
@ -136,6 +161,9 @@ static void spawn_crate(Scene_t *scene, unsigned int tile_idx, bool metal)
p_ctransform->position.y = (tile_idx / data->tilemap.width) * TILE_SIZE;
add_component(&scene->ent_manager, p_crate, CMOVEMENTSTATE_T);
add_component(&scene->ent_manager, p_crate, CTILECOORD_COMP_T);
CHurtbox_t* p_hurtbox = add_component(&scene->ent_manager, p_crate, CHURTBOX_T);
p_hurtbox->size = p_bbox->size;
p_hurtbox->fragile = !metal;
}
static void spawn_player(Scene_t *scene)
@ -143,7 +171,7 @@ static void spawn_player(Scene_t *scene)
Entity_t *p_ent = add_entity(&scene->ent_manager, PLAYER_ENT_TAG);
CBBox_t *p_bbox = add_component(&scene->ent_manager, p_ent, CBBOX_COMP_T);
set_bbox(p_bbox, 30, 45);
set_bbox(p_bbox, PLAYER_WIDTH, PLAYER_HEIGHT);
add_component(&scene->ent_manager, p_ent, CTRANSFORM_COMP_T);
CJump_t *p_cjump = add_component(&scene->ent_manager, p_ent, CJUMP_COMP_T);
p_cjump->jump_speed = 680;
@ -153,6 +181,9 @@ static void spawn_player(Scene_t *scene)
add_component(&scene->ent_manager, p_ent, CPLAYERSTATE_T);
add_component(&scene->ent_manager, p_ent, CTILECOORD_COMP_T);
add_component(&scene->ent_manager, p_ent, CMOVEMENTSTATE_T);
CHitBox_t* p_hitbox = add_component(&scene->ent_manager, p_ent, CHITBOX_T);
p_hitbox->size = Vector2Add(p_bbox->size, (Vector2){2,2});
p_hitbox->offset = (Vector2){-1, -1};
}
static void toggle_block_system(Scene_t *scene)
@ -272,6 +303,7 @@ void init_level_scene(LevelScene_t *scene)
sc_array_add(&scene->scene.systems, &movement_update_system);
sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &tile_collision_system);
sc_array_add(&scene->scene.systems, &hitbox_update_system);
//sc_array_add(&scene->scene.systems, &update_tilemap_system);
sc_array_add(&scene->scene.systems, &state_transition_update_system);
sc_array_add(&scene->scene.systems, &player_ground_air_transition_system);

View File

@ -405,6 +405,7 @@ void tile_collision_system(Scene_t *scene)
checked_entities[other_ent_idx] = true;
Entity_t *p_other_ent = get_entity(&scene->ent_manager, other_ent_idx);
if (!p_other_ent->m_alive) continue; // To only allow one way collision check
if (p_other_ent->m_tag < p_ent->m_tag) continue; // To only allow one way collision check
CBBox_t *p_other_bbox = get_component(&scene->ent_manager, p_other_ent, CBBOX_COMP_T);
if (p_other_bbox == NULL) continue;
@ -421,51 +422,15 @@ void tile_collision_system(Scene_t *scene)
}
// TODO: Resolve all collision events
uint32_t collision_key;
sc_map_foreach(&data->collision_events, collision_key, collision_value)
{
ent_idx = (collision_key >> 16);
uint other_ent_idx = (collision_key & 0xFFFF);
Entity_t *p_ent = get_entity(&scene->ent_manager, ent_idx);
Entity_t *p_other_ent = get_entity(&scene->ent_manager, other_ent_idx);
if (!p_ent->m_alive || !p_other_ent->m_alive) continue;
if (p_ent->m_tag == PLAYER_ENT_TAG && p_other_ent->m_tag == CRATES_ENT_TAG)
{
CBBox_t *p_other_bbox = get_component(&scene->ent_manager, p_other_ent, CBBOX_COMP_T);
if (!p_other_bbox->fragile) continue;
CTransform_t *p_ctransform = get_component(&scene->ent_manager, p_ent, CTRANSFORM_COMP_T);
CBBox_t * p_bbox = get_component(&scene->ent_manager, p_ent, CBBOX_COMP_T);
CPlayerState_t * p_pstate = get_component(&scene->ent_manager, p_ent, CPLAYERSTATE_T);
CTransform_t *p_other_ct = get_component(&scene->ent_manager, p_other_ent, CTRANSFORM_COMP_T);
if (
// TODO: Check Material of the crates
p_ctransform->position.y + p_bbox->size.y <= p_other_ct->position.y
)
{
p_ctransform->velocity.y = -400;
if (p_pstate->jump_pressed)
{
p_ctransform->velocity.y = -600;
CJump_t * p_cjump = get_component(&scene->ent_manager, p_ent, CJUMP_COMP_T);
p_cjump->short_hop = false;
p_cjump->jumped = true;
}
}
{
CTileCoord_t *p_tilecoord = get_component(&scene->ent_manager, p_other_ent, CTILECOORD_COMP_T);
for (size_t i=0;i<p_tilecoord->n_tiles;++i)
{
// Use previously store tile position
// Clear from those positions
unsigned int tile_idx = p_tilecoord->tiles[i];
sc_map_del_64(&(tilemap.tiles[tile_idx].entities_set), other_ent_idx);
}
remove_entity(&scene->ent_manager, other_ent_idx);
}
}
}
//uint32_t collision_key;
//sc_map_foreach(&data->collision_events, collision_key, collision_value)
//{
// ent_idx = (collision_key >> 16);
// uint other_ent_idx = (collision_key & 0xFFFF);
// Entity_t *p_ent = get_entity(&scene->ent_manager, ent_idx);
// Entity_t *p_other_ent = get_entity(&scene->ent_manager, other_ent_idx);
// if (!p_ent->m_alive || !p_other_ent->m_alive) continue;
//}
sc_map_clear_32(&data->collision_events);
// Level boundary collision
@ -749,6 +714,89 @@ void update_tilemap_system(Scene_t *scene)
}
}
void hitbox_update_system(Scene_t *scene)
{
static bool checked_entities[MAX_COMP_POOL_SIZE] = {0};
LevelSceneData_t *data = (LevelSceneData_t *)scene->scene_data;
TileGrid_t tilemap = data->tilemap;
unsigned int ent_idx;
CHitBox_t* p_hitbox;
//sc_map_foreach_value(&scene->ent_manager.entities_map[PLAYER_ENT_TAG], p_player)
sc_map_foreach(&scene->ent_manager.component_map[CHITBOX_T], ent_idx, p_hitbox)
{
Entity_t *p_ent = get_entity(&scene->ent_manager, ent_idx);
CTransform_t* p_ctransform = get_component(&scene->ent_manager, p_ent, CTRANSFORM_COMP_T);
Vector2 hitbox_pos = Vector2Add(p_ctransform->position, p_hitbox->offset);
unsigned int tile_x1 = (hitbox_pos.x) / TILE_SIZE;
unsigned int tile_y1 = (hitbox_pos.y) / TILE_SIZE;
unsigned int tile_x2 = (hitbox_pos.x + p_hitbox->size.x - 1) / TILE_SIZE;
unsigned int tile_y2 = (hitbox_pos.y + p_hitbox->size.y - 1) / TILE_SIZE;
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;
unsigned int other_ent_idx;
memset(checked_entities, 0, sizeof(checked_entities));
sc_map_foreach_key(&tilemap.tiles[tile_idx].entities_set, other_ent_idx)
{
if (other_ent_idx == ent_idx) continue;
if (checked_entities[other_ent_idx]) continue;
Entity_t *p_other_ent = get_entity(&scene->ent_manager, other_ent_idx);
if (!p_other_ent->m_alive) continue; // To only allow one way collision check
if (p_other_ent->m_tag < p_ent->m_tag) continue; // To only allow one way collision check
CHurtbox_t *p_other_hurtbox = get_component(&scene->ent_manager, p_other_ent, CHURTBOX_T);
if (p_other_hurtbox == NULL) continue;
CTransform_t *p_other_ct = get_component(&scene->ent_manager, p_other_ent, CTRANSFORM_COMP_T);
Vector2 hurtbox_pos = Vector2Add(p_other_ct->position, p_other_hurtbox->offset);
Vector2 overlap;
if (find_AABB_overlap(hitbox_pos, p_hitbox->size, hurtbox_pos, p_other_hurtbox->size, &overlap))
{
if (!p_other_hurtbox->fragile) continue;
if (p_other_ent->m_tag == CRATES_ENT_TAG)
{
CBBox_t * p_bbox = get_component(&scene->ent_manager, p_ent, CBBOX_COMP_T);
CPlayerState_t * p_pstate = get_component(&scene->ent_manager, p_ent, CPLAYERSTATE_T);
if (
// TODO: Check Material of the crates
p_ctransform->position.y + p_bbox->size.y <= p_other_ct->position.y
)
{
p_ctransform->velocity.y = -400;
if (p_pstate->jump_pressed)
{
p_ctransform->velocity.y = -600;
CJump_t * p_cjump = get_component(&scene->ent_manager, p_ent, CJUMP_COMP_T);
p_cjump->short_hop = false;
p_cjump->jumped = true;
}
}
CTileCoord_t *p_tilecoord = get_component(&scene->ent_manager, p_other_ent, CTILECOORD_COMP_T);
for (size_t i=0;i<p_tilecoord->n_tiles;++i)
{
// Use previously store tile position
// Clear from those positions
unsigned int tile_idx = p_tilecoord->tiles[i];
sc_map_del_64(&(tilemap.tiles[tile_idx].entities_set), other_ent_idx);
}
remove_entity(&scene->ent_manager, other_ent_idx);
}
}
}
}
}
}
}
void init_level_scene_data(LevelSceneData_t *data)
{
sc_map_init_32(&data->collision_events, 128, 0);

View File

@ -12,4 +12,5 @@ void movement_update_system(Scene_t* scene);
void player_ground_air_transition_system(Scene_t* scene);
void state_transition_update_system(Scene_t *scene);
void update_tilemap_system(Scene_t *scene);
void hitbox_update_system(Scene_t *scene);
#endif // __GAME_SYSTEMS_H