#ifndef __GE_COLLISION_SYSTEM_H__ #define __GE_COLLISION_SYSTEM_H__ #include "ge_entity.h" #include "ge_collision_component.h" #include #include #include typedef void (*ge_collision_response_func_t)( void* ctx, ge_entity_t* entity_from, ge_entity_t* entity_to, const ge_vector2i_t* vector // 反向推荐速度 ); typedef struct ge_collision_system { ge_ecs_storage_t* ecs; void* ctx; ge_collision_response_func_t callback; } ge_collision_system_t; #define _GE_COLLIDER_MASK (GE_COLLIDER_MASK | GE_COMPONENT_ACVIVE) /** * must transit to global position before calling this function */ static inline int check_box_collision(ge_collision_box_t* a, ge_collision_box_t* b) { return (a->relative_pos.x < b->relative_pos.x + b->size.x) && (a->relative_pos.x + a->size.x > b->relative_pos.x) && (a->relative_pos.y < b->relative_pos.y + b->size.y) && (a->relative_pos.y + a->size.y > b->relative_pos.y); } static inline ge_vector2i_t check_tilemap_collision( ge_collision_box_t* entity_collision, ge_vector2i_t* entity_pos, ge_collision_tilemap_t* tilemap, ge_vector2i_t* tilemap_pos ) { Assert(tilemap->tile_size > 0); Assert(tilemap->map_size.x > 0 && tilemap->map_size.y > 0); Assert(entity_collision->size.x > 0 && entity_collision->size.y > 0); const ge_unit_t tile_size_int = tilemap->tile_size; const ge_unit_t grid_cols = tilemap->map_size.x; const ge_unit_t grid_rows = tilemap->map_size.y; // 计算实体碰撞盒在瓦片地图局部坐标系中的位置 const ge_vector2i_t local_entity_pos = GE_VEC2I_SUB( *entity_pos, *tilemap_pos ); // 计算实体边界(闭区间) const int entity_left = local_entity_pos.x; const int entity_top = local_entity_pos.y; const int entity_right = entity_left + entity_collision->size.x; const int entity_bottom = entity_top + entity_collision->size.y; // 计算覆盖的瓦片范围 const int start_tile_x = GE_MAX(0, entity_left / tile_size_int); const int start_tile_y = GE_MAX(0, entity_top / tile_size_int); const int end_tile_x = GE_MIN(grid_cols - 1, entity_right / tile_size_int); const int end_tile_y = GE_MIN(grid_rows - 1, entity_bottom / tile_size_int); ge_vector2i_t correction = GE_VEC2I(0, 0); // 默认无纠正 for (int y = start_tile_y; y <= end_tile_y; y++) { for (int x = start_tile_x; x <= end_tile_x; x++) { if (tilemap->layers[y * grid_cols + x] == 0) continue; // 计算当前瓦片的边界 const int tile_left = x * tile_size_int; const int tile_top = y * tile_size_int; const int tile_right = tile_left + tile_size_int; const int tile_bottom = tile_top + tile_size_int; // 检查实体是否实际接触到瓦片内部 const int overlap_x = entity_left < tile_right && entity_right > tile_left; const int overlap_y = entity_top < tile_bottom && entity_bottom > tile_top; if (overlap_x && overlap_y) { // 计算重叠区域 const int overlap_left = GE_MAX(entity_left, tile_left); const int overlap_right = GE_MIN(entity_right, tile_right); const int overlap_top = GE_MAX(entity_top, tile_top); const int overlap_bottom = GE_MIN(entity_bottom, tile_bottom); const int overlap_width = overlap_right - overlap_left; const int overlap_height = overlap_bottom - overlap_top; // 计算实体中心到瓦片中心的向量 const int entity_center_x = (entity_left + entity_right) / 2; const int entity_center_y = (entity_top + entity_bottom) / 2; const int tile_center_x = (tile_left + tile_right) / 2; const int tile_center_y = (tile_top + tile_bottom) / 2; const int dx = entity_center_x - tile_center_x; const int dy = entity_center_y - tile_center_y; // 确定主要分离方向(选择重叠量较小的轴) if (overlap_width < overlap_height) { // X轴方向分离(左右方向) correction.x = (dx > 0) ? overlap_width : -overlap_width; } else { // Y轴方向分离(上下方向) correction.y = (dy > 0) ? overlap_height : -overlap_height; } // 返回第一个找到的碰撞纠正(最大影响) return correction; } } } return correction; // 无碰撞时返回零向量 } // FIXME bad practice static inline void ge_collision_system_run_all(ge_collision_system_t* ctx) { Assert(ctx != NULL); if (ctx->callback == NULL) return; ge_ecs_storage_t* ecs = ctx->ecs; Assert(ecs != NULL); for (int i = 1; i <= ecs->count && i < GE_ECS_MAX; ++i) { ge_entity_t* entity_i = &ecs->entities[i]; ge_ecs_mask_t mask_i = entity_i->component_mask; if ((mask_i & _GE_COLLIDER_MASK) != _GE_COLLIDER_MASK) continue; ge_collision_component_t* comp_i = &entity_i->collision; Assert(comp_i != NULL); // FIXME this design is bad /** * 仅仅尝试 (box, box) 和 (box, tilemap) 的碰撞 */ if (comp_i->type != GE_COLLISION_COMP_TYPE_BOX) continue; for (int j = 1; j <= ecs->count && j < GE_ECS_MAX; ++j) { if (i == j) continue; ge_entity_t* entity_j = &ecs->entities[j]; ge_ecs_mask_t mask_j = entity_j->component_mask; if ((mask_j & _GE_COLLIDER_MASK) != _GE_COLLIDER_MASK) continue; ge_collision_component_t* comp_j = &entity_j->collision; Assert(comp_j != NULL); /** * using layer/mask to accelerate check */ if ( !(comp_i->mask & comp_j->layers) ) { continue; } /** * Check collision */ switch (comp_j->type) { case GE_COLLISION_COMP_TYPE_BOX: { int ret = check_box_collision(&comp_i->collider.box, &comp_j->collider.box); if (!ret) ctx->callback(ctx->ctx, entity_i, entity_j, NULL); break; } case GE_COLLISION_COMP_TYPE_TILEMAP: { ge_vector2i_t ret = check_tilemap_collision( &comp_i->collider.box, &entity_i->position, &comp_j->collider.tilemap, &entity_j->position ); if (ret.x == 0 && ret.y == 0) { continue; } ctx->callback(ctx->ctx, entity_i, entity_j, &ret); break; } default: LOG_WARN("Collision system: Unknown collision type"); break; } } } } #endif /* __GE_COLLISION_SYSTEM_H__ */