feat(game_core): 重构游戏引擎并添加新功能

- 重构了游戏引擎的核心逻辑和架构
- 添加了新的实体组件系统(ECS)
- 实现了简单的碰撞检测和响应
- 新增了地图和子弹功能
- 优化了输入处理和渲染逻辑
- 调整了游戏控制方式
This commit is contained in:
ZZY
2025-07-02 12:14:57 +08:00
parent 89bede93a9
commit b5b2c90e75
32 changed files with 856 additions and 441 deletions

View File

@@ -0,0 +1,34 @@
#ifndef __GE_COLLISION_COMPONENT_H__
#define __GE_COLLISION_COMPONENT_H__
#include <utils/ge_vector2i.h>
typedef unsigned char ge_layers_t;
typedef enum {
GE_COLLISION_COMP_TYPE_NONE,
GE_COLLISION_COMP_TYPE_BOX,
GE_COLLISION_COMP_TYPE_TILEMAP,
} ge_collision_type_t;
typedef struct ge_collision_box {
ge_vector2i_t relative_pos;
ge_vector2i_t size;
} ge_collision_box_t;
typedef struct ge_collision_tilemap {
ge_vector2i_t map_size;
ge_unit_t tile_size;
ge_layers_t* layers;
} ge_collision_tilemap_t;
typedef struct ge_collision_component {
ge_collision_type_t type;
ge_layers_t layers;
ge_layers_t mask;
struct {
ge_collision_box_t box;
ge_collision_tilemap_t tilemap;
} collider;
} ge_collision_component_t;
#endif /* __GE_COLLISION_COMPONENT_H__ */

View File

@@ -0,0 +1,186 @@
#ifndef __GE_COLLISION_SYSTEM_H__
#define __GE_COLLISION_SYSTEM_H__
#include "ge_entity.h"
#include "ge_collision_component.h"
#include <utils/ge_vector2i.h>
#include <utils/ge_maroc.h>
#include <pynic_log/pynic_log.h>
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__ */

View File

