From 5ce660e3a6e0739ee515d6a1ed88981c6890846e Mon Sep 17 00:00:00 2001 From: ZZY <2450266535@qq.com> Date: Tue, 24 Jun 2025 02:16:01 +0800 Subject: [PATCH] =?UTF-8?q?feat(game):=20=E6=B7=BB=E5=8A=A0=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=B8=B8=E6=88=8F=E5=BC=95=E6=93=8E=E5=92=8C=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增游戏引擎核心模块,包括初始化和运行逻辑 - 实现基本的渲染功能,支持控制台输出 - 添加物理引擎基础,包括碰撞检测 - 集成日志系统,用于调试和信息输出 - 创建窗口和输入管理模块 --- .gitignore | 15 ++ game_core/Makefile | 65 +++++ game_core/main.c | 72 ++++++ game_core/plantform/win_term/interface.h | 74 ++++++ game_core/plantform/win_term/win_term.c | 178 +++++++++++++ game_core/plantform/win_term/win_term.h | 14 + game_engine/components/ge_entity.h | 20 ++ game_engine/components/ge_sprite.h | 6 + game_engine/events/ge_events_bus.h | 10 + game_engine/ge_common.h | 12 + game_engine/ge_config.h | 27 ++ game_engine/ge_core.c | 54 ++++ game_engine/ge_core.h | 44 ++++ game_engine/physics/ge_collision_box.h | 20 ++ game_engine/physics/ge_collision_tilemap.h | 58 +++++ game_engine/physics/ge_collison.h | 27 ++ game_engine/physics/ge_physics.h | 8 + game_engine/pynic_log/README.md | 212 +++++++++++++++ game_engine/pynic_log/pynic_color.h | 59 +++++ game_engine/pynic_log/pynic_log.h | 288 +++++++++++++++++++++ game_engine/render/ge_render.h | 30 +++ game_engine/resources/ge_resources.h | 38 +++ game_engine/timer/ge_fps.c | 53 ++++ game_engine/timer/ge_fps.h | 31 +++ game_engine/timer/ge_timer.c | 0 game_engine/timer/ge_timer.h | 16 ++ game_engine/utils/ge_kfifo.h | 236 +++++++++++++++++ game_engine/utils/ge_maroc.h | 129 +++++++++ game_engine/utils/ge_static_alloc.h | 6 + game_engine/utils/ge_vector2i.h | 254 ++++++++++++++++++ 30 files changed, 2056 insertions(+) create mode 100644 .gitignore create mode 100644 game_core/Makefile create mode 100644 game_core/main.c create mode 100644 game_core/plantform/win_term/interface.h create mode 100644 game_core/plantform/win_term/win_term.c create mode 100644 game_core/plantform/win_term/win_term.h create mode 100644 game_engine/components/ge_entity.h create mode 100644 game_engine/components/ge_sprite.h create mode 100644 game_engine/events/ge_events_bus.h create mode 100644 game_engine/ge_common.h create mode 100644 game_engine/ge_config.h create mode 100644 game_engine/ge_core.c create mode 100644 game_engine/ge_core.h create mode 100644 game_engine/physics/ge_collision_box.h create mode 100644 game_engine/physics/ge_collision_tilemap.h create mode 100644 game_engine/physics/ge_collison.h create mode 100644 game_engine/physics/ge_physics.h create mode 100644 game_engine/pynic_log/README.md create mode 100644 game_engine/pynic_log/pynic_color.h create mode 100644 game_engine/pynic_log/pynic_log.h create mode 100644 game_engine/render/ge_render.h create mode 100644 game_engine/resources/ge_resources.h create mode 100644 game_engine/timer/ge_fps.c create mode 100644 game_engine/timer/ge_fps.h create mode 100644 game_engine/timer/ge_timer.c create mode 100644 game_engine/timer/ge_timer.h create mode 100644 game_engine/utils/ge_kfifo.h create mode 100644 game_engine/utils/ge_maroc.h create mode 100644 game_engine/utils/ge_static_alloc.h create mode 100644 game_engine/utils/ge_vector2i.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29403de --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.*/ +!.gitignore +build/ +test/ +*.txt + +*.obj +*.dll +*.lib +*.exe + +*.o +*.a +*.so +*.out diff --git a/game_core/Makefile b/game_core/Makefile new file mode 100644 index 0000000..3ab56a5 --- /dev/null +++ b/game_core/Makefile @@ -0,0 +1,65 @@ +# 编译器设置 +CC = gcc +CFLAGS = -Wall -Wextra -g -I../game_engine +LDFLAGS = + +# 目录设置 +ROOT_DIR := . +BUILD_DIR := build +ENGINE_DIR := ../game_engine + +# 手动指定源文件目录 +SRC_DIRS = $(ROOT_DIR) \ + $(ROOT_DIR)/test \ + $(ROOT_DIR)/plantform \ + $(ROOT_DIR)/plantform/win_term \ + $(ENGINE_DIR) \ + $(ENGINE_DIR)/components \ + $(ENGINE_DIR)/events \ + $(ENGINE_DIR)/physics \ + $(ENGINE_DIR)/pynic_log \ + $(ENGINE_DIR)/render \ + $(ENGINE_DIR)/resources \ + $(ENGINE_DIR)/timer \ + $(ENGINE_DIR)/utils + +SRCS = $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c)) +OBJS := $(patsubst $(ROOT_DIR)/%.c,$(BUILD_DIR)/core/%.o,$(filter $(ROOT_DIR)/%,$(SRCS))) +OBJS += $(patsubst $(ENGINE_DIR)/%.c,$(BUILD_DIR)/engine/%.o,$(filter $(ENGINE_DIR)/%,$(SRCS))) + + +TARGET = $(BUILD_DIR)/../game.exe + +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +# 当前目录下的文件编译规则 +$(BUILD_DIR)/core/%.o: $(ROOT_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + +# 引擎目录下的文件编译规则 +$(BUILD_DIR)/engine/%.o: $(ENGINE_DIR)/%.c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +debug: + @echo "Source files:" + @echo $(SRCS) + @echo "Object files:" + @echo $(OBJS) + +all: build + +build: $(TARGET) + +run: build + ./$(TARGET) + +clean: + rm -rf $(BUILD_DIR) + +.PHONY: all build run clean debug diff --git a/game_core/main.c b/game_core/main.c new file mode 100644 index 0000000..d53ad46 --- /dev/null +++ b/game_core/main.c @@ -0,0 +1,72 @@ +#include +#include +#include + +#define GE_VEC2I_USE_SHORT_NAMES +#define _pynic_logout_printf(...) fprintf(fp , ##__VA_ARGS__) + +#include +#include "plantform/win_term/interface.h" +FILE* fp; +logger_t logger; + +static void log_handler +(log_level_t level, const char* module, const char* file, int line, const char* message) { + fprintf(fp, "[%s] %s:%d | %s: %s\n", + pynic_level_str(level), file, line, module, message); + fflush(fp); +} + +void init(ge_core_t* core) { + register_win_term(core); + register_win_timer(core); + fp = fopen("./log.txt", "w+"); + init_logger_ex(&logger, "game", log_handler); + Assert(fp != NULL); +} + +void run(ge_core_t* core) { + static ge_vector2i_t pos = vec2i(0, 0); + // LOG_INFO("vec2: %d, %d", a.x, a.y); + + int key = terminal_get_key((win_term_t*)core->render.content); + switch (key) { + case 'w': + MLOG_INFO(&logger, "w"); + core->render.clear(&core->render); + pos = vec2i_add(pos, GE_VEC2I_UP); + core->render.draw(&core->render, pos, "@"); + break; + case 'a': + MLOG_INFO(&logger, "a"); + core->render.clear(&core->render); + pos = vec2i_add(pos, GE_VEC2I_LEFT); + core->render.draw(&core->render, pos, "@"); + break; + case 's': + MLOG_INFO(&logger, "s"); + core->render.clear(&core->render); + pos = vec2i_add(pos, GE_VEC2I_DOWN); + core->render.draw(&core->render, pos, "@"); + break; + case 'd': + MLOG_INFO(&logger, "d"); + core->render.clear(&core->render); + pos = vec2i_add(pos, GE_VEC2I_RIGHT); + core->render.draw(&core->render, pos, "@"); + break; + case 'q': + core->state = GE_ENGINE_STATE_EXIT; + MLOG_INFO(&logger, "exit"); + break; + } +} + +int main(void) { + ge_core_t core; + ge_engine_init(&core); + core.callbacks.init = init; + core.callbacks.run = run; + ge_engine_run(&core); + return 0; +} diff --git a/game_core/plantform/win_term/interface.h b/game_core/plantform/win_term/interface.h new file mode 100644 index 0000000..c8c2c26 --- /dev/null +++ b/game_core/plantform/win_term/interface.h @@ -0,0 +1,74 @@ +#ifndef __WIN_TERM_INTERFACE_H__ +#define __WIN_TERM_INTERFACE_H__ + +// #include +#include +#include +#include "win_term.h" +// typedef void(*ge_render_data_func_t)(const void* content, ge_vector2i_t pos, const char* data); +static inline void register_win_term(ge_core_t* core); + +void win_sleep_ms(uint32_t ms); +uint32_t win_get_timer_ms(); +static inline void register_win_timer(ge_core_t* core) { + core->timer.sleep_ms = (ge_sleep_ms_func_t)win_sleep_ms; + core->timer.get_ms = (ge_get_ms_func_t)win_get_timer_ms; +} + +static void win_render_draw(ge_render_t* ctx, ge_vector2i_t pos, const char* data) { + win_term_t* term = (win_term_t*)ctx->content; + const int start_x = pos.x; + const int start_y = pos.y; + int current_x = start_x; + int current_y = start_y; + + const char* ptr = data; + while (*ptr) { + if (*ptr == '\n') { + // 换行处理:重置X坐标,Y坐标下移一行 + current_x = start_x; + current_y++; + ptr++; + continue; + } + + // 处理制表符(可选) + if (*ptr == '\t') { + int spaces = 4 - ((current_x - start_x) % 4); // 4空格制表符 + for (int i = 0; i < spaces; i++) { + terminal_print(term, current_x, current_y, " "); + current_x++; + } + ptr++; + continue; + } + + // 普通字符处理 + char char_str[2] = { *ptr, '\0' }; + terminal_print(term, current_x, current_y, char_str); + + current_x++; + ptr++; + } +} + +static void win_render_clear(ge_render_t* ctx) { + terminal_clean((win_term_t*)ctx->content); +} + +static void win_render_getsize(ge_render_t* ctx, ge_vector2i_t* size) { + size->y = terminal_get_height((win_term_t*)ctx->content); + size->x = terminal_get_width((win_term_t*)ctx->content); +} + +static inline void register_win_term(ge_core_t* core) { + win_term_t* ctx = terminal_init(); + Assert(ctx != NULL); + core->render.content = ctx; + core->render.init = NULL; + core->render.draw = win_render_draw; + core->render.clear = win_render_clear; + core->render.getsize = win_render_getsize; +} + +#endif // __WIN_TERM_INTERFACE_H__ diff --git a/game_core/plantform/win_term/win_term.c b/game_core/plantform/win_term/win_term.c new file mode 100644 index 0000000..d0f0300 --- /dev/null +++ b/game_core/plantform/win_term/win_term.c @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include "win_term.h" + +// 终端抽象结构体 +struct win_term { + HANDLE hStdout; + HANDLE hStdin; + CONSOLE_SCREEN_BUFFER_INFO csbi; +}; + +// 初始化终端 +win_term_t* terminal_init() { + win_term_t* term = malloc(sizeof(win_term_t)); + term->hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + term->hStdin = GetStdHandle(STD_INPUT_HANDLE); + + SetConsoleOutputCP(65001); + SetConsoleCP(65001); + + // 保存初始控制台信息 + GetConsoleScreenBufferInfo(term->hStdout, &term->csbi); + + // 设置输入模式(禁用行缓冲) + DWORD mode = 0; + GetConsoleMode(term->hStdin, &mode); + SetConsoleMode(term->hStdin, mode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)); + + // 关闭光标(游标) + CONSOLE_CURSOR_INFO cursorInfo; + GetConsoleCursorInfo(term->hStdout, &cursorInfo); + cursorInfo.bVisible = FALSE; // 设置为 FALSE 隐藏光标 + SetConsoleCursorInfo(term->hStdout, &cursorInfo); + + system("cls"); + return term; +} + +// 清理终端 +void terminal_cleanup(win_term_t* term) { + // 恢复初始属性 + SetConsoleTextAttribute(term->hStdout, term->csbi.wAttributes); + free(term); +} + +// 设置光标位置 +static inline void terminal_set_cursor(win_term_t* term, int x, int y) { + COORD coord = { x, y }; + SetConsoleCursorPosition(term->hStdout, coord); +} + +// 打印文本(带位置和颜色) +void terminal_print(win_term_t* term, int x, int y, const char* text) { + terminal_set_cursor(term, x, y); + printf("%s", text); + fflush(stdout); +} + +// 获取按键事件 +int terminal_get_key(win_term_t* term) { + INPUT_RECORD ir; + DWORD count; + + while (1) { + ReadConsoleInput(term->hStdin, &ir, 1, &count); + + if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown) { + // 返回虚拟键码 + // return ir.Event.KeyEvent.wVirtualKeyCode; + + // 如果需要字符可改用: + return ir.Event.KeyEvent.uChar.AsciiChar; + } + } +} + +int terminal_get_width(win_term_t* term) { + GetConsoleScreenBufferInfo(term->hStdout, &term->csbi); + return term->csbi.srWindow.Right - term->csbi.srWindow.Left + 1; +} + +int terminal_get_height(win_term_t* term) { + GetConsoleScreenBufferInfo(term->hStdout, &term->csbi); + return term->csbi.srWindow.Bottom - term->csbi.srWindow.Top + 1; +} + +int terminal_clean(win_term_t* term) { + // Write the sequence for clearing the display. + DWORD written = 0; + PCWSTR sequence = L"\x1b[2J"; + if (!WriteConsoleW(term->hStdout, sequence, (DWORD)wcslen(sequence), &written, NULL)) + { + // If we fail, try to restore the mode on the way out. + SetConsoleMode(term->hStdout, ENABLE_VIRTUAL_TERMINAL_PROCESSING); + return GetLastError(); + } + return 0; +} + +// int main() { +// win_term_t* term = terminal_init(); + +// // 清屏 +// system("cls"); + +// // 打印彩色文本 +// terminal_print(term, 10, 5, +// "按方向键移动, ESC退出"); + +// int x = 10, y = 10; +// char* player = "@"; + +// while (1) { +// // 绘制玩家 +// terminal_print(term, x, y, +// player); + +// // 获取按键 +// int key = terminal_get_key(term); + +// // 擦除旧位置 +// terminal_print(term, x, y, " "); + +// // 处理移动 +// switch (key) { +// case VK_UP: y--; break; +// case VK_DOWN: y++; break; +// case VK_LEFT: x--; break; +// case VK_RIGHT: x++; break; +// case VK_ESCAPE: +// terminal_cleanup(term); +// return 0; +// } + +// // 边界检查 +// if (x < 0) x = 0; +// if (y < 0) y = 0; +// } +// } + +uint32_t win_get_timer_ms() { + static LARGE_INTEGER freq = {0}; + static BOOL has_freq = FALSE; + + if (!has_freq) { + has_freq = QueryPerformanceFrequency(&freq); + if (!has_freq) { + // 回退到低精度计时 + return (uint32_t)GetTickCount(); + } + } + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return (uint32_t)((counter.QuadPart * 1000) / freq.QuadPart); +} + +void win_sleep_ms(uint32_t ms) { + if (ms == 0) return; + + // 高精度休眠(Windows) + HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); + if (!timer) { + Sleep(ms); + return; + } + + LARGE_INTEGER ft; + ft.QuadPart = -(10000LL * ms); // 负值表示相对时间 + + if (SetWaitableTimer(timer, &ft, 0, NULL, NULL, FALSE)) { + WaitForSingleObject(timer, INFINITE); + } + + CloseHandle(timer); +} diff --git a/game_core/plantform/win_term/win_term.h b/game_core/plantform/win_term/win_term.h new file mode 100644 index 0000000..4eb57af --- /dev/null +++ b/game_core/plantform/win_term/win_term.h @@ -0,0 +1,14 @@ +#ifndef __WIN_TERM_H__ +#define __WIN_TERM_H__ + +struct win_term; +typedef struct win_term win_term_t; +win_term_t* terminal_init(); +void terminal_cleanup(win_term_t* term); +void terminal_print(win_term_t* term, int x, int y, const char* text); +int terminal_get_key(win_term_t* term); +int terminal_get_width(win_term_t* term); +int terminal_get_height(win_term_t* term); +int terminal_clean(win_term_t* term); + +#endif // __WIN_TERM_H__ diff --git a/game_engine/components/ge_entity.h b/game_engine/components/ge_entity.h new file mode 100644 index 0000000..ae70999 --- /dev/null +++ b/game_engine/components/ge_entity.h @@ -0,0 +1,20 @@ +#ifndef __GE_ENTIRY_H__ +#define __GE_ENTIRY_H__ + +#include "../ge_config.h" +#include "../math/ge_vector2i.h" + +typedef struct { + uint16_t id; + uint16_t components; +} ge_entity_t; + +typedef struct { + ge_vector2i_t position; + unsigned char z; +} ge_position_t; + + + + +#endif // __GE_ENTIRY_H__ diff --git a/game_engine/components/ge_sprite.h b/game_engine/components/ge_sprite.h new file mode 100644 index 0000000..2d531d3 --- /dev/null +++ b/game_engine/components/ge_sprite.h @@ -0,0 +1,6 @@ +#ifndef __GE_SPRITE_H__ +#define __GE_SPRITE_H__ + + + +#endif // __GE_SPRITE_H__ diff --git a/game_engine/events/ge_events_bus.h b/game_engine/events/ge_events_bus.h new file mode 100644 index 0000000..8777d45 --- /dev/null +++ b/game_engine/events/ge_events_bus.h @@ -0,0 +1,10 @@ +#ifndef __GE_EVENTS_BUS_H__ +#define __GE_EVENTS_BUS_H__ + +#include + +typedef struct { + DECLARE_GE_KFIFO(event_queue, void*, 16, ge_queue_t); +} ge_event_queue_t; + +#endif // __GE_EVENTS_BUS_H__ diff --git a/game_engine/ge_common.h b/game_engine/ge_common.h new file mode 100644 index 0000000..3acfad6 --- /dev/null +++ b/game_engine/ge_common.h @@ -0,0 +1,12 @@ +#ifndef __GE_COMMON_H__ +#define __GE_COMMON_H__ + +#include +#include + +#include +#include +#include +#include + +#endif // __GE_COMMON_H__ diff --git a/game_engine/ge_config.h b/game_engine/ge_config.h new file mode 100644 index 0000000..3c3153a --- /dev/null +++ b/game_engine/ge_config.h @@ -0,0 +1,27 @@ +#ifndef __GE_CONFIG_H__ +#define __GE_CONFIG_H__ + +#define GE_VEC2_USE_SHORT_NAMES + +#include +#include + +typedef int8_t ge_i8_t; +typedef int16_t ge_i16_t; +typedef int32_t ge_i32_t; + +typedef uint8_t ge_u8_t; +typedef uint16_t ge_u16_t; +typedef uint32_t ge_u32_t; + +typedef intptr_t ge_iptr_t; +typedef uintptr_t ge_uptr_t; +typedef int ge_int_t; +typedef unsigned int ge_uint_t; + +typedef uint32_t ge_size_t; +typedef uint32_t ge_color_t; + +typedef ge_uint_t ge_uid_t; + +#endif // __GE_CONFIG_H__ diff --git a/game_engine/ge_core.c b/game_engine/ge_core.c new file mode 100644 index 0000000..e2c3a47 --- /dev/null +++ b/game_engine/ge_core.c @@ -0,0 +1,54 @@ +#define __PYNIC_LOG_IMPLIMENT__ +#include + +void ge_engine_init(ge_core_t *core) { + // TOOD init all data + ge_u8_t* ptr = (ge_u8_t*)core; + for (int i = 0; i < (int)sizeof(ge_core_t); i++) { + *(ptr + i) = 0; + } + core->configs.fps = 60; +} + +static inline void ge_init(ge_core_t* core) { + /** + * check render + */ + ge_render_t* render_ctx = &core->render; + if (render_ctx->content) { + Assert(render_ctx->clear && render_ctx->draw + && render_ctx->getsize); + GE_SAFE_CALL(render_ctx->init, render_ctx); + render_ctx->getsize(render_ctx, &render_ctx->screen_size); + } + + Assert(core->timer.sleep_ms != NULL); + Assert(core->configs.fps != 0); + + core->timer.fps_ctl.target_fps = 0; + if (core->timer.get_ms != NULL) { + ge_fps_init(&core->timer.fps_ctl, core->configs.fps, + core->timer.get_ms, core->timer.sleep_ms); + } +} + +static inline void ge_render(ge_render_t* ctx) { + // ctx->clear(ctx); +} + +void ge_engine_run(ge_core_t *core) { + GE_SAFE_CALL(core->callbacks.init, core); + ge_init(core); + + core->state = GE_ENGINE_STATE_RUNNING; + while (core->state == GE_ENGINE_STATE_RUNNING) { + if (core->timer.fps_ctl.target_fps) ge_fps_begin_frame(&core->timer.fps_ctl); + + GE_SAFE_CALL(core->callbacks.run, core); + if (core->render.content) ge_render(&core->render); + + if (core->timer.fps_ctl.target_fps) ge_fps_end_frame(&core->timer.fps_ctl); + else core->timer.sleep_ms(1000 / core->configs.fps); + } + GE_SAFE_CALL(core->callbacks.exit, core); +} diff --git a/game_engine/ge_core.h b/game_engine/ge_core.h new file mode 100644 index 0000000..65c0a33 --- /dev/null +++ b/game_engine/ge_core.h @@ -0,0 +1,44 @@ +#ifndef __GE_CORE_H__ +#define __GE_CORE_H__ + +#include +#include +#include + +struct ge_engine_core; +typedef struct ge_engine_core ge_core_t; + +/** + * Callback Function + */ +typedef void (*ge_init_func_t)(ge_core_t* core); +typedef void (*ge_exit_func_t)(ge_core_t* core); +typedef void (*ge_run_func_t)(ge_core_t* core); + +typedef enum ge_engine_state { + GE_ENGINE_STATE_RUNNING, + GE_ENGINE_STATE_EXIT, +} ge_engine_state_t; + +struct ge_engine_core { + struct { + ge_init_func_t init; + ge_exit_func_t exit; + ge_run_func_t run; + } callbacks; + ge_engine_state_t state; + + ge_render_t render; + ge_timer_t timer; + struct { + ge_uint_t fps; + } configs; +}; + +/** + * Basic Call + */ +void ge_engine_init(ge_core_t *core); +void ge_engine_run(ge_core_t *core); + +#endif // __GE_CORE_H__ diff --git a/game_engine/physics/ge_collision_box.h b/game_engine/physics/ge_collision_box.h new file mode 100644 index 0000000..12f848c --- /dev/null +++ b/game_engine/physics/ge_collision_box.h @@ -0,0 +1,20 @@ +#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__ diff --git a/game_engine/physics/ge_collision_tilemap.h b/game_engine/physics/ge_collision_tilemap.h new file mode 100644 index 0000000..ed6fa84 --- /dev/null +++ b/game_engine/physics/ge_collision_tilemap.h @@ -0,0 +1,58 @@ + +#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; // 无碰撞 +} diff --git a/game_engine/physics/ge_collison.h b/game_engine/physics/ge_collison.h new file mode 100644 index 0000000..3ac9be5 --- /dev/null +++ b/game_engine/physics/ge_collison.h @@ -0,0 +1,27 @@ + + +// // 在 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); diff --git a/game_engine/physics/ge_physics.h b/game_engine/physics/ge_physics.h new file mode 100644 index 0000000..8a7e53d --- /dev/null +++ b/game_engine/physics/ge_physics.h @@ -0,0 +1,8 @@ +#ifndef __GE_PHYSICS_H__ +#define __GE_PHYSICS_H__ + +#include + +typedef ge_u8_t ge_phy_layers_t; + +#endif // __GE_PHYSICS_H__ diff --git a/game_engine/pynic_log/README.md b/game_engine/pynic_log/README.md new file mode 100644 index 0000000..6452e28 --- /dev/null +++ b/game_engine/pynic_log/README.md @@ -0,0 +1,212 @@ +# Pythonic_log + +纯头文件的轻量化日志库,模仿 Python `logging` 标准库的设计,支持终端彩色输出,不依赖任何标准库。 + +## 特性亮点 + +- 🚀 **纯头文件实现** - 单文件集成,无需编译 +- 🧩 **零依赖** - 不依赖标准库(可通过宏配置) +- 🌈 **终端彩色输出** - 内置 ANSI 颜色支持 +- ⚙️ **多级日志系统** - 支持 DEBUG/INFO/WARN/ERROR/FATAL/TRACE +- 🧪 **高级断言工具** - 包含 Assert, Panie, TODO 等实用宏 +- 📦 **模块化设计** - 支持多模块独立日志配置 +- 🔧 **高度可配置** - 自定义输出、格式化和退出行为 + +## 快速入门 + +### 基础使用 + +```c +#include "pylike_log.h" + +int main() { + // 基础日志输出 + LOG_INFO("Application started"); + LOG_DEBUG("Debug value: %d", 42); + LOG_WARN("This is a warning"); + LOG_ERROR("Something went wrong!"); + + // 断言工具 + int value = 10; + Assert(value > 5); // 条件断言 + AssertFmt(value != 0, "Value cannot be zero"); + + return 0; +} +``` + +### 输出示例 + +```shell +[INFO ] - root - main.c:8 | Application started +[DEBUG] - root - main.c:9 | Debug value: 42 +[WARN ] - root - main.c:10 | This is a warning +[ERROR] - root - main.c:11 | Something went wrong! +``` + +## 核心功能 + +### 日志级别控制 + +```c +// 获取日志器实例 +logger_t* logger = log_get("my_module"); + +// 设置日志级别(只显示 WARN 及以上) +log_set_level(logger, LOG_LEVEL_WARN | LOG_LEVEL_ERROR | LOG_LEVEL_FATAL); + +// 模块化日志 +MLOG_WARN(logger, "Module-specific warning: %s", "Low memory"); +``` + +### 自定义日志处理器 + +```c +void custom_handler( + log_level_t level, + const char* module, + const char* file, + int line, + const char* message +) { + // 自定义处理逻辑(如写入文件/网络) + printf("[CUSTOM] %s:%d - %s\n", file, line, message); +} + +int main() { + logger_t* logger = log_get(NULL); + log_set_handler(logger, custom_handler); + + LOG_INFO("This will use custom handler"); + return 0; +} +``` + +### 断言系统 + +```c +void critical_function(int* ptr) { + // 空指针检查 + AssertFmt(ptr != NULL, "Received NULL pointer"); + + // 范围检查 + AssertFmt(*ptr >= 0 && *ptr <= 100, "Value %d out of range", *ptr); + + // 标记未实现功能 + TODO(); // 触发致命错误 +} +``` + +## 配置选项 + +### 基本配置宏 + +| 宏名称 | 默认值 | 描述 | +| :-------------------- | :----- | :--------------- | +| `LOGGER_MAX_BUF_SIZE` | 512 | 单条日志最大长度 | +| `__LOG_NO_COLOR__` | - | 禁用彩色输出 | +| `__NO_STDIO__` | - | 禁用标准库依赖 | + + +### 标准库替代(嵌入式环境) +```c +// 在包含头文件前定义这些宏 +#define _pylike_logout_printf(fmt, ...) my_printf(fmt, ##__VA_ARGS__) +#define _pylike_snprintf my_snprintf +#define _pylike_exit my_exit + +#include "pylike_log.h" +``` + +### 颜色自定义 +编辑 `pylike_color.h` 修改 ANSI 颜色代码,或定义 `__NO_PYLIKE_COLOR__` 完全禁用颜色。 + +## 高级用法 + +### 性能追踪 + +```c +void complex_operation() { + LOG_TRACE("Entering complex_operation"); + + // ... 复杂计算 ... + + LOG_TRACE("Exiting complex_operation"); +} +``` + +### 致命错误处理 + +```c +void load_config() { + if (!config_valid) { + LOG_FATAL("Invalid configuration - aborting"); + // 此处会自动调用 exit(-1) + } +} +``` + +### 模块化日志系统 + +```c +// 创建模块专用日志器 +logger_t network_logger; +init_logger(&network_logger, "NETWORK"); + +// 设置模块日志级别 +log_set_level(&network_logger, LOG_LEVEL_DEBUG | LOG_LEVEL_ERROR); + +// 使用模块日志 +MLOG_DEBUG(&network_logger, "Connecting to %s", "192.168.1.1"); +``` + +## API 参考 + +### 日志级别 + +```c +typedef enum log_level { + LOG_LEVEL_NOTSET = 0, // 未设置级别(继承默认配置) + LOG_LEVEL_DEBUG = 1 << 0, // 调试信息(开发阶段详细信息) + LOG_LEVEL_INFO = 1 << 1, // 常规信息(系统运行状态) + LOG_LEVEL_WARN = 1 << 2, // 警告信息(潜在问题提示) + LOG_LEVEL_ERROR = 1 << 3, // 错误信息(可恢复的错误) + LOG_LEVEL_FATAL = 1 << 4, // 致命错误(导致程序终止的严重错误) + LOG_LEVEL_TRACE = 1 << 5, // 追踪(性能追踪或者栈帧追踪) + LOG_LEVEL_ALL = 0xFF, // 全级别标志(组合所有日志级别) +} log_level_t; +``` + +### 日志器结构 + +```c +typedef struct logger { + const char* name; // 日志器名称(用于模块区分) + log_level_t level; // 当前设置的日志级别 + log_handler handler; // 日志处理回调函数 + char buf[LOGGER_MAX_BUF_SIZE]; // 格式化缓冲区 +} logger_t; +``` + +### 核心函数 + +| 函数 | 描述 | +| :---------------------------------------- | :------------------- | +| `init_logger(logger_t*, const char*)` | 初始化日志器实例 | +| `log_get(const char*)` | 获取或创建日志器实例 | +| `log_set_level(logger_t*, log_level_t)` | 设置日志级别 | +| `log_set_handler(logger_t*, log_handler)` | 设置自定义日志处理器 | + +## 集成指南 + +1. 将 `pylike_log.h` 和 `pylike_color.h` 添加到项目 +2. 在需要日志的源文件中包含头文件 +3. (可选)配置自定义输出函数(嵌入式系统) +4. 使用提供的日志宏记录信息 + +## 设计理念 + +- **极简主义**:单头文件实现核心功能 +- **灵活性**:通过宏配置适应各种环境 +- **Pythonic API**:借鉴 Python logging 的优秀设计 +- **安全第一**:所有宏都使用 `do {...} while(0)` 包装 diff --git a/game_engine/pynic_log/pynic_color.h b/game_engine/pynic_log/pynic_color.h new file mode 100644 index 0000000..11c5966 --- /dev/null +++ b/game_engine/pynic_log/pynic_color.h @@ -0,0 +1,59 @@ +/** + * @file color.h + * @brief ANSI终端颜色控制码定义 + * + * 提供跨平台的终端文本颜色和样式控制支持 + */ + +#ifndef __PYTHONIC_TERMINAL_COLOR_H__ +#define __PYTHONIC_TERMINAL_COLOR_H__ + +/// @name 前景色控制码 +/// @{ +#define ANSI_FG_BLACK "\33[30m" ///< 黑色前景 +#define ANSI_FG_RED "\33[31m" ///< 红色前景 +#define ANSI_FG_GREEN "\33[32m" ///< 绿色前景 +#define ANSI_FG_YELLOW "\33[33m" ///< 黄色前景 +#define ANSI_FG_BLUE "\33[34m" ///< 蓝色前景 +#define ANSI_FG_MAGENTA "\33[35m" ///< 品红色前景 +#define ANSI_FG_CYAN "\33[36m" ///< 青色前景 +#define ANSI_FG_WHITE "\33[37m" ///< 白色前景 +/// @} + +/// @name 背景色控制码 +/// @{ +#define ANSI_BG_BLACK "\33[40m" ///< 黑色背景 +#define ANSI_BG_RED "\33[41m" ///< 红色背景 +#define ANSI_BG_GREEN "\33[42m" ///< 绿色背景 +#define ANSI_BG_YELLOW "\33[43m" ///< 黄色背景 +#define ANSI_BG_BLUE "\33[44m" ///< 蓝色背景 +#define ANSI_BG_MAGENTA "\33[45m" ///< 品红色背景(注:原始代码此处应为45m) +#define ANSI_BG_CYAN "\33[46m" ///< 青色背景 +#define ANSI_BG_WHITE "\33[47m" ///< 白色背景 +/// @} + +/// @name 文字样式控制码 +/// @{ +#define ANSI_UNDERLINED "\33[4m" ///< 下划线样式 +#define ANSI_BOLD "\33[1m" ///< 粗体样式 +#define ANSI_NONE "\33[0m" ///< 重置所有样式 +/// @} + +/** + * @def ANSI_FMT + * @brief 安全文本格式化宏 + * @param str 目标字符串 + * @param fmt ANSI格式序列(可组合多个样式) + * + * @note 当定义ANSI_FMT_DISABLE时自动禁用颜色输出 + * @code + * printf(ANSI_FMT("Warning!", ANSI_FG_YELLOW ANSI_BOLD)); + * @endcode + */ +#ifndef ANSI_FMT_DISABLE +#define ANSI_FMT(str, fmt) fmt str ANSI_NONE ///< 启用样式包裹 +#else +#define ANSI_FMT(str, fmt) str ///< 禁用样式输出 +#endif + +#endif // __PYTHONIC_TERMINAL_COLOR_H__ diff --git a/game_engine/pynic_log/pynic_log.h b/game_engine/pynic_log/pynic_log.h new file mode 100644 index 0000000..7d4b23d --- /dev/null +++ b/game_engine/pynic_log/pynic_log.h @@ -0,0 +1,288 @@ +/** + * @file pythonic_log.h + * @brief 模仿python标准库logger的日志系统核心模块(支持多级日志、断言和异常处理) + */ + +#ifndef __PYTHONIC_LOG_H__ +#define __PYTHONIC_LOG_H__ + +#ifndef __NO_STDIO__ +#include +#include + +#ifndef _pynic_logout_printf +#define _pynic_logout_printf(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) +#endif +#ifndef _pynic_snprintf +#define _pynic_snprintf snprintf +#endif +#ifndef _pynic_exit +#define _pynic_exit exit +#endif +#endif + +#ifndef __NO_PYNIC_COLOR__ +#include "pynic_color.h" +#else +#ifndef __LOG_NO_COLOR__ +#define __LOG_NO_COLOR__ +#endif +#endif + +#ifndef PYNIC_LOGGER_MAX_BUF_SIZE +#define PYNIC_LOGGER_MAX_BUF_SIZE 512 ///< 单条日志最大缓冲区尺寸 +#endif + +#ifndef NULL +#define NULL ((void *)0) +#endif + +// #define __PYNIC_LOG_IMPLIMENT__ + +#ifndef _pynic_exit +#warning _pynic_exit not defined, it will make some exit not triggered +#endif +#ifndef _pynic_logout_printf +#error _pynic_logout_printf not defined so log will not work +#endif +#ifndef _pynic_snprintf +#error _pynic_logout_printf not defined so log will not work +#endif + +#define _PYNIC_STR(str) #str +#define PYLIKE_STR(str) _PYNIC_STR(str) + +/** + * @brief 日志级别枚举 + * + * 定义日志系统的输出级别和组合标志位 + */ +typedef enum log_level { + LOG_LEVEL_NOTSET = 0, ///< 未设置级别(继承默认配置) + LOG_LEVEL_DEBUG = 1 << 0, ///< 调试信息(开发阶段详细信息) + LOG_LEVEL_INFO = 1 << 1, ///< 常规信息(系统运行状态) + LOG_LEVEL_WARN = 1 << 2, ///< 警告信息(潜在问题提示) + LOG_LEVEL_ERROR = 1 << 3, ///< 错误信息(可恢复的错误) + LOG_LEVEL_FATAL = 1 << 4, ///< 致命错误(导致程序终止的严重错误) + LOG_LEVEL_TRACE = 1 << 5, ///< 追踪(性能追踪或者栈帧追踪) + LOG_LEVEL_ALL = 0xFF, ///< 全级别标志(组合所有日志级别) +} log_level_t; + +/** + * @brief 日志处理回调函数类型 + * @param level 日志级别 + * @param module 模块名称(可为NULL) + * @param file 源文件名 + * @param line 代码行号 + * @param message 格式化后的日志消息 + * @todo 待实现模块名称,输入的模块名称,都将被忽略 + */ +typedef void (*pynic_log_handler)( + log_level_t level, + const char* module, + const char* file, + int line, + const char* message +); + +/** + * @brief 日志器实例结构体 + * + * 每个日志器实例维护独立的配置和缓冲区 + */ +typedef struct logger { + const char* name; ///< 日志器名称(用于模块区分) + log_level_t level; ///< 当前设置的日志级别 + pynic_log_handler handler; ///< 日志处理回调函数 + char buf[PYNIC_LOGGER_MAX_BUF_SIZE]; ///< 格式化缓冲区 +} logger_t; + +/** + * @brief 初始化日志实例 其余参数设置为默认值 + * @param[in] logger 日志器实例指针 + * @param[in] name 日志器名称(NULL表示获取默认日志器名称) + */ +void init_logger(logger_t* logger, const char* name); + +/** + * @brief 初始化日志实例 + * @param[in] logger 日志器实例指针 + * @param[in] name 日志器名称(NULL表示获取默认日志器名称) + * @param[in] handler 日志处理回调函数 + */ +void init_logger_ex(logger_t* logger, const char* name, pynic_log_handler hander); + +// TODO log_set(); 暂未实现 日志注册 + +/** + * @brief 获取或创建日志器实例 + * @param[in] name 日志器名称(NULL表示获取默认日志器) + * @return 日志器实例指针 + * @warning 若没有找到相应日志器则会返回根日志器 + */ +logger_t* log_get(const char* name); + +/** + * @brief 设置日志级别 + * @param[in] logger 目标日志器实例 + * @param[in] level 要设置的日志级别(可组合多个级别) + */ +void log_set_level(logger_t* logger, log_level_t level); + +/** + * @brief 设置自定义日志处理器 + * @param[in] logger 目标日志器实例 + * @param[in] handler 自定义处理函数(NULL恢复默认处理) + */ +void log_set_handler(logger_t* logger, pynic_log_handler handler); + +#ifndef LOG_MAX_MAROC_BUF_SIZE +#define LOG_MAX_MAROC_BUF_SIZE LOGGER_MAX_BUF_SIZE ///< 宏展开缓冲区尺寸 +#endif + +/** + * @def _LOG + * @brief 内部日志宏(供其他日志宏调用) + * @param _module_ 模块实例(NULL表示使用默认日志器) + * @param _level_ 日志级别 + * @param _msg_ 格式字符串 + * @param ... 可变参数列表 + */ +#define _LOG(_module_, _level_, _msg_, ...) \ + do { \ + logger_t* _logger; \ + if (_module_ == NULL) _logger = log_get(NULL); \ + else _logger = _module_; \ + if (_logger && _logger->handler && (_logger->level & (_level_))) { \ + _pynic_snprintf(_logger->buf, sizeof(_logger->buf), (_msg_), ##__VA_ARGS__); \ + _logger->handler((_level_), _logger->name, __FILE__, __LINE__, _logger->buf); \ + } \ + } while(0) + +/// @name 模块日志宏 +/// @{ +#define MLOG_NOTSET(module, ...) _LOG(module, LOG_LEVEL_NOTSET, __VA_ARGS__) ///< 未分类日志 +#define MLOG_DEBUG( module, ...) _LOG(module, LOG_LEVEL_DEBUG, __VA_ARGS__) ///< 调试日志(需启用DEBUG级别) +#define MLOG_INFO( module, ...) _LOG(module, LOG_LEVEL_INFO, __VA_ARGS__) ///< 信息日志(常规运行日志) +#define MLOG_WARN( module, ...) _LOG(module, LOG_LEVEL_WARN, __VA_ARGS__) ///< 警告日志(潜在问题) +#define MLOG_ERROR( module, ...) _LOG(module, LOG_LEVEL_ERROR, __VA_ARGS__) ///< 错误日志(可恢复错误) +#define MLOG_FATAL( module, ...) _LOG(module, LOG_LEVEL_FATAL, __VA_ARGS__) ///< 致命错误日志(程序终止前) +#define MLOG_TRACE( module, ...) _LOG(module, LOG_LEVEL_TRACE, __VA_ARGS__) ///< 追踪日志(调用栈跟踪) +/// @} + +/// @name 快捷日志宏 +/// @{ +#define LOG_NOTSET(...) _LOG(NULL, LOG_LEVEL_NOTSET, __VA_ARGS__) ///< 未分类日志 +#define LOG_DEBUG(...) _LOG(NULL, LOG_LEVEL_DEBUG, __VA_ARGS__) ///< 调试日志(需启用DEBUG级别) +#define LOG_INFO(...) _LOG(NULL, LOG_LEVEL_INFO, __VA_ARGS__) ///< 信息日志(常规运行日志) +#define LOG_WARN(...) _LOG(NULL, LOG_LEVEL_WARN, __VA_ARGS__) ///< 警告日志(潜在问题) +#define LOG_ERROR(...) _LOG(NULL, LOG_LEVEL_ERROR, __VA_ARGS__) ///< 错误日志(可恢复错误) +#define LOG_FATAL(...) _LOG(NULL, LOG_LEVEL_FATAL, __VA_ARGS__) ///< 致命错误日志(程序终止前) +#define LOG_TRACE(...) _LOG(NULL, LOG_LEVEL_TRACE, __VA_ARGS__) ///< 追踪日志(调用栈跟踪) +/// @} + +/** + * @def _Assert + * @brief 断言检查内部宏 + * @param cond 检查条件表达式 + * @param ... 错误信息参数(格式字符串+参数) + */ +#define _Assert(cond, ...) \ + do { \ + if (!(cond)) { \ + LOG_FATAL(__VA_ARGS__); \ + } \ + } while (0) + +/// @name 断言工具宏 +/// @{ +#define AssertFmt(cond, format, ...) _Assert(cond, "Assertion Failure: " format, ## __VA_ARGS__) ///< 带格式的断言检查 +#define PanicFmt(format, ...) _Assert(0, "Panic: " format, ## __VA_ARGS__) ///< 立即触发致命错误 +#ifndef Assert +#define Assert(cond) AssertFmt(cond, "cond is `" PYLIKE_STR(cond) "`") ///< 基础断言检查 +#endif +#define Panic(...) PanicFmt(__VA_ARGS__) ///< 触发致命错误(带自定义消息) +#define TODO() PanicFmt("TODO please implement me") ///< 标记未实现代码(触发致命错误) +/// @} + +static inline const char* pynic_level_str(log_level_t level) { + const char* level_str; + switch (level) { + case LOG_LEVEL_DEBUG: level_str = "DEBUG"; break; + case LOG_LEVEL_INFO: level_str = "INFO "; break; + case LOG_LEVEL_WARN: level_str = "WARN "; break; + case LOG_LEVEL_ERROR: level_str = "ERROR"; break; + case LOG_LEVEL_FATAL: level_str = "FATAL"; break; + case LOG_LEVEL_TRACE: level_str = "TRACE"; break; + default: level_str = "NOTSET"; break; + } + return level_str; +} + +#ifdef __PYTHONIC_TERMINAL_COLOR_H__ +static inline const char* pynic_level_color(log_level_t level) { + const char* color_code = ANSI_NONE; + switch (level) { + case LOG_LEVEL_DEBUG: color_code = ANSI_FG_CYAN; break; + case LOG_LEVEL_INFO: color_code = ANSI_FG_GREEN; break; + case LOG_LEVEL_TRACE: color_code = ANSI_FG_BLUE; break; + case LOG_LEVEL_WARN: color_code = ANSI_FG_YELLOW; break; + case LOG_LEVEL_ERROR: color_code = ANSI_FG_RED; break; + case LOG_LEVEL_FATAL: color_code = ANSI_FG_RED ANSI_UNDERLINED; break; // 增强对比度 + default: color_code = ANSI_NONE; + } + return color_code; +} +#endif + +#ifdef __PYNIC_LOG_IMPLIMENT__ + +static void default_handler(log_level_t level, const char* module, const char* file, int line, const char* message) { + const char* level_str = pynic_level_str(level); + +/// @note: 定义 __LOG_NO_COLOR__ 会取消颜色输出 +#ifndef __LOG_NO_COLOR__ + _pynic_logout_printf(ANSI_BOLD "%s[%s] - %s - %s:%d | %s" ANSI_NONE "\n", + pynic_level_color(level), level_str, module, file, line, message); +#else + _pynic_logout_printf("[%s] %s:%d | %s: %s\n", + level_str, file, line, module, message); +#endif + if (level & LOG_LEVEL_FATAL) { + _pynic_exit(-LOG_LEVEL_FATAL); + } +} + +static logger_t __pynic_root_logger = { + .name = "root", + .level = LOG_LEVEL_ALL, + .handler = default_handler, +}; + +void init_logger(logger_t* logger, const char* name) { + init_logger_ex(logger, name, NULL); +} + +void init_logger_ex(logger_t* logger, const char* name, pynic_log_handler hander) { + logger->name = name; + logger->handler = hander ? hander : default_handler; + log_set_level(logger, LOG_LEVEL_ALL); +} + +logger_t* log_get(const char* name) { + if (name == NULL) return &__pynic_root_logger; + return &__pynic_root_logger; +} + +void log_set_level(logger_t* logger, log_level_t level) { + if (logger) logger->level = level; + else __pynic_root_logger.level = level; +} + +void log_set_handler(logger_t* logger, pynic_log_handler handler) { + if (logger) logger->handler = handler; + else __pynic_root_logger.handler = handler; +} +#endif + +#endif // __PYTHONIC_LOG_H__ diff --git a/game_engine/render/ge_render.h b/game_engine/render/ge_render.h new file mode 100644 index 0000000..cc1c93c --- /dev/null +++ b/game_engine/render/ge_render.h @@ -0,0 +1,30 @@ +#ifndef __GE_RENDER_H__ +#define __GE_RENDER_H__ + +#include + +struct ge_render_ctx; +typedef struct ge_render_ctx ge_render_t; + +typedef void (*ge_render_init_func_t)(ge_render_t* ctx); +typedef void (*ge_render_clear_func_t)(ge_render_t* ctx); +typedef void (*ge_render_draw_func_t) + (ge_render_t* ctx, ge_vector2i_t pos, const char* data); +typedef void (*ge_render_drawex_func_t) + (ge_render_t* ctx, ge_vector2i_t pos, const char* data, const void* propety); +typedef void (*ge_render_getsize_func_t) + (ge_render_t* ctx, ge_vector2i_t *size); +typedef void (*ge_render_push_event) + (ge_render_t* ctx, void* event); + +struct ge_render_ctx { + const void* content; + ge_vector2i_t screen_size; + ge_render_init_func_t init; + ge_render_clear_func_t clear; + ge_render_draw_func_t draw; + ge_render_drawex_func_t drawex; + ge_render_getsize_func_t getsize; +}; + +#endif // __GE_RENDER_H__ diff --git a/game_engine/resources/ge_resources.h b/game_engine/resources/ge_resources.h new file mode 100644 index 0000000..99cb530 --- /dev/null +++ b/game_engine/resources/ge_resources.h @@ -0,0 +1,38 @@ +#ifndef __GE_RESOURCES_H__ +#define __GE_RESOURCES_H__ + +#include + +typedef struct { +} ge_res_bmp_t; + +typedef struct { + ge_uid_t res_id; + const char* data; +} ge_drawable_t; + +/* +tank 3 * 3 (0) +"|\n" +" # \n" +"###\n" +"###\n" + +wall 3 * 3 (1) +"@@@\n" +"@@@\n" +"@@@\n" + +tilemap 3 * 3 +"1,1,1\n" +"-1,1,0\n" +"1,0,1\n" + +exece +{qiang} + +"@@@\n""@@@\n" +"@@@\n""@@@\n" +"@@@\n""@@@\n" +*/ +#endif // __GE_RESOURCES_H__ diff --git a/game_engine/timer/ge_fps.c b/game_engine/timer/ge_fps.c new file mode 100644 index 0000000..7b9c5b1 --- /dev/null +++ b/game_engine/timer/ge_fps.c @@ -0,0 +1,53 @@ +// ge_fps.c +#include "ge_fps.h" + +// 初始化FPS控制器 +void ge_fps_init(ge_fps_controller_t* fps_ctrl, ge_u32_t target_fps, + ge_fps_get_ms_func_t get_ms, ge_fps_sleep_ms_func_t sleep_ms) { + fps_ctrl->target_fps = target_fps; + fps_ctrl->frame_duration = 1000 / target_fps; + fps_ctrl->last_frame_time = get_ms(); + fps_ctrl->frame_time = 0; + fps_ctrl->sleep_time = 0; + fps_ctrl->frame_count = 0; + fps_ctrl->fps = 0; + fps_ctrl->last_fps_time = fps_ctrl->last_frame_time; + fps_ctrl->call_get_ms = get_ms; + fps_ctrl->call_sleep_ms = sleep_ms; +} + +// 帧开始 +void ge_fps_begin_frame(ge_fps_controller_t* fps_ctrl) { + fps_ctrl->last_frame_time = fps_ctrl->call_get_ms(); +} + +// 帧结束 +void ge_fps_end_frame(ge_fps_controller_t* fps_ctrl) { + // 计算当前帧耗时 + ge_u32_t current_time = fps_ctrl->call_get_ms(); + fps_ctrl->frame_time = current_time - fps_ctrl->last_frame_time; + + // 计算需要休眠的时间 + if (fps_ctrl->frame_time < fps_ctrl->frame_duration) { + fps_ctrl->sleep_time = fps_ctrl->frame_duration - fps_ctrl->frame_time; + + // 高精度休眠 + fps_ctrl->call_sleep_ms(fps_ctrl->sleep_time); + + // 更新实际休眠后时间 + current_time = fps_ctrl->call_get_ms(); + fps_ctrl->sleep_time = current_time - fps_ctrl->last_frame_time - fps_ctrl->frame_time; + } else { + fps_ctrl->sleep_time = 0; + } + + // 更新FPS计数 + fps_ctrl->frame_count++; + + // 每秒计算一次实际FPS + if (current_time - fps_ctrl->last_fps_time >= 1000) { + fps_ctrl->fps = fps_ctrl->frame_count; + fps_ctrl->frame_count = 0; + fps_ctrl->last_fps_time = current_time; + } +} diff --git a/game_engine/timer/ge_fps.h b/game_engine/timer/ge_fps.h new file mode 100644 index 0000000..d558634 --- /dev/null +++ b/game_engine/timer/ge_fps.h @@ -0,0 +1,31 @@ +#ifndef __GE_FPS_H__ +#define __GE_FPS_H__ + +#include +struct ge_fps_controller; +typedef struct ge_fps_controller ge_fps_controller_t; + +typedef ge_u32_t (*ge_fps_get_ms_func_t)(void); +typedef void (*ge_fps_sleep_ms_func_t)(ge_u32_t ms); + +void ge_fps_init(ge_fps_controller_t* fps_ctrl, ge_u32_t target_fps, + ge_fps_get_ms_func_t get_ms, ge_fps_sleep_ms_func_t sleep_ms); +void ge_fps_begin_frame(ge_fps_controller_t* fps_ctrl); +void ge_fps_end_frame(ge_fps_controller_t* fps_ctrl); + +// FPS控制器结构体 +struct ge_fps_controller { + ge_u32_t target_fps; // 目标帧率 + ge_u32_t frame_duration; // 目标每帧时长(毫秒) + ge_u32_t last_frame_time; // 上一帧开始时间 + ge_u32_t frame_time; // 当前帧耗时 + ge_u32_t sleep_time; // 需要休眠的时间 + ge_u32_t frame_count; // 帧计数器 + ge_u32_t fps; // 实际帧率 + ge_u32_t last_fps_time; // 上次计算FPS的时间 + + ge_fps_get_ms_func_t call_get_ms; + ge_fps_sleep_ms_func_t call_sleep_ms; +}; + +#endif // __GE_FPS_H__ diff --git a/game_engine/timer/ge_timer.c b/game_engine/timer/ge_timer.c new file mode 100644 index 0000000..e69de29 diff --git a/game_engine/timer/ge_timer.h b/game_engine/timer/ge_timer.h new file mode 100644 index 0000000..d5f8223 --- /dev/null +++ b/game_engine/timer/ge_timer.h @@ -0,0 +1,16 @@ +#ifndef __GE_TIMER_H__ +#define __GE_TIMER_H__ + +#include +#include "ge_fps.h" + +typedef void (*ge_sleep_ms_func_t)(ge_u32_t); +typedef ge_u32_t (*ge_get_ms_func_t)(void); + +typedef struct { + ge_sleep_ms_func_t sleep_ms; + ge_get_ms_func_t get_ms; + ge_fps_controller_t fps_ctl; +} ge_timer_t; + +#endif // __GE_TIMER_H__ diff --git a/game_engine/utils/ge_kfifo.h b/game_engine/utils/ge_kfifo.h new file mode 100644 index 0000000..980f264 --- /dev/null +++ b/game_engine/utils/ge_kfifo.h @@ -0,0 +1,236 @@ +/* + * A generic kernel FIFO implementation + * + * Copyright (C) 2009/2010 Stefani Seibold + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ +#ifndef __GE_KFIFO_H__ +#define __GE_KFIFO_H__ +/** + * 仿照linux内核中的kfifo实现 + */ + +#ifndef GE_MEMORY_BARRIR +#define GE_MEMORY_BARRIR() __sync_synchronize() +#endif + +#ifndef GE_ARRAY_SIZE +#define GE_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +struct __ge_kfifo { + unsigned int in; + unsigned int out; + unsigned int mask; + unsigned int esize; + void *data; +}; + +#define __STRUCT_GE_KFIFO(type, size) \ +{ \ + struct __ge_kfifo kfifo; \ + type buf[((size < 2) || (size & (size - 1))) ? -1 : size]; \ +} + +/** + * DECLARE_KFIFO - macro to declare a fifo object + * @fifo: name of the declared fifo + * @type: type of the fifo elements + * @size: the number of elements in the fifo, this must be a power of 2 + */ +#define DECLARE_GE_KFIFO(fifo, type, size, tname) struct tname __STRUCT_GE_KFIFO(type, size) fifo + +#define INIT_GE_KFIFO(fifo, type) do { \ + struct __ge_kfifo *__kfifo = &((fifo)->kfifo); \ + __kfifo->in = 0; \ + __kfifo->out = 0; \ + __kfifo->mask = GE_ARRAY_SIZE((fifo).buf) - 1; \ + __kfifo->esize = sizeof(*(fifo).buf); \ + __kfifo->data = (fifo).buf; \ + } while (0) + +/** + * DEFINE_KFIFO - macro to define and initialize a fifo + * @fifo: name of the declared fifo datatype + * @type: type of the fifo elements + * @size: the number of elements in the fifo, this must be a power of 2 + * + * Note: the macro can be used for global and local fifo data type variables. + */ +#define DEFINE_GE_KFIFO(fifo, type, size, tname) \ + DECLARE_GE_KFIFO(fifo, type, size, tname) = \ + (struct tname) { { \ + .in = 0, \ + .out = 0, \ + .mask = GE_ARRAY_SIZE((fifo).buf) - 1, \ + .esize = sizeof(*(fifo).buf), \ + .data = (fifo).buf, \ + }, { 0 } } + +/** + * kfifo_size - returns the size of the fifo in elements + * @fifo: address of the fifo to be used + */ +#define ge_kfifo_size(fifo) \ + ((fifo)->kfifo.mask + 1) + +/** + * kfifo_len - returns the number of used elements in the fifo + * @fifo: address of the fifo to be used + */ +#define ge_kfifo_len(fifo) \ + ((fifo)->kfifo.in - (fifo)->kfifo.out) + +/** + * kfifo_is_empty - returns true if the fifo is empty + * @fifo: address of the fifo to be used + */ +#define ge_kfifo_is_empty(fifo) \ + ((fifo)->kfifo.in == (fifo)->kfifo.out) + +/** + * kfifo_is_full - returns true if the fifo is full + * @fifo: address of the fifo to be used + */ +#define ge_kfifo_is_full(fifo) (ge_kfifo_len(fifo) > (fifo)->kfifo.mask) + +/** + * kfifo_put - put data into the fifo + * @fifo: address of the fifo to be used + * @val: the data to be added + * + * This macro copies the given value into the fifo. + * It returns 0 if the fifo was full. Otherwise it returns the number + * processed elements. + * + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ +#define ge_kfifo_put(fifo, val) do { \ + unsigned int __ret; \ + struct __ge_kfifo *__kfifo = &((fifo)->kfifo); \ + __ret = !ge_kfifo_is_full(fifo); \ + if (__ret) { \ + ((fifo)->buf)[__kfifo->in & __kfifo->mask] = val; \ + /*smp_wmb();*/ \ + GE_MEMORY_BARRIR(); \ + __kfifo->in++; \ + } \ + /*__ret;*/ \ +} while(0) + +/** + * kfifo_get - get data from the fifo + * @fifo: address of the fifo to be used + * @val: address where to store the data + * + * This macro reads the data from the fifo. + * It returns 0 if the fifo was empty. Otherwise it returns the number + * processed elements. + * + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ +#define ge_kfifo_get(fifo, val) do { \ + unsigned int __ret; \ + struct __ge_kfifo *__kfifo = &((fifo)->kfifo); \ + __ret = !ge_kfifo_is_empty(fifo); \ + if (__ret) { \ + *val = ((fifo)->buf)[__kfifo->out & __kfifo->mask]; \ + /*smp_wmb();*/ \ + GE_MEMORY_BARRIR(); \ + __kfifo->out++; \ + } \ + /*__ret;*/ \ +} while(0) + +/** + * kfifo_peek - get data from the fifo without removing + * @fifo: address of the fifo to be used + * @val: address where to store the data + * + * This reads the data from the fifo without removing it from the fifo. + * It returns 0 if the fifo was empty. Otherwise it returns the number + * processed elements. + * + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ +#define ge_kfifo_peek(fifo, val) do { \ + unsigned int __ret; \ + struct __ge_kfifo *__kfifo = &((fifo)->kfifo); \ + __ret = !kfifo_is_empty(fifo); \ + if (__ret) { \ + *val = ((fifo)->buf)[__kfifo->out & __kfifo->mask]; \ + /*smp_wmb();*/ \ + GE_MEMORY_BARRIR(); \ + } \ + /*__ret;*/ \ +} while(0) + +/** + * kfifo_in - put data into the fifo + * @fifo: address of the fifo to be used + * @buf: the data to be added + * @n: number of elements to be added + * + * This macro copies the given buffer into the fifo and returns the + * number of copied elements. + * + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ +/* +#define kfifo_in(fifo, buf, n) \ +({ \ + typeof((fifo) + 1) __tmp = (fifo); \ + typeof(__tmp->ptr_const) __buf = (buf); \ + unsigned long __n = (n); \ + const size_t __recsize = sizeof(*__tmp->rectype); \ + struct __kfifo *__kfifo = &__tmp->kfifo; \ + (__recsize) ?\ + __kfifo_in_r(__kfifo, __buf, __n, __recsize) : \ + __kfifo_in(__kfifo, __buf, __n); \ +}) +*/ +/** + * kfifo_out - get data from the fifo + * @fifo: address of the fifo to be used + * @buf: pointer to the storage buffer + * @n: max. number of elements to get + * + * This macro gets some data from the fifo and returns the numbers of elements + * copied. + * + * Note that with only one concurrent reader and one concurrent + * writer, you don't need extra locking to use these macro. + */ +/* +#define kfifo_out(fifo, buf, n) \ +({ \ + typeof((fifo) + 1) __tmp = (fifo); \ + typeof(__tmp->ptr) __buf = (buf); \ + unsigned long __n = (n); \ + const size_t __recsize = sizeof(*__tmp->rectype); \ + struct __kfifo *__kfifo = &__tmp->kfifo; \ + (__recsize) ?\ + __kfifo_out_r(__kfifo, __buf, __n, __recsize) : \ + __kfifo_out(__kfifo, __buf, __n); \ +}) +*/ + +#endif // __GE_KFIFO_H__ diff --git a/game_engine/utils/ge_maroc.h b/game_engine/utils/ge_maroc.h new file mode 100644 index 0000000..efb9907 --- /dev/null +++ b/game_engine/utils/ge_maroc.h @@ -0,0 +1,129 @@ +#ifndef __GE_MAROC_H__ +#define __GE_MAROC_H__ + +#define GE_ABS(x) ((x) > 0 : (x) : -(x)) +#define GE_MAX(a, b) ((a) > (b) ? (a) : (b)) +#define GE_MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define _GE_STR(x) #x +#define GE_STRINGIFY(x) _GE_STR(x) + +#define GE_SAFE_CALL(func, ...) \ + do { \ + if (func) func(__VA_ARGS__); \ + } while(0) + +#endif // __GE_MAROC_H__ + +/*************************************************************************************** +* Copyright (c) 2014-2022 Zihao Yu, Nanjing University +* +* NEMU is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* +* See the Mulan PSL v2 for more details. +***************************************************************************************/ +/* +#ifndef __MACRO_H__ +#define __MACRO_H__ + +#include + +// macro stringizing +#define str_temp(x) #x +#define str(x) str_temp(x) + +// strlen() for string constant +#define STRLEN(CONST_STR) (sizeof(CONST_STR) - 1) + +// calculate the length of an array +#define ARRLEN(arr) (int)(sizeof(arr) / sizeof(arr[0])) + +// macro concatenation +#define concat_temp(x, y) x ## y +#define concat(x, y) concat_temp(x, y) +#define concat3(x, y, z) concat(concat(x, y), z) +#define concat4(x, y, z, w) concat3(concat(x, y), z, w) +#define concat5(x, y, z, v, w) concat4(concat(x, y), z, v, w) + +// macro testing +// See https://stackoverflow.com/questions/26099745/test-if-preprocessor-symbol-is-defined-inside-macro +#define CHOOSE2nd(a, b, ...) b +#define MUX_WITH_COMMA(contain_comma, a, b) CHOOSE2nd(contain_comma a, b) +#define MUX_MACRO_PROPERTY(p, macro, a, b) MUX_WITH_COMMA(concat(p, macro), a, b) +// define placeholders for some property +#define __P_DEF_0 X, +#define __P_DEF_1 X, +#define __P_ONE_1 X, +#define __P_ZERO_0 X, +// define some selection functions based on the properties of BOOLEAN macro +#define MUXDEF(macro, X, Y) MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y) +#define MUXNDEF(macro, X, Y) MUX_MACRO_PROPERTY(__P_DEF_, macro, Y, X) +#define MUXONE(macro, X, Y) MUX_MACRO_PROPERTY(__P_ONE_, macro, X, Y) +#define MUXZERO(macro, X, Y) MUX_MACRO_PROPERTY(__P_ZERO_,macro, X, Y) + +// test if a boolean macro is defined +#define ISDEF(macro) MUXDEF(macro, 1, 0) +// test if a boolean macro is undefined +#define ISNDEF(macro) MUXNDEF(macro, 1, 0) +// test if a boolean macro is defined to 1 +#define ISONE(macro) MUXONE(macro, 1, 0) +// test if a boolean macro is defined to 0 +#define ISZERO(macro) MUXZERO(macro, 1, 0) +// test if a macro of ANY type is defined +// NOTE1: it ONLY works inside a function, since it calls `strcmp()` +// NOTE2: macros defined to themselves (#define A A) will get wrong results +#define isdef(macro) (strcmp("" #macro, "" str(macro)) != 0) + +// simplification for conditional compilation +#define __IGNORE(...) +#define __KEEP(...) __VA_ARGS__ +// keep the code if a boolean macro is defined +#define IFDEF(macro, ...) MUXDEF(macro, __KEEP, __IGNORE)(__VA_ARGS__) +// keep the code if a boolean macro is undefined +#define IFNDEF(macro, ...) MUXNDEF(macro, __KEEP, __IGNORE)(__VA_ARGS__) +// keep the code if a boolean macro is defined to 1 +#define IFONE(macro, ...) MUXONE(macro, __KEEP, __IGNORE)(__VA_ARGS__) +// keep the code if a boolean macro is defined to 0 +#define IFZERO(macro, ...) MUXZERO(macro, __KEEP, __IGNORE)(__VA_ARGS__) + +// functional-programming-like macro (X-macro) +// apply the function `f` to each element in the container `c` +// NOTE1: `c` should be defined as a list like: +// f(a0) f(a1) f(a2) ... +// NOTE2: each element in the container can be a tuple +#define MAP(c, f) c(f) + +#define BITMASK(bits) ((1ull << (bits)) - 1) +#define BITS(x, hi, lo) (((x) >> (lo)) & BITMASK((hi) - (lo) + 1)) // similar to x[hi:lo] in verilog +#define SEXT(x, len) ({ struct { int64_t n : len; } __x = { .n = x }; (uint64_t)__x.n; }) + +#define ROUNDUP(a, sz) ((((uintptr_t)a) + (sz) - 1) & ~((sz) - 1)) +#define ROUNDDOWN(a, sz) ((((uintptr_t)a)) & ~((sz) - 1)) + +#define PG_ALIGN __attribute((aligned(4096))) + +#if !defined(likely) +#define likely(cond) __builtin_expect(cond, 1) +#define unlikely(cond) __builtin_expect(cond, 0) +#endif + +// for AM IOE +#define io_read(reg) \ + ({ reg##_T __io_param; \ + ioe_read(reg, &__io_param); \ + __io_param; }) + +#define io_write(reg, ...) \ + ({ reg##_T __io_param = (reg##_T) { __VA_ARGS__ }; \ + ioe_write(reg, &__io_param); }) + +#endif + + */ \ No newline at end of file diff --git a/game_engine/utils/ge_static_alloc.h b/game_engine/utils/ge_static_alloc.h new file mode 100644 index 0000000..80085c9 --- /dev/null +++ b/game_engine/utils/ge_static_alloc.h @@ -0,0 +1,6 @@ +#ifndef __GE_STATIC_ALLOC_H__ +#define __GE_STATIC_ALLOC_H__ + +#include + +#endif // __GE_STATIC_ALLOC_H__ diff --git a/game_engine/utils/ge_vector2i.h b/game_engine/utils/ge_vector2i.h new file mode 100644 index 0000000..9fcc46a --- /dev/null +++ b/game_engine/utils/ge_vector2i.h @@ -0,0 +1,254 @@ +#ifndef __GE_VECTOR2_H__ +#define __GE_VECTOR2_H__ + + +/** + * @file ge_vector2i.h + * @brief 2D整数向量库 (仿Godot Vector2i设计) 不依赖任何库的单文件 + * @defgroup Vector2 2D向量操作 + */ + +// #define GE_VEC2I_USE_SHORT_NAMES + +#ifdef GE_ABS +#undef GE_ABS +#define GE_ABS(x) ((x) > 0 ? (x) : -(x)) +#endif +typedef int ge_unit_t; /**< 坐标值类型定义 */ + +/** + * @struct ge_vector2_t + * @brief 2D整数向量结构体 + * @ingroup Vector2 + */ +typedef struct { + ge_unit_t x; /**< X轴坐标 */ + ge_unit_t y; /**< Y轴坐标 */ +} ge_vector2i_t; + +// ========================= 构造函数 ========================= + +/** + * @def GE_VEC2I + * @brief 创建向量 (x, y) + * @param x_val X坐标值 + * @param y_val Y坐标值 + * @return 构造的向量 + * @ingroup Vector2 + */ +#define GE_VEC2I(x_val, y_val) ((ge_vector2i_t){(x_val), (y_val)}) + +// ========================= 常量定义 ========================= + +/** @def GE_VEC2I_ZERO 零向量 (0, 0) */ +#define GE_VEC2I_ZERO GE_VEC2I(0, 0) + +/** @def GE_VEC2I_ONE 单位向量 (1, 1) */ +#define GE_VEC2I_ONE GE_VEC2I(1, 1) + +/** @def GE_VEC2I_LEFT 左方向向量 (-1, 0) */ +#define GE_VEC2I_LEFT GE_VEC2I(-1, 0) + +/** @def GE_VEC2I_RIGHT 右方向向量 (1, 0) */ +#define GE_VEC2I_RIGHT GE_VEC2I(1, 0) + +/** @def GE_VEC2I_UP 上方向向量 (0, -1) */ +#define GE_VEC2I_UP GE_VEC2I(0, -1) + +/** @def GE_VEC2I_DOWN 下方向向量 (0, 1) */ +#define GE_VEC2I_DOWN GE_VEC2I(0, 1) + +// ========================= 运算宏定义 ========================= + +/** + * @def GE_VEC2I_ADD + * @brief 向量加法 + * @param a 第一个向量 + * @param b 第二个向量 + * @return a + b + * @ingroup Vector2 + */ +#define GE_VEC2I_ADD(a, b) GE_VEC2I((a).x + (b).x, (a).y + (b).y) + +/** + * @def GE_VEC2I_SUB + * @brief 向量减法 + * @param a 被减向量 + * @param b 减数向量 + * @return a - b + * @ingroup Vector2 + */ +#define GE_VEC2I_SUB(a, b) GE_VEC2I((a).x - (b).x, (a).y - (b).y) + +/** + * @def GE_VEC2I_MUL + * @brief 向量分量乘法 + * @param a 第一个向量 + * @param b 第二个向量 + * @return a * b (分量相乘) + * @ingroup Vector2 + */ +#define GE_VEC2I_MUL(a, b) GE_VEC2I((a).x * (b).x, (a).y * (b).y) + +/** + * @def GE_VEC2I_DIV + * @brief 向量分量除法 + * @param a 被除向量 + * @param b 除数向量 + * @return a / b (分量相除),如果分母为0会出现严重错误 + * @ingroup Vector2 + */ +#define GE_VEC2I_DIV(a, b) GE_VEC2I( \ + (a).x / (b).x, \ + (a).y / (b).y \ +) + +/** + * @def GE_VEC2I_DIV_SAFE + * @brief 向量分量除法 + * @param a 被除向量 + * @param b 除数向量 + * @return a / b (分量相除),除零保护(分母为0返回0) + * @ingroup Vector2 + */ +#define GE_VEC2I_DIV_SAFE(a, b) GE_VEC2I( \ + (b).x ? (a).x / (b).x : 0, \ + (b).y ? (a).y / (b).y : 0 \ +) + +/** + * @def GE_VEC2I_MUL_SCALAR + * @brief 向量标量乘法 + * @param v 目标向量 + * @param s 标量值 + * @return v * s + * @ingroup Vector2 + */ +#define GE_VEC2I_MUL_SCALAR(v, s) GE_VEC2I((v).x * (s), (v).y * (s)) + +/** + * @def GE_VEC2I_DOT + * @brief 向量点积 + * @param a 第一个向量 + * @param b 第二个向量 + * @return a · b + * @ingroup Vector2 + */ +#define GE_VEC2I_DOT(a, b) ((a).x * (b).x + (a).y * (b).y) + +/** + * @def GE_VEC2I_LENGTH_SQR + * @brief 向量长度平方 + * @param v 目标向量 + * @return |v|² + * @ingroup Vector2 + */ +#define GE_VEC2I_LENGTH_SQR(v) (GE_VEC2I_DOT(v, v)) + +/** + * @def GE_VEC2I_DISTANCE_SQR + * @brief 两点间距离平方 + * @param a 起点向量 + * @param b 终点向量 + * @return |a - b|² + * @ingroup Vector2 + */ +#define GE_VEC2I_DISTANCE_SQR(a, b) GE_VEC2I_LENGTH_SQR(GE_VEC2I_SUB(a, b)) + +/** + * @def GE_VEC2I_MIN + * @brief 分量最小值 + * @param a 第一个向量 + * @param b 第二个向量 + * @return (min(a.x, b.x), min(a.y, b.y)) + * @ingroup Vector2 + */ +#define GE_VEC2I_MIN(a, b) GE_VEC2I( \ + ((a).x < (b).x) ? (a).x : (b).x, \ + ((a).y < (b).y) ? (a).y : (b).y \ +) + +/** + * @def GE_VEC2I_MAX + * @brief 分量最大值 + * @param a 第一个向量 + * @param b 第二个向量 + * @return (max(a.x, b.x), max(a.y, b.y)) + * @ingroup Vector2 + */ +#define GE_VEC2I_MAX(a, b) GE_VEC2I( \ + ((a).x > (b).x) ? (a).x : (b).x, \ + ((a).y > (b).y) ? (a).y : (b).y \ +) + +/** + * @def GE_VEC2I_CLAMP + * @brief 分量钳制(钳制操作的意思是:如果某个分量小于最小值, + * 则将其设置为最小值;如果大于最大值,则设置为最大值; + * 如果在最小值和最大值之间,则保持不变。) + * @param v 目标向量 + * @param min_vec 最小值向量 + * @param max_vec 最大值向量 + * @return 钳制后的向量 + * @ingroup Vector2 + */ +#define GE_VEC2I_CLAMP(v, min_vec, max_vec) \ + GE_VEC2I_MIN(GE_VEC2I_MAX(v, min_vec), max_vec) + +// ========================= 操作符 ========================= +/** + * @def GE_VEC2I_EQUAL + * @brief 判断两个向量是否相等(分量完全相等) + * @param a 第一个向量 + * @param b 第二个向量 + * @return 1 表示相等,0 表示不相等 + * @ingroup Vector2 + */ +#define GE_VEC2I_EQUAL(a, b) (((a).x == (b).x) && ((a).y == (b).y)) + +/** + * @def GE_VEC2I_NOT_EQUAL + * @brief 判断两个向量是否不相等 + * @param a 第一个向量 + * @param b 第二个向量 + * @return 1 表示不相等,0 表示相等 + * @ingroup Vector2 + */ +#define GE_VEC2I_NOT_EQUAL(a, b) (((a).x != (b).x) || ((a).y != (b).y)) + +/** + * @def GE_VEC2I_APPROX_EQUAL + * @brief 判断两个向量是否近似相等(在容忍度范围内) + * @param a 第一个向量 + * @param b 第二个向量 + * @param tolerance 容忍度值 + * @return 1 表示近似相等,0 表示不近似相等 + * @note 对于整数向量,通常使用精确比较,但此宏提供了容忍度机制 + * @ingroup Vector2 + */ +#define GE_VEC2I_APPROX_EQUAL(a, b, tolerance) \ + ((GE_ABS((a).x - (b).x) <= tolerance) && \ + (GE_ABS((a).y - (b).y) <= tolerance)) + +// ========================= 可选前缀模式 ========================= +#ifdef GE_VEC2I_USE_SHORT_NAMES + #define vec2i(x, y) GE_VEC2I(x, y) + #define vec2i_zero GE_VEC2I_ZERO + #define vec2i_one GE_VEC2I_ONE + #define vec2i_add(a, b) GE_VEC2I_ADD(a, b) + #define vec2i_sub(a, b) GE_VEC2I_SUB(a, b) + #define vec2i_mul(a, b) GE_VEC2I_MUL(a, b) + #define vec2i_mul_scalar(v, s) GE_VEC2I_MUL_SCALAR(v, s) + #define vec2i_div(a, b) GE_VEC2I_DIV(a, b) + #define vec2i_div_safe(a, b) GE_VEC2I_DIV_SAFE(a, b) + #define vec2i_dot(a, b) GE_VEC2I_DOT(a, b) + #define vec2i_length_sqr(v) GE_VEC2I_LENGTH_SQR(v) + #define vec2i_min(a, b) GE_VEC2I_MIN(a, b) + #define vec2i_max(a, b) GE_VEC2I_MAX(a, b) + #define vec2i_clamp(v, min, max) GE_VEC2I_CLAMP(v, min, max) + #define vec2i_equal(a, b) GE_VEC2I_EQUAL(a, b) + #define vec2i_not_equal(a, b) GE_VEC2I_NOT_EQUAL(a, b) + #define vec2i_approx_equal(a, b, tol) GE_VEC2I_APPROX_EQUAL(a, b, tol) +#endif + +#endif // __GE_VECTOR2_H__