@@ -1,10 +1,10 @@
#ifndef __GE_ENTIRY_H__
#define __GE_ENTIRY_H__
#include <ge_core.h>
#include <utils/ge_vector2i.h>
#include "ge_render_component.h"
#include "ge_physics_component.h"
#include "ge_collision_component.h"
#include <stdint.h>
typedef uint16_t ge_ecs_id_t; // 支持65536个实体
@@ -15,8 +15,8 @@ typedef enum {
GE_COMPONENT_POSITION = 1 << 1,
GE_COMPONENT_TRANSFORM = 1 << 2, // TODO not implimented
GE_COMPONENT_RENDERABLE = 1 << 3,
GE_COMPONENT_PHYSICS_BODY = 1 << 4, // TODO not implimented
GE_COMPONENT_COLLIDER = 1 << 5, // TODO not implimented
GE_COMPONENT_PHYSICS_BODY = 1 << 4,
GE_COMPONENT_COLLIDER = 1 << 5,
GE_COMPONENT_TIMED_LIFE = 1 << 6, // TODO not implimented
} ge_ecs_mask_t;
@@ -24,13 +24,17 @@ typedef enum {
(GE_COMPONENT_POSITION | GE_COMPONENT_RENDERABLE)
#define GE_PHYSICS_MASK \
(GE_COMPONENT_POSITION | GE_COMPONENT_PHYSICS_BODY)
#define GE_COLLIDER_MASK \
(GE_COMPONENT_POSITION | GE_COMPONENT_COLLIDER)
typedef struct ge_entity {
int user_type;
ge_ecs_id_t id;
ge_ecs_mask_t component_mask;
ge_vector2i_t position;
ge_render_component_t renderable;
ge_physics_component_t physics_body;
ge_collision_component_t collision;
} ge_entity_t;
typedef struct {
@@ -43,7 +47,6 @@ typedef struct ge_ecs {
ge_ecs_storage_t storage;
} ge_ecs_t;
static inline ge_ecs_id_t ge_ecs_add_entity(ge_ecs_t* ecs, ge_entity_t** entity) {
ge_ecs_id_t id = ++ecs->storage.count;
if (id >= GE_ECS_MAX) {

View File

@@ -7,9 +7,9 @@
#define GE_PHYSICS_ACCELERATION_BIT 3
typedef enum {
GE_PHYSICS_COMPONENT_TYPE_NONE = 1 << 0,
GE_PHYSICS_COMPONENT_TYPE_VELOCITY = 1 << 1,
GE_PHYSICS_COMPONENT_TYPE_ACCELERATION = 1 << 2,
GE_PHYSICS_COMP_TYPE_NONE = 1 << 0,
GE_PHYSICS_COMP_TYPE_VELOCITY = 1 << 1,
GE_PHYSICS_COMP_TYPE_ACCELERATION = 1 << 2,
} ge_physics_component_type_t;
typedef struct ge_physics_component {

View File

@@ -25,11 +25,11 @@ ge_physics_system_run_all(ge_physics_system_t* ctx) {
Assert(comp != NULL);
ge_physics_component_type_t type= comp->type;
if (type & GE_PHYSICS_COMPONENT_TYPE_ACCELERATION) {
if (type & GE_PHYSICS_COMP_TYPE_ACCELERATION) {
comp->velocity.x += comp->acceleration.x >> GE_PHYSICS_ACCELERATION_BIT;
comp->velocity.y += comp->acceleration.y >> GE_PHYSICS_ACCELERATION_BIT;
}
if (type & GE_PHYSICS_COMPONENT_TYPE_VELOCITY) {
if (type & GE_PHYSICS_COMP_TYPE_VELOCITY) {
entity->position.x += comp->velocity.x >> GE_PHYSICS_VELOCITY_BIT;
entity->position.y += comp->velocity.y >> GE_PHYSICS_VELOCITY_BIT;
}

View File

@@ -4,14 +4,21 @@
#include <interface/ge_render.h>
typedef enum {
GE_RENDER_COMPONENT_TYPE_NONE,
GE_RENDER_COMPONENT_TYPE_POINT,
GE_RENDER_COMPONENT_TYPE_TEXT,
GE_RENDER_COMPONENT_TYPE_RECT,
GE_RENDER_COMPONENT_TYPE_RECOURCE,
GE_RENDER_COMP_TYPE_NONE,
GE_RENDER_COMP_TYPE_POINT,
GE_RENDER_COMP_TYPE_TEXT,
GE_RENDER_COMP_TYPE_RECT,
GE_RENDER_COMP_TYPE_RECOURCE,
GE_RENDER_COMP_TYPE_TILEMAP,
GE_RENDER_COMP_TYPE_SIZE, /* never used, only for calulate size */
} ge_render_component_type_t;
typedef struct ge_render_component {
struct ge_render_component;
typedef struct ge_render_component ge_render_component_t;
struct ge_render_component {
ge_render_component_type_t type;
union {
struct {
@@ -21,7 +28,12 @@ typedef struct ge_render_component {
ge_render_pos2_t size;
ge_render_color_t color;
} rect;
struct {
ge_render_pos2_t map_size; // number of tiles
ge_render_unit_t tile_size;
ge_render_component_t** components; // not be tilemap
} tilemap;
} data;
} ge_render_component_t;
};
#endif // __GE_RENDER_COMPONENT_H__

View File

@@ -18,6 +18,43 @@ ge_render_system_init(ge_render_system_t* ctx, ge_render_t* render) {
#define _GE_RENDERABLE_MASK (GE_RENDERABLE_MASK | GE_COMPONENT_ACVIVE)
static inline void
ge_render_system_draw_basic(
ge_render_system_t* ctx,
ge_render_component_t* comp,
ge_render_pos2_t* pos) {
Assert(comp->type < GE_RENDER_COMP_TYPE_SIZE);
switch (comp->type) {
case GE_RENDER_COMP_TYPE_POINT:
ctx->render->func_draw_point(
ctx->render,
pos,
comp->data.point.color
);
break;
case GE_RENDER_COMP_TYPE_RECT:
ctx->render->func_draw_rect(
ctx->render,
&(ge_render_rect_t) {
*pos,
comp->data.rect.size,
},
comp->data.rect.color
);
break;
case GE_RENDER_COMP_TYPE_TEXT:
TODO();
break;
case GE_RENDER_COMP_TYPE_RECOURCE:
TODO();
break;
default:
LOG_WARN("render component not set Avaliable type %d at [%d,%d]",
comp->type, pos->x, pos->y);
break;
}
}
static inline void
ge_render_system_draw_all(ge_render_system_t* ctx) {
Assert(ctx != NULL);
@@ -35,33 +72,22 @@ ge_render_system_draw_all(ge_render_system_t* ctx) {
ge_render_component_t* comp = &entity->renderable;
Assert(comp != NULL);
switch (comp->type) {
case GE_RENDER_COMPONENT_TYPE_POINT:
ctx->render->func_draw_point(
ctx->render,
&pos,
comp->data.point.color
);
break;
case GE_RENDER_COMPONENT_TYPE_RECT:
ctx->render->func_draw_rect(
ctx->render,
&(ge_render_rect_t) {
pos,
comp->data.rect.size,
},
comp->data.rect.color
);
break;
case GE_RENDER_COMPONENT_TYPE_TEXT:
TODO();
break;
case GE_RENDER_COMPONENT_TYPE_RECOURCE:
TODO();
break;
default:
LOG_WARN("render component not set Avaliable type %d, id %d", comp->type, i);
break;
if (comp->type != GE_RENDER_COMP_TYPE_TILEMAP) {
ge_render_system_draw_basic(ctx, comp, &pos);
continue;
}
for (int y = 0; y < comp->data.tilemap.map_size.y; y++) {
for (int x = 0; x < comp->data.tilemap.map_size.x; x++, pos.x += comp->data.tilemap.tile_size) {
ge_render_component_t* tile = (comp->data.tilemap.components)
[y * comp->data.tilemap.map_size.x + x];
if (tile == NULL || tile->type == GE_RENDER_COMP_TYPE_NONE) {
continue;
}
ge_render_system_draw_basic(ctx, tile, &pos);
}
pos.x -= comp->data.tilemap.map_size.x * comp->data.tilemap.tile_size;
pos.y += comp->data.tilemap.tile_size;
}
}
}

View File

@@ -1,10 +0,0 @@
#ifndef __GE_EVENTS_BUS_H__
#define __GE_EVENTS_BUS_H__
#include <ge_common.h>
typedef struct {
DECLARE_GE_KFIFO(event_queue, void*, 16, ge_queue_t);
} ge_event_queue_t;
#endif // __GE_EVENTS_BUS_H__

View File

@@ -29,10 +29,11 @@ static inline void ge_init(ge_core_t* core) {
}
ge_render_system_init(&core->_systems.render, &core->_render);
// TODO using other storage system
core->_systems.render.ecs = &core->ecs.storage;
core->ecs.storage.count = 0;
core->_systems.render.ecs = &core->ecs.storage;
core->_systems.physics.ecs = &core->ecs.storage;
core->_systems.collision.ecs = &core->ecs.storage;
}
void ge_engine_run(ge_core_t *core) {
@@ -45,6 +46,7 @@ void ge_engine_run(ge_core_t *core) {
ge_physics_system_run_all(&core->_systems.physics);
GE_SAFE_CALL(core->callbacks.process, core);
ge_collision_system_run_all(&core->_systems.collision);
ge_render_system_draw_all(&core->_systems.render);
GE_SAFE_CALL(core->_render.func_flush, &core->_render);

View File

@@ -5,6 +5,7 @@
#include <ecs/ge_entity.h>
#include <ecs/ge_render_system.h>
#include <ecs/ge_physics_system.h>
#include <ecs/ge_collision_system.h>
struct ge_engine_core;
typedef struct ge_engine_core ge_core_t;
@@ -38,6 +39,7 @@ struct ge_engine_core {
struct {
ge_render_system_t render;
ge_physics_system_t physics;
ge_collision_system_t collision;
} _systems;
ge_render_t _render;
ge_timer_t _timer;

View File

@@ -22,10 +22,18 @@ typedef struct ge_render ge_render_t;
typedef int(*ge_render_init_func_t) (ge_render_t* ctx, const ge_render_pos2_t* init_screen_size);
typedef int(*ge_render_flush_func_t) (ge_render_t* ctx);
typedef int(*ge_render_draw_point_func_t) (ge_render_t* ctx, const ge_render_pos2_t* pos, ge_render_color_t color);
typedef int(*ge_render_draw_rect_func_t) (ge_render_t* ctx, const ge_render_rect_t* rect, ge_render_color_t color);
typedef int(*ge_render_draw_text_func_t) (ge_render_t* ctx, const ge_render_pos2_t* pos, const char* text);
typedef int(*ge_render_draw_resource_func_t)(ge_render_t* ctx, const ge_render_pos2_t* pos, const ge_resource_t* res);
typedef int(*ge_render_draw_point_func_t)
(ge_render_t* ctx, const ge_render_pos2_t* pos, ge_render_color_t color);
typedef int(*ge_render_draw_rect_func_t)
(ge_render_t* ctx, const ge_render_rect_t* rect, ge_render_color_t color);
typedef int(*ge_render_draw_text_func_t) (
ge_render_t* ctx,
const ge_render_pos2_t* pos,
ge_render_unit_t font_size,
ge_render_color_t font_color,
const char* text);
typedef int(*ge_render_draw_resource_func_t)
(ge_render_t* ctx, const ge_render_pos2_t* pos, const ge_resource_t* res);
struct ge_render {
void* context;

View File

@@ -1,20 +0,0 @@
#ifndef __GE_COLLISION_H__
#define __GE_COLLISION_H__
#include "ge_physics.h"
typedef struct ge_collision {
ge_phy_layers_t layers;
ge_phy_layers_t mask;
ge_vector2i_t position;
ge_vector2i_t size;
} ge_phy_box_t;
static inline int check_box_collision(ge_phy_box_t* a, ge_phy_box_t* b) {
return (a->position.x < b->position.x + b->size.x) &&
(a->position.x + a->size.x > b->position.x) &&
(a->position.y < b->position.y + b->size.y) &&
(a->position.y + a->size.y > b->position.y);
}
#endif // __GE_COLLISION_H__

View File

@@ -1,58 +0,0 @@
#include "ge_physics.h"
#include "ge_collision_box.h"
typedef struct {
ge_vector2i_t position;
ge_vector2i_t size;
ge_int_t tile_size;
ge_phy_layers_t* layers; // 二维layer数组
} ge_phy_tilemap_t;
static inline int check_tilemap_collision(ge_phy_tilemap_t* tilemap, ge_phy_box_t* entity_collision) {
Assert(tilemap->tile_size > 0);
const int tile_size_int = tilemap->tile_size;
// 计算瓦片地图的网格尺寸(列数和行数)
const int grid_cols = tilemap->size.x / tile_size_int;
const int grid_rows = tilemap->size.y / tile_size_int;
// 计算实体碰撞盒在瓦片地图局部坐标系中的位置
const ge_vector2i_t local_entity_pos = GE_VEC2I_SUB(
entity_collision->position,
tilemap->position
);
// 计算实体边界(使用闭区间)
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 - 1; // 闭区间右边界
const int entity_bottom = entity_top + entity_collision->size.y - 1; // 闭区间下边界
// 计算覆盖的瓦片范围
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);
// 遍历实体覆盖的瓦片区域
for (int y = start_tile_y; y <= end_tile_y; y++) {
for (int x = start_tile_x; x <= end_tile_x; x++) {
// 计算当前瓦片的边界(开区间)
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 && tilemap->layers[y * grid_cols + x] != 0) {
return 1; // 检测到碰撞
}
}
}
return 0; // 无碰撞
}

View File

@@ -1,27 +0,0 @@
// // 在 ge_common.h 中定义通用碰撞类型
// typedef enum ge_collision_type {
// GE_COLLISION_TYPE_BOX, // 实体间碰撞
// GE_COLLISION_TYPE_TILEMAP // 实体与瓦片地图碰撞
// } ge_collision_type_t;
// // 通用碰撞数据结构
// typedef struct {
// ge_collision_type_t type;
// void* entityA; // 主要实体(通常是被检测的实体)
// void* entityB; // 对于BOX碰撞这是另一个实体对于TILEMAP这是瓦片地图
// ge_vector2i_t collision_point;
// union {
// struct {
// int tile_x;
// int tile_y;
// } tilemap_data; // 瓦片地图碰撞特有数据
// struct {
// // 可以添加实体间碰撞特有数据
// } box_data;
// } specific;
// } ge_collision_event_t;
// // 碰撞回调函数类型
// typedef void (*ge_collision_callback_t)(ge_collision_event_t* event);

View File

@@ -1,8 +0,0 @@
#ifndef __GE_PHYSICS_H__
#define __GE_PHYSICS_H__
#include <ge_common.h>
typedef ge_u8_t ge_phy_layers_t;
#endif // __GE_PHYSICS_H__

View File

@@ -7,6 +7,8 @@
* @brief 2D整数向量库 (仿Godot Vector2i设计) 不依赖任何库的单文件
* @defgroup Vector2 2D向量操作
*/
#include <stdint.h>
typedef int32_t ge_unit_t; /**< 坐标值类型定义 */
// #define GE_VEC2I_USE_SHORT_NAMES
@@ -14,7 +16,6 @@
#undef GE_ABS
#define GE_ABS(x) ((x) > 0 ? (x) : -(x))
#endif
typedef int32_t ge_unit_t; /**< 坐标值类型定义 */
/**
* @struct ge_vector2_t