diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 62ce6bd..d18481d 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -72,6 +72,11 @@ jobs: if: runner.os == 'Windows' uses: ilammy/msvc-dev-cmd@v1 + - name: Setup Lua + uses: leafo/gh-actions-lua@v13 + with: + luaVersion: "5.4" + - name: "[Linux Only]: Install Linux dependencies" if: runner.os == 'Linux' run: | @@ -122,6 +127,7 @@ jobs: -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DCMAKE_PREFIX_PATH="${{ github.workspace }}/.lua/" -S ${{ github.workspace }} - name: Build diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a3d114..6c564c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,9 @@ cmake_minimum_required(VERSION 3.28) -project(The-Hidden-Route VERSION 0.1.0 DESCRIPTION "迷宫,但是被隐藏的路由你定义。" LANGUAGES CXX) +project(The-Hidden-Route + VERSION 0.1.0 + DESCRIPTION "迷宫,但是被隐藏的路由你定义。" + LANGUAGES CXX +) # 取消编译器扩展 set(CMAKE_CXX_EXTENSIONS OFF "Disable C++ compiler extensions") @@ -11,8 +15,14 @@ set(CMAKE_CXX_SCAN_FOR_MODULES OFF "Disable CMake C++ modules support") enable_testing() # 添加版本 -configure_file(${PROJECT_SOURCE_DIR}/include/thr/base/config.hpp.in ${PROJECT_SOURCE_DIR}/include/thr/base/config.hpp) -configure_file(${PROJECT_SOURCE_DIR}/Doxyfile.internal.in ${PROJECT_SOURCE_DIR}/Doxyfile.internal) +configure_file( + ${PROJECT_SOURCE_DIR}/include/thr/base/config.hpp.in + ${PROJECT_SOURCE_DIR}/include/thr/base/config.hpp +) +configure_file( + ${PROJECT_SOURCE_DIR}/Doxyfile.internal.in + ${PROJECT_SOURCE_DIR}/Doxyfile.internal +) configure_file(${PROJECT_SOURCE_DIR}/Doxyfile.in ${PROJECT_SOURCE_DIR}/Doxyfile) # 添加编译器标志 @@ -25,44 +35,40 @@ target_compile_features(thr_compiler_flags INTERFACE cxx_std_23) set(gcc_like_cxx $) set(msvc_cxx $) target_compile_options(thr_compiler_flags - INTERFACE - $<${gcc_like_cxx}:$> - $<${msvc_cxx}:$> + INTERFACE + $<${gcc_like_cxx}:$> + $<${msvc_cxx}:$> ) # 添加优化 target_compile_options(thr_compiler_flags - INTERFACE - $<${gcc_like_cxx}:$:-O2>>> + INTERFACE $<${gcc_like_cxx}:$:-O2>>> ) # 添加对 gcc 的 c++23 `print` / `println` 函数的 `Windows` 版本的支持 set(gcc_cxx $) if(gcc_cxx) - target_link_libraries(thr_compiler_flags - INTERFACE - $ - ) + target_link_libraries(thr_compiler_flags + INTERFACE $ + ) endif() # 添加对 MSVC 的支持 target_compile_options(thr_compiler_flags - INTERFACE - $<${msvc_cxx}:$> + INTERFACE $<${msvc_cxx}:$> ) # 包含头文件目录 target_include_directories(thr_compiler_flags - INTERFACE - $ + INTERFACE $ ) # 添加调试/发行宏 target_compile_definitions(thr_compiler_flags - INTERFACE - $:DEBUG=1>> - $:_DEBUG=1>> - $:NDEBUG=1>> + INTERFACE + $:DEBUG=1>> + $:_DEBUG=1>> + $:NDEBUG=1>> ) # 添加子目录 diff --git a/main/LICENSE.txt b/assets/fonts/LICENSE.txt similarity index 100% rename from main/LICENSE.txt rename to assets/fonts/LICENSE.txt diff --git a/main/font.otf b/assets/fonts/font.otf similarity index 100% rename from main/font.otf rename to assets/fonts/font.otf diff --git a/configs.json b/assets/json/configs.json similarity index 85% rename from configs.json rename to assets/json/configs.json index 4fac4b1..461f61d 100644 --- a/configs.json +++ b/assets/json/configs.json @@ -28,5 +28,10 @@ "r": 25, "g": 125, "b": 245 + }, + "font_path": "font.otf", + "game_screen_size": { + "x": 800, + "y": 600 } } \ No newline at end of file diff --git a/assets/json/level1.json b/assets/json/level1.json new file mode 100644 index 0000000..f5104fb --- /dev/null +++ b/assets/json/level1.json @@ -0,0 +1,227 @@ +{ + "$schema": "../../schema/level-v0.1.0-4.schema.json", + "level_script": "level1.lua", + "segments": [ + { + "start_center": { + "x": 200, + "y": 200 + }, + "length": 300, + "dir": "right", + "next": 1 + }, + { + "start_center": { + "x": 500, + "y": 200 + }, + "length": 100, + "dir": "down", + "prev": 0, + "next": 2 + }, + { + "start_center": { + "x": 500, + "y": 300 + }, + "length": 250, + "dir": "left", + "prev": 1, + "next": 3 + }, + { + "start_center": { + "x": 250, + "y": 300 + }, + "length": 80, + "dir": "up", + "prev": 2, + "tags": [ + 0 + ] + }, + { + "start_center": { + "x": 250, + "y": 180 + }, + "length": 130, + "dir": "up", + "next": 5 + }, + { + "start_center": { + "x": 250, + "y": 50 + }, + "length": 250, + "dir": "right", + "prev": 4, + "next": 6 + }, + { + "start_center": { + "x": 500, + "y": 50 + }, + "length": 100, + "dir": "down", + "prev": 5, + "next": 7 + }, + { + "start_center": { + "x": 500, + "y": 150 + }, + "length": 150, + "dir": "left", + "prev": 6, + "next": 8 + }, + { + "start_center": { + "x": 350, + "y": 150 + }, + "length": 80, + "dir": "up", + "prev": 7, + "tags": [ + 1 + ] + }, + { + "start_center": { + "x": 520, + "y": 50 + }, + "length": 80, + "dir": "right" + } + ], + "line_strips": { + "vertexs": [ + [ + { + "x": 260, + "y": 210 + }, + { + "x": 260, + "y": 290 + }, + { + "x": 490, + "y": 290 + }, + { + "x": 490, + "y": 210 + }, + { + "x": 190, + "y": 210 + }, + { + "x": 190, + "y": 190 + }, + { + "x": 510, + "y": 190 + }, + { + "x": 510, + "y": 310 + }, + { + "x": 240, + "y": 310 + }, + { + "x": 240, + "y": 210 + } + ], + [ + { + "x": 240, + "y": 190 + }, + { + "x": 240, + "y": 40 + }, + { + "x": 510, + "y": 40 + }, + { + "x": 510, + "y": 160 + }, + { + "x": 340, + "y": 160 + }, + { + "x": 340, + "y": 60 + } + ], + [ + { + "x": 260, + "y": 190 + }, + { + "x": 260, + "y": 60 + }, + { + "x": 490, + "y": 60 + }, + { + "x": 490, + "y": 140 + }, + { + "x": 360, + "y": 140 + }, + { + "x": 360, + "y": 60 + } + ], + [ + { + "x": 510, + "y": 40 + }, + { + "x": 610, + "y": 40 + }, + { + "x": 610, + "y": 60 + }, + { + "x": 510, + "y": 60 + } + ] + ], + "width": 5 + }, + "level_info": { + "start_segment_entity": 0, + "end_segment_entity": 9 + } +} \ No newline at end of file diff --git a/main/level1.json b/assets/json/level2.json similarity index 98% rename from main/level1.json rename to assets/json/level2.json index 6374875..c556f57 100644 --- a/main/level1.json +++ b/assets/json/level2.json @@ -1,5 +1,6 @@ { - "$schema": "../schema/level-v0.1.0-3.schema.json", + "$schema": "../../schema/level-v0.1.0-4.schema.json", + "level_script": "level2.lua", "segments": [ { "start_center": { @@ -37,7 +38,10 @@ }, "length": 80, "dir": "up", - "prev": 2 + "prev": 2, + "tags": [ + 0 + ] }, { "start_center": { diff --git a/main/level_graph.json b/assets/json/level_graph.json similarity index 85% rename from main/level_graph.json rename to assets/json/level_graph.json index 8569630..168abf8 100644 --- a/main/level_graph.json +++ b/assets/json/level_graph.json @@ -1,5 +1,5 @@ { - "$schema": "../schema/level_graph-v0.1.0-2.schema.json", + "$schema": "../../schema/level_graph-v0.1.0-2.schema.json", "level_nodes": [ { "relative_entities": [ diff --git a/assets/lua_scripts/level1.lua b/assets/lua_scripts/level1.lua new file mode 100644 index 0000000..3a7a5c7 --- /dev/null +++ b/assets/lua_scripts/level1.lua @@ -0,0 +1,49 @@ +---关卡加载时的脚本。 +function on_level_started() + local entities_with_tag_0 = game:query_entities_with_tag(0) + assert(#entities_with_tag_0 == 1, "错误:游戏关卡提供的带有 0 标签的实体不是仅有一个。") + + entity_with_tag_0 = entities_with_tag_0[1] + triggered_tag_0 = false + assert(entity_with_tag_0:valid(), "错误:方法 `Game.query_entities_with_tag` 内部实现有问题。") + assert(entity_with_tag_0:query_tag(0), "错误:方法 `Game.query_entities_with_tag` 内部实现有问题。") + + local entities_with_tag_1 = game:query_entities_with_tag(1) + assert(#entities_with_tag_1 == 1, "错误:游戏关卡提供的带有 1 标签的实体不是仅有一个。") + + entity_with_tag_1 = entities_with_tag_1[1] + triggered_tag_1 = false + assert(entity_with_tag_1:valid(), "错误:方法 `Game.query_entities_with_tag` 内部实现有问题。") + assert(entity_with_tag_1:query_tag(1), "错误:方法 `Game.query_entities_with_tag` 内部实现有问题。") + + Logger.info("The function `on_level_started` is done.") +end + +---玩家走到特殊线段上时的脚本。 +---@param entity Entity 特殊线段对应的实体。 +function on_special_segment_walked(entity) + if not triggered_tag_0 and entity == entity_with_tag_0 then + triggered_tag_0 = true + + if text_entity ~= nil and text_entity:valid() then + print("警告:文本实体已存在,可能是上一次触发的文本还未被销毁,或是关卡有 bug。") + text_entity:destroy() + end + + text_entity = game:add_text("“断路亦是通路,\n平面也似立体。”", Vector2.new(130, 230), 20) + + Logger.info("The tag 0 is triggered.") + end + + if not triggered_tag_1 and entity == entity_with_tag_1 then + triggered_tag_1 = true + + if text_entity ~= nil and text_entity:valid() then + text_entity:destroy() + end + + text_entity = game:add_text("“或许,应该转个弯?”", Vector2.new(400, 13), 20) + + Logger.info("The tag 1 is triggered.") + end +end diff --git a/assets/lua_scripts/level2.lua b/assets/lua_scripts/level2.lua new file mode 100644 index 0000000..170020b --- /dev/null +++ b/assets/lua_scripts/level2.lua @@ -0,0 +1,22 @@ +---关卡加载时的脚本。 +function on_level_started() + local entities = game:query_entities_with_tag(0) + assert(#entities == 1, "错误:游戏关卡提供的带有 0 标签的实体不是仅有一个。") + + entity_with_tag_1 = entities[1] + triggered_tag_0 = false + assert(entity_with_tag_1:valid()) + assert(entity_with_tag_1:query_tag(0)) + + print("message from lua in on_level_started!") +end + +---玩家走到特殊线段上时的脚本。 +---@param entity Entity 特殊线段对应的实体。 +function on_special_segment_walked(entity) + if not triggered_tag_0 and entity == entity_with_tag_1 then + triggered_tag_0 = true + text_entity = game:add_text("你好呀!", Vector2.new(200, 250)) + print("message from lua in on_special_segment_walked!") + end +end diff --git a/file_versions/file_versions.dox b/file_versions/file_versions.dox index 6593d21..3971a55 100644 --- a/file_versions/file_versions.dox +++ b/file_versions/file_versions.dox @@ -15,12 +15,16 @@ * * \subpage ecs_hpp * + * \subpage entity_wrapper_hpp + * * \subpage file_cpp * * \subpage file_hpp * * \subpage floating_point_compare_hpp * + * \subpage game_api_hpp + * * \subpage game_base_hpp * * \subpage game_state_components_hpp @@ -35,6 +39,8 @@ * * \subpage generate_file_version_cpp * + * \subpage level_components_hpp + * * \subpage level_graph_components_hpp * * \subpage level_graph_render_system_cpp @@ -53,6 +59,12 @@ * * \subpage level_serialization_system_hpp * + * \subpage lua_bindings_hpp + * + * \subpage lua_manager_cpp + * + * \subpage lua_manager_hpp + * * \subpage main_cpp * * \subpage maze_components_hpp diff --git a/file_versions/include/thr/base.hpp.md b/file_versions/include/thr/base.hpp.md index da498f1..fe44013 100644 --- a/file_versions/include/thr/base.hpp.md +++ b/file_versions/include/thr/base.hpp.md @@ -6,4 +6,4 @@ ### Added(新增) -1. base 文件夹 7 个文件的汇总。 +1. `thr/base` 文件夹的汇总。 diff --git a/file_versions/include/thr/base/file.hpp.md b/file_versions/include/thr/base/file.hpp.md index c47a643..ea6605e 100644 --- a/file_versions/include/thr/base/file.hpp.md +++ b/file_versions/include/thr/base/file.hpp.md @@ -2,6 +2,12 @@ # file.hpp 版本历史 +## \[0.1.0-2] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 添加函数 `thr::get_existing_full_path`,用于简化处理相对文件路径。 + ## \[0.1.0-1] - 2026-06-19 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/include/thr/ecs.hpp.md b/file_versions/include/thr/ecs.hpp.md index f20c13c..5992318 100644 --- a/file_versions/include/thr/ecs.hpp.md +++ b/file_versions/include/thr/ecs.hpp.md @@ -6,4 +6,4 @@ ### Added(新增) -1. 汇总了 `thr::ecs` 命名空间(包含 `thr/ecs/components.hpp` 和 `thr/ecs/systems.hpp`)。 +1. `thr/ecs` 文件夹的汇总。 diff --git a/file_versions/include/thr/ecs/components.hpp.md b/file_versions/include/thr/ecs/components.hpp.md index 9a41d83..0c82d22 100644 --- a/file_versions/include/thr/ecs/components.hpp.md +++ b/file_versions/include/thr/ecs/components.hpp.md @@ -6,4 +6,4 @@ ### Added(新增) -1. 汇总各种组件。 +1. `thr/ecs/components` 文件夹的汇总。 diff --git a/file_versions/include/thr/ecs/components/level_components.hpp.md b/file_versions/include/thr/ecs/components/level_components.hpp.md new file mode 100644 index 0000000..9d6d57c --- /dev/null +++ b/file_versions/include/thr/ecs/components/level_components.hpp.md @@ -0,0 +1,9 @@ + + +# level_components.hpp 版本历史 + +## \[0.1.0-1] - 2026-07-09 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 定义了关卡相关的组件 `thr::level_info`, `thr::level_script`, `thr::tag`。 diff --git a/file_versions/include/thr/ecs/components/player_components.hpp.md b/file_versions/include/thr/ecs/components/player_components.hpp.md index 069fc91..5cfd185 100644 --- a/file_versions/include/thr/ecs/components/player_components.hpp.md +++ b/file_versions/include/thr/ecs/components/player_components.hpp.md @@ -2,6 +2,12 @@ # player_components.hpp 版本历史 +## \[0.1.0-2] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Changed(修改) + +1. **BREKING(不向下兼容):** 将 `thr::ecs::level_info` 移至 文件[`thr/ecs/components/level_components.hpp`](../../../../../include/thr/ecs/components/level_components.hpp) + ## \[0.1.0-1] - 2026-05-23 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/include/thr/ecs/configs.hpp.md b/file_versions/include/thr/ecs/configs.hpp.md index e1da16d..006e18a 100644 --- a/file_versions/include/thr/ecs/configs.hpp.md +++ b/file_versions/include/thr/ecs/configs.hpp.md @@ -2,6 +2,12 @@ # configs.hpp 版本历史 +## \[0.1.0-2] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 添加配置 `font_path`, `game_screen_size` 和用函数获取的 `get_sfml_font()`, `get_tgui_font()`。 + ## \[0.1.0-1] - 2026-06-20 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/include/thr/ecs/lua_bindings.hpp.md b/file_versions/include/thr/ecs/lua_bindings.hpp.md new file mode 100644 index 0000000..d4b5b0a --- /dev/null +++ b/file_versions/include/thr/ecs/lua_bindings.hpp.md @@ -0,0 +1,9 @@ + + +# lua_bindings.hpp 版本历史 + +## \[0.1.0-1] - 2026-07-08 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. `thr/ecs/lua_bindings` 文件夹的汇总。 diff --git a/file_versions/include/thr/ecs/lua_bindings/entity_wrapper.hpp.md b/file_versions/include/thr/ecs/lua_bindings/entity_wrapper.hpp.md new file mode 100644 index 0000000..299735b --- /dev/null +++ b/file_versions/include/thr/ecs/lua_bindings/entity_wrapper.hpp.md @@ -0,0 +1,9 @@ + + +# entity_wrapper.hpp 版本历史 + +## \[0.1.0-1] - 2026-07-08 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 定义了实体包装器 `thr::ecs::lua_bindings::entity_wrapper`。 diff --git a/file_versions/include/thr/ecs/lua_bindings/game_api.hpp.md b/file_versions/include/thr/ecs/lua_bindings/game_api.hpp.md new file mode 100644 index 0000000..cdc5a45 --- /dev/null +++ b/file_versions/include/thr/ecs/lua_bindings/game_api.hpp.md @@ -0,0 +1,9 @@ + + +# game_api.hpp 版本历史 + +## \[0.1.0-1] - 2026-07-08 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 定义 `thr::ecs::lua_bindings::game_api` 类,集中声明为 Lua 提供的游戏 API。 diff --git a/file_versions/include/thr/ecs/lua_bindings/lua_manager.hpp.md b/file_versions/include/thr/ecs/lua_bindings/lua_manager.hpp.md new file mode 100644 index 0000000..a3be05a --- /dev/null +++ b/file_versions/include/thr/ecs/lua_bindings/lua_manager.hpp.md @@ -0,0 +1,9 @@ + + +# lua_manager.hpp 版本历史 + +## \[0.1.0-1] - 2026-07-08 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 定义了与 Lua 沟通的管理类 `thr::ecs::lua_bindings::lua_manager`。 diff --git a/file_versions/include/thr/ecs/systems.hpp.md b/file_versions/include/thr/ecs/systems.hpp.md index 6d8fdb3..4d464b5 100644 --- a/file_versions/include/thr/ecs/systems.hpp.md +++ b/file_versions/include/thr/ecs/systems.hpp.md @@ -6,4 +6,4 @@ ### Added(新增) -1. 汇总了各种系统 +1. `thr/ecs/systems` 文件夹的汇总。 diff --git a/file_versions/include/thr/ecs/systems/global/game_state_manager.hpp.md b/file_versions/include/thr/ecs/systems/global/game_state_manager.hpp.md index 0095244..9dff608 100644 --- a/file_versions/include/thr/ecs/systems/global/game_state_manager.hpp.md +++ b/file_versions/include/thr/ecs/systems/global/game_state_manager.hpp.md @@ -2,6 +2,19 @@ # game_state_manager.hpp 版本历史 +## \[0.1.0-3] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 在 `m_gui` 中默认添加一个有游戏界面大小的 UI 面板组件成员, + - 其渲染在屏幕上的大小会与 SFML 的游戏渲染界面渲染在屏幕上的大小相同。 + - **注意:** 其逻辑大小与 SFML 的游戏渲染界面的逻辑大小不同! +2. 添加静态成员 `thr::ecs::game_state_manager::game_screen_panel_name`,用于提供上述的有游戏界面大小的 UI 面板组件的名称。 +3. 在 `thr::ecs::game_state_manager::handle_event` 函数中自动处理 `sf::Event::Closed` 和 `sf::Event::Resized` 事件 + - 对于 `sf::Event::Closed`,采取直接关闭窗口。 + - 对于 `sf::Event::Resized`,采取自动调整 SFML 窗口的视图,并调整上述的有游戏界面大小的 UI 面板组件的大小和位置,使其与 SFML 窗口匹配。 +4. 为 `m_gui` 添加默认字体。 + ## \[0.1.0-2] - 2026-06-19 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/include/thr/ecs/systems/level_graph_render_system.hpp.md b/file_versions/include/thr/ecs/systems/level_graph_render_system.hpp.md index cde24fc..79d9594 100644 --- a/file_versions/include/thr/ecs/systems/level_graph_render_system.hpp.md +++ b/file_versions/include/thr/ecs/systems/level_graph_render_system.hpp.md @@ -2,6 +2,15 @@ # level_graph_render_system.hpp 版本历史 +## \[0.1.0-2] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Changed(修改) + +1. **BREKING(不向下兼容):** 将 `thr::ecs::level_graph_render_system::draw` 中的参数 `gui` 修改为两个参数: + 1. 类型为 `tgui::Container::Ptr` 的参数 `container`,用于装 UI。 + 2. 类型为 `sf::RenderTarget &` 的参数 `target`,用于绘制。 + - 迁移指南(从原来的 `gui` 参数转换): `gui->getContainer()` 对应 `container`,`gui->getTarget()` 对应 `target`。 + ## \[0.1.0-1] - 2026-07-06 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/include/thr/thr.hpp.md b/file_versions/include/thr/thr.hpp.md index 0dbabb4..4746220 100644 --- a/file_versions/include/thr/thr.hpp.md +++ b/file_versions/include/thr/thr.hpp.md @@ -6,4 +6,4 @@ ### Added(新增) -1. thr 总文件夹的汇总。 +1. `thr` 总文件夹的汇总。 diff --git a/file_versions/main/game_states.cpp.md b/file_versions/main/game_states.cpp.md index ffb9ae9..647c921 100644 --- a/file_versions/main/game_states.cpp.md +++ b/file_versions/main/game_states.cpp.md @@ -2,6 +2,20 @@ # game_states.cpp 版本历史 +## \[0.1.0-6] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Changed(修改) + +1. 将按钮改为圆角。 + +## \[0.1.0-5] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 对 Lua 脚本的支持。 +2. 对自由调整窗口大小更好的支持(按钮不偏离预设方位)。 +3. 同步一些 API 的更新。 + ## \[0.1.0-4] - 2026-07-07 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/main/game_states.hpp.md b/file_versions/main/game_states.hpp.md index 5726e75..71ceab2 100644 --- a/file_versions/main/game_states.hpp.md +++ b/file_versions/main/game_states.hpp.md @@ -2,6 +2,12 @@ # game_states.hpp 版本历史 +## \[0.1.0-5] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 配合 [main/game_states.cpp](../../main/game_states.cpp) 添加对 Lua 脚本的支持,为 `mainhelper::game_screen` 添加数据成员 `m_lua_manager`。 + ## \[0.1.0-4] - 2026-07-07 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/main/main.cpp.md b/file_versions/main/main.cpp.md index e5af606..a5faf1b 100644 --- a/file_versions/main/main.cpp.md +++ b/file_versions/main/main.cpp.md @@ -2,6 +2,12 @@ # main.cpp 版本历史 +## \[0.1.0-3] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Changed(修改) + +1. 简单修改以适应 `thr::ecs::game_state_manager`, `thr::ecs::configs` 的新 API。 + ## \[0.1.0-2] - 2026-06-19 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Changed(修改) diff --git a/file_versions/src/thr/base/file.cpp.md b/file_versions/src/thr/base/file.cpp.md index 31242cf..f03e050 100644 --- a/file_versions/src/thr/base/file.cpp.md +++ b/file_versions/src/thr/base/file.cpp.md @@ -2,6 +2,12 @@ # file.cpp 版本历史 +## \[0.1.0-2] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 添加函数 `thr::get_existing_full_path` 的实现。 + ## \[0.1.0-1] - 2026-06-19 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/src/thr/ecs/lua_bindings/lua_manager.cpp.md b/file_versions/src/thr/ecs/lua_bindings/lua_manager.cpp.md new file mode 100644 index 0000000..817e619 --- /dev/null +++ b/file_versions/src/thr/ecs/lua_bindings/lua_manager.cpp.md @@ -0,0 +1,9 @@ + + +# lua_manager.cpp 版本历史 + +## \[0.1.0-1] - 2026-07-09 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 实现了与 Lua 沟通的管理类 `thr::ecs::lua_bindings::lua_manager`。 diff --git a/file_versions/src/thr/ecs/systems/global/game_state_manager.cpp.md b/file_versions/src/thr/ecs/systems/global/game_state_manager.cpp.md index 423950b..cef32ba 100644 --- a/file_versions/src/thr/ecs/systems/global/game_state_manager.cpp.md +++ b/file_versions/src/thr/ecs/systems/global/game_state_manager.cpp.md @@ -2,6 +2,18 @@ # game_state_manager.cpp 版本历史 +## \[0.1.0-4] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Fixed(bug 修复) + +1. 修复了按钮与点击位置错位的问题。 + +## \[0.1.0-3] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 同步对 [game_state_manager.hpp](../../../../../../include/thr/ecs/systems/global/game_state_manager.hpp) 的新增。 + ## \[0.1.0-2] - 2026-06-19 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Changed(修改) diff --git a/file_versions/src/thr/ecs/systems/level_graph_render_system.cpp.md b/file_versions/src/thr/ecs/systems/level_graph_render_system.cpp.md index bfa054a..c906507 100644 --- a/file_versions/src/thr/ecs/systems/level_graph_render_system.cpp.md +++ b/file_versions/src/thr/ecs/systems/level_graph_render_system.cpp.md @@ -2,11 +2,24 @@ # level_graph_render_system.cpp 版本历史 +## \[0.1.0-4] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Changed(修改) + +1. 将按钮改为圆角。 + +## \[0.1.0-3] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Changed(修改) + +1. **BREKING(不向下兼容):** 同步对应头文件中 API 的修改。 +2. 将按钮的绝对坐标改为相对坐标。 + ## \[0.1.0-2] - 2026-07-07 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) -1. 添加关卡节点按钮的名称。 +1. 完善关卡节点按钮的显示的字。 ## \[0.1.0-1] - 2026-07-06 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) diff --git a/file_versions/src/thr/ecs/systems/level_render_system.cpp.md b/file_versions/src/thr/ecs/systems/level_render_system.cpp.md index 3cf436b..7634f22 100644 --- a/file_versions/src/thr/ecs/systems/level_render_system.cpp.md +++ b/file_versions/src/thr/ecs/systems/level_render_system.cpp.md @@ -2,6 +2,12 @@ # level_render_system.cpp 版本历史 +## \[0.1.0-3] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 添加了对文字和精灵的渲染支持。 + ## \[0.1.0-2] - 2026-06-19 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/src/thr/ecs/systems/level_serialization_system.cpp.md b/file_versions/src/thr/ecs/systems/level_serialization_system.cpp.md index b30f510..c302af8 100644 --- a/file_versions/src/thr/ecs/systems/level_serialization_system.cpp.md +++ b/file_versions/src/thr/ecs/systems/level_serialization_system.cpp.md @@ -2,6 +2,16 @@ # level_serialization_system.cpp 版本历史 +## \[0.1.0-4] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Added(新增) + +1. 同步 [`level.schema.json`](../../../../../schema/level-v0.1.0-2.schema.json) 的新增内容。 + +### Changed(修改) + +1. 重构部分代码,提取重复部分。 + ## \[0.1.0-3] - 2026-07-02 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/file_versions/tests/thr/ecs/systems/test_level_graph_render_system.cpp.md b/file_versions/tests/thr/ecs/systems/test_level_graph_render_system.cpp.md index 473525c..fd788d1 100644 --- a/file_versions/tests/thr/ecs/systems/test_level_graph_render_system.cpp.md +++ b/file_versions/tests/thr/ecs/systems/test_level_graph_render_system.cpp.md @@ -2,6 +2,12 @@ # test_level_graph_render_system.cpp 版本历史 +## \[0.1.0-3] - 2026-07-12 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) + +### Fixed(bug 修复) + +1. 修复了测试中不准确的验证按钮生成正确的方法。 + ## \[0.1.0-2] - 2026-07-07 - cpp-love(<207296385+cpp-love@users.noreply.github.com>) ### Added(新增) diff --git a/helper/CMakeLists.txt b/helper/CMakeLists.txt index 13c2ad2..f5311ab 100644 --- a/helper/CMakeLists.txt +++ b/helper/CMakeLists.txt @@ -4,22 +4,34 @@ option(THR_BUILD_HELPER "Build The Non-\"real\" World helper programs" OFF) enable_testing() if(THR_BUILD_HELPER) - # 设置输出目录 - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/helper) + # 设置输出目录 + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/helper) - # 设置链接文件 - add_executable(generate_file_version ${CMAKE_CURRENT_SOURCE_DIR}/generate_file_version.cpp) + # 设置链接文件 + add_executable(generate_file_version + ${CMAKE_CURRENT_SOURCE_DIR}/generate_file_version.cpp + ) - # 链接第三方库 - target_link_libraries(generate_file_version - PUBLIC - thr_compiler_flags - ) + # 链接第三方库 + target_link_libraries(generate_file_version + PUBLIC thr_compiler_flags + ) - # 添加安装支持 - install(TARGETS generate_file_version DESTINATION bin/helper) - install(PROGRAMS generate_code_issues.py generate_test_expressions_data.py helper_base.py move_source_file.py DESTINATION bin) + # 添加安装支持 + install(TARGETS generate_file_version DESTINATION bin/helper) + install( + PROGRAMS + generate_code_issues.py + generate_test_expressions_data.py + helper_base.py + move_source_file.py + DESTINATION bin + ) - # 添加测试支持 - add_test(NAME RunHelper COMMAND generate_file_version ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/include default_author) + # 添加测试支持 + add_test( + NAME RunHelper + COMMAND generate_file_version ${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/include default_author + ) endif() diff --git a/include/thr/base.hpp b/include/thr/base.hpp index 0ecdad0..04385aa 100644 --- a/include/thr/base.hpp +++ b/include/thr/base.hpp @@ -1,7 +1,7 @@ /** * @file base.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) - * @brief base 文件夹的汇总文件。 + * @brief `thr/base` 文件夹的汇总文件。 * @version 0.1.0-1 * @date 2026-05-01 * @@ -15,6 +15,7 @@ #include "thr/base/assert_msg.hpp" #include "thr/base/concepts.hpp" #include "thr/base/config.hpp" +#include "thr/base/file.hpp" #include "thr/base/floating_point_compare.hpp" #include "thr/base/observable.hpp" #include "thr/base/overload.hpp" diff --git a/include/thr/base/file.hpp b/include/thr/base/file.hpp index 8bd0f8d..6cd3bea 100644 --- a/include/thr/base/file.hpp +++ b/include/thr/base/file.hpp @@ -2,8 +2,8 @@ * @file file.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 声明了一些与文件相关的函数。 - * @version 0.1.0-1 - * @date 2026-06-19 + * @version 0.1.0-2 + * @date 2026-07-12 * * @copyright cpp-love * @@ -20,14 +20,26 @@ namespace thr { /** * @brief 获取当前可执行文件路径。 * @return std::optional 可执行文件路径。 + * @retval std::nullopt 获取失败。 */ - std::optional get_executable_path() noexcept; + [[nodiscard]] std::optional get_executable_path() noexcept; /** * @brief 获取当前可执行文件父文件夹路径。 * @return std::optional 可执行文件父文件夹路径。 + * @retval std::nullopt 获取失败。 */ - std::optional get_executable_directory() noexcept; + [[nodiscard]] std::optional get_executable_directory() noexcept; + + /** + * @brief 获取存在的完整路径。 + * @param [in] path 相对路径。 + * @return std::optional 存在的完整路径。 + * @retval std::nullopt 不存在这样的完整路径。 + * @details 默认相对于当前可执行文件父文件夹,若此路径不存在则尝试相对于工作目录的路径。 + */ + [[nodiscard]] std::optional + get_existing_full_path(const std::filesystem::path &path) noexcept; } // namespace thr diff --git a/include/thr/ecs.hpp b/include/thr/ecs.hpp index 0f82bcb..b8f272f 100644 --- a/include/thr/ecs.hpp +++ b/include/thr/ecs.hpp @@ -1,7 +1,7 @@ /** * @file ecs.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) - * @brief ecs 命名空间内容的汇总文件。 + * @brief `thr/ecs` 文件夹的汇总文件。 * @version 0.1.0-1 * @date 2026-05-02 * @@ -14,6 +14,7 @@ #include "thr/ecs/components.hpp" #include "thr/ecs/configs.hpp" +#include "thr/ecs/lua_bindings.hpp" #include "thr/ecs/systems.hpp" /// @brief ecs 系统相关的命名空间。 diff --git a/include/thr/ecs/components.hpp b/include/thr/ecs/components.hpp index b1d94e3..3c0c2cf 100644 --- a/include/thr/ecs/components.hpp +++ b/include/thr/ecs/components.hpp @@ -1,7 +1,7 @@ /** * @file components.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) - * @brief ecs 命名空间内容的组件部分的汇总文件。 + * @brief `thr/ecs/components` 文件夹的汇总文件。 * @version 0.1.0-1 * @date 2026-05-02 * @@ -15,6 +15,7 @@ #include "thr/ecs/components/global/game_base.hpp" #include "thr/ecs/components/global/game_state_components.hpp" #include "thr/ecs/components/global/scene_components.hpp" +#include "thr/ecs/components/level_components.hpp" #include "thr/ecs/components/level_graph_components.hpp" #include "thr/ecs/components/maze_components.hpp" #include "thr/ecs/components/player_components.hpp" diff --git a/include/thr/ecs/components/level_components.hpp b/include/thr/ecs/components/level_components.hpp new file mode 100644 index 0000000..bd79471 --- /dev/null +++ b/include/thr/ecs/components/level_components.hpp @@ -0,0 +1,40 @@ +/** + * @file level_components.hpp + * @author cpp-love (207296385+cpp-love@users.noreply.github.com) + * @brief 定义了关卡相关的组件。 + * @version 0.1.0-1 + * @date 2026-07-09 + * + * @copyright cpp-love + * + */ + +#ifndef THR_ECS_COMPONENTS_LEVEL_COMPONENTS_HPP +#define THR_ECS_COMPONENTS_LEVEL_COMPONENTS_HPP + +#include +#include +#include + +namespace thr::ecs { + + /// @brief 关卡的基本信息。 + struct level_info { + entt::entity start_segment_entity; ///< 起始段落对应的实体。 + entt::entity end_segment_entity; ///< 结束段落对应的实体。 + }; + + /// @brief 关卡对应的脚本。 + struct level_script { + std::filesystem::path script_path; ///< 关卡脚本的路径。 + std::string script_file_name; ///< 关卡脚本的文件名(相对于 `assets/lua_scripts/` 目录)。 + }; + + /// @brief 特殊标签。 + struct tag { + std::set tag_ids; ///< 标签的所有编号。 + }; + +} // namespace thr::ecs + +#endif // THR_ECS_COMPONENTS_LEVEL_COMPONENTS_HPP \ No newline at end of file diff --git a/include/thr/ecs/components/player_components.hpp b/include/thr/ecs/components/player_components.hpp index 86cd3d0..62dd3a2 100644 --- a/include/thr/ecs/components/player_components.hpp +++ b/include/thr/ecs/components/player_components.hpp @@ -2,8 +2,8 @@ * @file player_components.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 定义了玩家相关的组件。 - * @version 0.1.0-1 - * @date 2026-05-23 + * @version 0.1.0-2 + * @date 2026-07-12 * * @copyright cpp-love * @@ -128,12 +128,6 @@ namespace thr::ecs { std::vector> turnings; }; - /// @brief 关卡的基本信息。 - struct level_info { - entt::entity start_segment_entity; ///< 起始段落对应的实体。 - entt::entity end_segment_entity; ///< 结束段落对应的实体。 - }; - } // namespace thr::ecs #endif // THR_ECS_COMPONENTS_PLAYER_COMPONENTS_HPP \ No newline at end of file diff --git a/include/thr/ecs/configs.hpp b/include/thr/ecs/configs.hpp index 5c4b183..bace222 100644 --- a/include/thr/ecs/configs.hpp +++ b/include/thr/ecs/configs.hpp @@ -15,25 +15,44 @@ #include "thr/base/file.hpp" #include "thr/base/sfml_helper.hpp" #include +#include +#include #include #include #include #include #include +#include namespace thr::ecs { /// @brief 集中获取一些配置的类(采用单例模式)。 class configs { public: - float segment_width{}; ///< 路径宽度。 - sf::Color segment_color; ///< 路径颜色。 - sf::Color segments_render_opacity{}; ///< 路径渲染透明度。 - sf::Color player_on_ground_color; ///< 在地面上的玩家颜色。 - sf::Color player_under_ground_color; ///< 在地面下的玩家颜色。 - float player_on_ground_side_length{}; ///< 在地面上的玩家边长。 - float player_under_ground_side_length{}; ///< 在地面下的玩家边长。 - sf::Color background_color; ///< 背景颜色。 + float segment_width{}; ///< 路径宽度。 + sf::Color segment_color; ///< 路径颜色。 + sf::Color segments_render_opacity; ///< 路径渲染透明度。 + sf::Color player_on_ground_color; ///< 在地面上的玩家颜色。 + sf::Color player_under_ground_color; ///< 在地面下的玩家颜色。 + float player_on_ground_side_length{}; ///< 在地面上的玩家边长。 + float player_under_ground_side_length{}; ///< 在地面下的玩家边长。 + sf::Color background_color; ///< 背景颜色。 + std::filesystem::path font_path; ///< 字体路径。 + sf::Vector2u game_screen_size; ///< 游戏屏幕大小。 + /** + * @brief 获取 SFML 的字体。 + * @return const sf::Font& SFML 的字体。 + */ + [[nodiscard]] const sf::Font &get_sfml_font() const { + static sf::Font font{font_path}; + return font; + }; + /** + * @brief 获取 TGUI 的字体。 + * @return tgui::Font TGUI 的字体。 + * @todo 不要每次都构造,但是像 `get_sfml_font()` 的方法无法正确处理 TGUI 的生命周期管理。 + */ + [[nodiscard]] tgui::Font get_tgui_font() const { return {font_path.generic_string()}; } /// @brief 删除复制构造函数。 configs(const configs &rhs) = delete; @@ -46,38 +65,22 @@ namespace thr::ecs { static const configs &singleton() { static const configs singleton = [] { - configs configs; nlohmann::json json; std::ifstream fin( - get_executable_directory() - .and_then( - [](std::filesystem::path path) -> std::optional { - // 先尝试可执行文件父文件夹目录下的配置文件。 - path /= "configs.json"; - if (std::filesystem::exists(path)) { - return path; - } - return std::nullopt; - }) - .or_else([] -> std::optional { - // 再尝试工作目录下的配置文件。 - std::filesystem::path path = - std::filesystem::current_path() / "configs.json"; - if (std::filesystem::exists(path)) { - return path; - } - return std::nullopt; - }) - .value() /*实在不行就抛异常爆炸*/); + get_existing_full_path("assets/json/configs.json").value() /*实在不行就抛异常爆炸*/); fin >> json; - configs.segment_width = json["segment_width"]; - configs.segment_color = json["segment_color"]; - configs.segments_render_opacity = json["segments_render_opacity"]; - configs.player_on_ground_color = json["player_on_ground_color"]; - configs.player_under_ground_color = json["player_under_ground_color"]; - configs.player_on_ground_side_length = json["player_on_ground_side_length"]; - configs.player_under_ground_side_length = json["player_under_ground_side_length"]; - configs.background_color = json["background_color"]; + configs configs{ + get_existing_full_path(std::filesystem::path("assets/fonts") / json.at("font_path")) + .value()}; + configs.segment_width = json.at("segment_width"); + configs.segment_color = json.at("segment_color"); + configs.segments_render_opacity = json.at("segments_render_opacity"); + configs.player_on_ground_color = json.at("player_on_ground_color"); + configs.player_under_ground_color = json.at("player_under_ground_color"); + configs.player_on_ground_side_length = json.at("player_on_ground_side_length"); + configs.player_under_ground_side_length = json.at("player_under_ground_side_length"); + configs.background_color = json.at("background_color"); + configs.game_screen_size = json.at("game_screen_size"); return configs; }(); return singleton; @@ -86,13 +89,15 @@ namespace thr::ecs { private: /** * @brief 构建一个 configs 对象。 - * @details 为遵循单例模式,将此函数设为私有,以防意外创建对象。 + * @param [in] font_path 字体路径。 + * @details 为遵循单例模式,将此函数设为私有,以防意外创建多余对象。 */ - constexpr configs() = default; + explicit configs(std::filesystem::path the_font_path) : font_path(std::move(the_font_path)) {} + /** * @brief 构建一个 configs 对象。 * @param [in] rhs 另一个对象。 - * @details 为遵循单例模式,将此函数设为私有,以防意外创建对象。 + * @details 为遵循单例模式,将此函数设为私有,以防意外创建多余对象。 */ configs(configs &&rhs) = default; }; diff --git a/include/thr/ecs/lua_bindings.hpp b/include/thr/ecs/lua_bindings.hpp new file mode 100644 index 0000000..f471e9b --- /dev/null +++ b/include/thr/ecs/lua_bindings.hpp @@ -0,0 +1,22 @@ +/** + * @file lua_bindings.hpp + * @author cpp-love (207296385+cpp-love@users.noreply.github.com) + * @brief `thr/ecs/lua_bindings` 文件夹的汇总文件。 + * @version 0.1.0-1 + * @date 2026-07-08 + * + * @copyright cpp-love + * + */ + +#ifndef THR_ECS_LUA_BINDINGS_HPP +#define THR_ECS_LUA_BINDINGS_HPP + +#include "thr/ecs/lua_bindings/entity_wrapper.hpp" +#include "thr/ecs/lua_bindings/game_api.hpp" +#include "thr/ecs/lua_bindings/lua_manager.hpp" + +/// @brief 与 Lua 沟通有关内容的命名空间。 +namespace thr::ecs::lua_bindings {} + +#endif // THR_ECS_LUA_BINDINGS_HPP \ No newline at end of file diff --git a/include/thr/ecs/lua_bindings/entity_wrapper.hpp b/include/thr/ecs/lua_bindings/entity_wrapper.hpp new file mode 100644 index 0000000..b46489c --- /dev/null +++ b/include/thr/ecs/lua_bindings/entity_wrapper.hpp @@ -0,0 +1,123 @@ +/** + * @file entity_wrapper.hpp + * @author cpp-love (207296385+cpp-love@users.noreply.github.com) + * @brief 定义了实体包装器。 + * @version 0.1.0-1 + * @date 2026-07-08 + * + * @copyright cpp-love + * + */ + +#ifndef THR_ECS_LUA_BINDINGS_ENTITY_WRAPPER_HPP +#define THR_ECS_LUA_BINDINGS_ENTITY_WRAPPER_HPP + +#include "thr/ecs/components/level_components.hpp" +#include +#include +#include +#include + +namespace thr::ecs::lua_bindings { + + /// @brief 给 Lua 的实体包装器。 + class entity_wrapper { + private: + entt::entity m_entity; ///< 当前实体。 + std::reference_wrapper m_registry; ///< 当前实体所属的注册表。 + public: + /** + * @brief 构建一个实体包装器。 + * @param [in] entity 实体。 + * @param [in] registry 实体所属的注册表。 + */ + entity_wrapper(entt::entity entity, std::reference_wrapper registry) noexcept + : m_entity(entity), m_registry(registry) {} + + /** + * @brief 检测实体是否合法。 + * @return true 实体合法。 + * @return false 实体不合法。 + */ + [[nodiscard]] bool valid() const { return m_registry.get().valid(m_entity); } + + /** + * @brief 获取原始实体。 + * @return entt::entity 原始实体。 + * @warning 此函数不应暴露给 Lua! + */ + [[nodiscard]] entt::entity get_raw_entity() const noexcept { return m_entity; } + + /** + * @brief 获取注册表。 + * @return entt::entity 注册表。 + * @warning 此函数不应暴露给 Lua! + */ + [[nodiscard]] std::reference_wrapper get_registry() const noexcept { + return m_registry; + } + + /// @brief 销毁该实体。 + void destroy() { m_registry.get().destroy(m_entity); } + + /** + * @brief 为实体添加标签。 + * @param [in] tag_id 标签编号。 + * @return true 添加成功。 + * @return false 添加失败:实体不合法或标签已经添加过。 + */ + bool add_tag(int tag_id) { + if (!valid()) { + return false; + } + auto &cur_tag = m_registry.get().get_or_emplace(m_entity); + return cur_tag.tag_ids.insert(tag_id).second; + } + + /** + * @brief 为实体删除标签。 + * @param [in] tag_id 标签编号。 + * @return true 删除成功。 + * @return false 删除失败:实体不合法或标签没有添加。 + */ + bool remove_tag(int tag_id) { + if (!valid()) { + return false; + } + auto &cur_tag = m_registry.get().get_or_emplace(m_entity); + return cur_tag.tag_ids.erase(tag_id) != 0u; + } + + /** + * @brief 查询实体是否拥有标签。 + * @param [in] tag_id 标签编号。 + * @return true 拥有此标签。 + * @return false 不拥有此标签。 + */ + [[nodiscard]] bool query_tag(int tag_id) const { + if (!valid()) { + return false; + } + const auto *cur_tag = m_registry.get().try_get(m_entity); + if (cur_tag == nullptr) { + return false; + } + return cur_tag->tag_ids.contains(tag_id); + } + }; + + /** + * @brief 比较两个实体包装器是否相等。 + * @param [in] lhs 左侧实体包装器。 + * @param [in] rhs 右侧实体包装器。 + * @return true 两个实体包装器相等。 + * @return false 两个实体包装器不相等。 + */ + inline bool operator==(const entity_wrapper &lhs, const entity_wrapper &rhs) { + return lhs.get_raw_entity() == rhs.get_raw_entity() + && (&lhs.get_registry().get() == &rhs.get_registry().get()); + } + +} // namespace thr::ecs::lua_bindings + +#endif // THR_ECS_LUA_BINDINGS_ENTITY_WRAPPER_HPP \ No newline at end of file diff --git a/include/thr/ecs/lua_bindings/game_api.hpp b/include/thr/ecs/lua_bindings/game_api.hpp new file mode 100644 index 0000000..19a5a52 --- /dev/null +++ b/include/thr/ecs/lua_bindings/game_api.hpp @@ -0,0 +1,89 @@ +/** + * @file game_api.hpp + * @author cpp-love (207296385+cpp-love@users.noreply.github.com) + * @brief 定义 `thr::ecs::lua_bindings::game_api` 类,集中声明为 Lua 提供的游戏 API。 + * @version 0.1.0-1 + * @date 2026-07-08 + * + * @copyright cpp-love + * + */ + +#ifndef THR_ECS_LUA_BINDINGS_GAME_API_HPP +#define THR_ECS_LUA_BINDINGS_GAME_API_HPP + +#include "thr/ecs/components/level_components.hpp" +#include "thr/ecs/configs.hpp" +#include "thr/ecs/lua_bindings/entity_wrapper.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace thr::ecs::lua_bindings { + + /// @brief 集中声明为 Lua 提供的游戏 API 的类。 + class game_api { + private: + std::reference_wrapper m_registry; ///< 注册表。 + + public: + /** + * @brief 构建一个 game api 对象。 + * @param [in] registry 注册表。 + */ + explicit game_api(std::reference_wrapper registry) noexcept + : m_registry(registry) {} + + /** + * @brief 创建一个实体(包装器)。 + * @return entity_wrapper 新建的实体包装器。 + */ + [[nodiscard]] entity_wrapper create_entity() { return {m_registry.get().create(), m_registry}; } + + /** + * @brief 查询带标签的实体。 + * @param [in] tag_id 标签编号。 + * @return std::vector 带标签的实体列表。 + */ + [[nodiscard]] std::vector query_entities_with_tag(int tag_id) const { + auto view = m_registry.get().view(); + std::vector vector; + for (const auto &[entity, tag] : view.each()) { + if (tag.tag_ids.contains(tag_id)) { + vector.emplace_back(entity, m_registry); + } + } + return vector; + } + + /** + * @brief 创建实体并为其添加文本。 + * @param [in] text 文本内容。 + * @param [in] position 位置。 + * @param [in] character_size 字号。 + * @return entity_wrapper 新建的实体(包装器)。 + */ + entity_wrapper add_text(std::string_view text, sf::Vector2f position, + unsigned int character_size = 30) { + entt::entity entity = m_registry.get().create(); + + sf::Text sf_text{configs::singleton().get_sfml_font(), + sf::String::fromUtf8(text.begin(), text.end()), character_size}; + sf_text.setPosition(position); + sf_text.setLineAlignment(sf::Text::LineAlignment::Center); + m_registry.get().emplace(entity, sf_text); + + return {entity, m_registry}; + } + }; + +} // namespace thr::ecs::lua_bindings + +#endif // THR_ECS_LUA_BINDINGS_GAME_API_HPP \ No newline at end of file diff --git a/include/thr/ecs/lua_bindings/lua_manager.hpp b/include/thr/ecs/lua_bindings/lua_manager.hpp new file mode 100644 index 0000000..829e83f --- /dev/null +++ b/include/thr/ecs/lua_bindings/lua_manager.hpp @@ -0,0 +1,90 @@ +/** + * @file lua_manager.hpp + * @author cpp-love (207296385+cpp-love@users.noreply.github.com) + * @brief 定义了与 Lua 沟通的管理类。 + * @version 0.1.0-1 + * @date 2026-07-08 + * + * @copyright cpp-love + * + */ + +#ifndef THR_ECS_LUA_BINDINGS_LUA_MANAGER_HPP +#define THR_ECS_LUA_BINDINGS_LUA_MANAGER_HPP + +#include "thr/ecs/systems/level_serialization_system.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace thr::ecs::lua_bindings { + + /// @brief 与 Lua 沟通的管理类。 + class lua_manager { + private: + sol::state m_lua; ///< Lua 状态机。 + std::reference_wrapper m_registry; ///< 注册表。 + + public: + /** + * @brief 构建一个 lua manager 对象。 + * @param [in] registry 注册表。 + */ + explicit lua_manager(std::reference_wrapper registry) : m_registry(registry) { + init(); + } + + /** + * @brief 从文件加载并执行 Lua 脚本。 + * @param [in] path Lua 脚本路径。 + * @return std::optional 可能的错误。 + */ + std::optional load_and_run_script_file(const std::filesystem::path &path) { + auto result = m_lua.script_file(path.generic_string(), &sol::script_pass_on_error); + if (!result.valid()) { + sol::error err = result; + return err.what(); + } + return std::nullopt; + } + + /** + * @brief 调用 Lua 函数。 + * @tparam Args 参数类型。 + * @param [in] function_name 函数名。 + * @param [in] args 参数。 + * @return std::optional 可能的错误。 + */ + template + std::optional call_function(std::string_view function_name, Args &&...args) { + sol::protected_function function = m_lua[function_name]; + if (function.valid()) { + sol::protected_function_result result = function(std::forward(args)...); + if (!result.valid()) { + sol::error err = result; + return err.what(); + } + } + return std::nullopt; + } + + private: + /// @brief 统一的初始化函数。 + void init(); + }; + +} // namespace thr::ecs::lua_bindings + +#endif // THR_ECS_LUA_BINDINGS_LUA_MANAGER_HPP \ No newline at end of file diff --git a/include/thr/ecs/systems.hpp b/include/thr/ecs/systems.hpp index cf69b42..11a60f7 100644 --- a/include/thr/ecs/systems.hpp +++ b/include/thr/ecs/systems.hpp @@ -1,7 +1,7 @@ /** * @file systems.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) - * @brief ecs 命名空间内容的系统部分的汇总文件。 + * @brief `thr/ecs/systems` 文件夹的汇总文件。 * @version 0.1.0-1 * @date 2026-05-02 * diff --git a/include/thr/ecs/systems/global/game_state_manager.hpp b/include/thr/ecs/systems/global/game_state_manager.hpp index d14d1e9..9c1a92d 100644 --- a/include/thr/ecs/systems/global/game_state_manager.hpp +++ b/include/thr/ecs/systems/global/game_state_manager.hpp @@ -2,8 +2,8 @@ * @file game_state_manager.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 声明了游戏状态管理系统。 - * @version 0.1.0-2 - * @date 2026-06-19 + * @version 0.1.0-3 + * @date 2026-07-12 * * @copyright cpp-love * @@ -38,6 +38,8 @@ namespace thr::ecs { tgui::Gui m_gui; ///< GUI 对象。 public: + static const tgui::String game_screen_panel_name; ///< 有游戏界面大小的 UI 面板组件名称。 + /** * @brief 构建一个 game state manager 对象。 * @param [in] window 渲染窗口。 diff --git a/include/thr/ecs/systems/level_graph_render_system.hpp b/include/thr/ecs/systems/level_graph_render_system.hpp index 5e85803..84e9785 100644 --- a/include/thr/ecs/systems/level_graph_render_system.hpp +++ b/include/thr/ecs/systems/level_graph_render_system.hpp @@ -2,8 +2,8 @@ * @file level_graph_render_system.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 声明了渲染关卡图的系统。 - * @version 0.1.0-1 - * @date 2026-07-06 + * @version 0.1.0-2 + * @date 2026-07-12 * * @copyright cpp-love * @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -26,17 +27,20 @@ namespace thr::ecs { /// @brief 渲染关卡图的系统。 class level_graph_render_system { public: - static constexpr std::string_view widget_prefix{"level_graph_node_"}; ///< 添加的 TGUI 按钮名称。 + static constexpr std::string_view widget_prefix{ + "level_graph_node_"}; ///< 添加的 TGUI 按钮名称前缀。 using node_callback = std::function; /** * @brief 渲染关卡图并生成对应的 GUI 按钮。 * @param [in] registry 注册表。 - * @param [in] gui GUI 对象。 + * @param [in] container 装 UI 的容器。 + * @param [in] target 渲染对象。 * @param [in] entity 从此实体开始遍历。 * @param [in] on_click 点击节点时触发的回调。 */ - static void draw(const entt::registry ®istry, tgui::Gui &gui, entt::entity entity, + static void draw(const entt::registry ®istry, tgui::Container::Ptr container, + sf::RenderTarget &target, entt::entity entity, node_callback on_click = {}) noexcept; }; diff --git a/include/thr/thr.hpp b/include/thr/thr.hpp index f1cceea..fe52e70 100644 --- a/include/thr/thr.hpp +++ b/include/thr/thr.hpp @@ -1,7 +1,7 @@ /** * @file thr.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) - * @brief thr 总文件夹的汇总文件。 + * @brief `thr` 总文件夹的汇总文件。 * @version 0.1.0-1 * @date 2026-05-02 * @@ -12,7 +12,8 @@ #ifndef THR_THR_HPP #define THR_THR_HPP -#include "thr/base/fwd.hpp" +#include "thr/base.hpp" +#include "thr/ecs.hpp" /** * @brief *The Hidden Route* 游戏的命名空间。 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index e5a0774..07485c7 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -2,19 +2,21 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # 设置链接文件 -add_executable(The-Hidden-Route ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/game_states.cpp) +add_executable(The-Hidden-Route + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/game_states.cpp +) # 包含头文件目录 target_include_directories(The-Hidden-Route - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) # 链接第三方库 target_link_libraries(The-Hidden-Route - PUBLIC - thr_compiler_flags - thr + PUBLIC + thr_compiler_flags + thr ) # 添加安装支持 diff --git a/main/game_states.cpp b/main/game_states.cpp index 4878417..4348b69 100644 --- a/main/game_states.cpp +++ b/main/game_states.cpp @@ -2,8 +2,8 @@ * @file game_states.cpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 实现了一些具体的游戏状态。 - * @version 0.1.0-4 - * @date 2026-07-07 + * @version 0.1.0-6 + * @date 2026-07-12 * * @copyright cpp-love * @@ -15,9 +15,13 @@ #include "thr/base/file.hpp" #include "thr/base/floating_point_compare.hpp" #include "thr/ecs/components/global/game_base.hpp" +#include "thr/ecs/components/level_components.hpp" #include "thr/ecs/components/level_graph_components.hpp" #include "thr/ecs/components/maze_components.hpp" #include "thr/ecs/components/player_components.hpp" +#include "thr/ecs/configs.hpp" +#include "thr/ecs/lua_bindings/entity_wrapper.hpp" +#include "thr/ecs/lua_bindings/lua_manager.hpp" #include "thr/ecs/systems/global/game_state_manager.hpp" #include "thr/ecs/systems/level_graph_render_system.hpp" #include "thr/ecs/systems/level_graph_serialization_system.hpp" @@ -35,32 +39,21 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include +#include #include #include #include #include -namespace { - - const sf::Font &get_sfml_font() { - static sf::Font font("font.otf"); - return font; - }; - const tgui::Font &get_tgui_font() { - static tgui::Font font("font.otf"); - return font; - }; - -} // namespace - -// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers) namespace mainhelper { // settings_menu @@ -85,29 +78,28 @@ namespace mainhelper { } void main_menu::init() noexcept { - m_global_gui->setFont(get_tgui_font()); tgui::Button::Ptr start_button = tgui::Button::create("点击进入游戏"); - start_button->setPosition({350, 250}); - start_button->setSize({100, 100}); - start_button->setTextSize(14u); + start_button->setPosition({"(&.size - size) / 2", "(&.size - size) / 2"}); + start_button->setSize({"10%", "10%"}); start_button->onPress([&] { m_outside_dispather->enqueue( std::make_unique()); }); + start_button->getRenderer()->setRoundedBorderRadius(10.f); m_global_gui->add(start_button, "start_button"); } void main_menu::on_pause() noexcept { - tgui::Button::Ptr start_button = m_global_gui->get("start_button"); - start_button->setVisible(false); + m_global_gui->get("start_button")->setVisible(false); m_is_paused = true; } void main_menu::on_resume() noexcept { - tgui::Button::Ptr start_button = m_global_gui->get("start_button"); - start_button->setVisible(true); + m_global_gui->get("start_button")->setVisible(true); m_is_paused = false; } bool main_menu::handle_event([[maybe_unused]] const sf::Event &event) noexcept { return false; } - void main_menu::update([[maybe_unused]] thr::ecs::milliseconds_f delta_time) noexcept {} + void main_menu::update([[maybe_unused]] thr::ecs::milliseconds_f delta_time) noexcept { + spdlog::debug(sf::Vector2f(m_global_gui->getView().getSize())); + } void main_menu::draw() noexcept {} void main_menu::connect_dispatcher() noexcept {} void main_menu::disconnect_dispatcher() noexcept {} @@ -117,67 +109,58 @@ namespace mainhelper { // adapted from main_helper::game_screen::game_screen connect_dispatcher(); nlohmann::json json; - std::ifstream fin( - thr::get_executable_directory() - .and_then([](std::filesystem::path path) -> std::optional { - // 先尝试可执行文件父文件夹目录下的配置文件。 - path /= "level_graph.json"; - if (std::filesystem::exists(path)) { - return path; - } - return std::nullopt; - }) - .or_else([] -> std::optional { - // 再尝试工作目录下的配置文件。 - std::filesystem::path path = std::filesystem::current_path() / "level_graph.json"; - if (std::filesystem::exists(path)) { - return path; - } - return std::nullopt; - }) - .value() /*实在不行就抛异常爆炸*/); + std::ifstream fin(thr::get_existing_full_path("assets/json/level_graph.json") + .value() /*实在不行就抛异常爆炸*/); fin >> json; thr::ecs::level_graph_serialization_system::deserialize_from_json(m_registry, json); } level_graph_screen::~level_graph_screen() noexcept { // adapted from thr::ecs::::remove_existing_nodes // 清空按钮。 - auto widgets = m_global_gui->getWidgets(); + tgui::Panel::Ptr panel = + m_global_gui->get(thr::ecs::game_state_manager::game_screen_panel_name); + auto widgets = panel->getWidgets(); for (const auto &widget : widgets) { - if (widget->getWidgetName() == "level_graph_screen_exit_button" - || widget->getWidgetName().starts_with( + if (widget->getWidgetName().starts_with( tgui::String(thr::ecs::level_graph_render_system::widget_prefix))) { - m_global_gui->remove(widget); + panel->remove(widget); } } + m_global_gui->remove(m_global_gui->get("level_graph_screen_exit_button")); disconnect_dispatcher(); } void level_graph_screen::init() noexcept { tgui::Button::Ptr exit_button = tgui::Button::create("退出/Esc"); + exit_button->setSize({"5%", "5%"}); exit_button->onPress([&] { m_outside_dispather->enqueue(); }); + exit_button->getRenderer()->setRoundedBorderRadius(10.f); m_global_gui->add(exit_button, "level_graph_screen_exit_button"); } void level_graph_screen::on_pause() noexcept { - auto widgets = m_global_gui->getWidgets(); + auto widgets = + m_global_gui->get(thr::ecs::game_state_manager::game_screen_panel_name) + ->getWidgets(); for (const auto &widget : widgets) { - if (widget->getWidgetName() == "level_graph_screen_exit_button" - || widget->getWidgetName().starts_with( + if (widget->getWidgetName().starts_with( tgui::String(thr::ecs::level_graph_render_system::widget_prefix))) { widget->setVisible(false); } } + m_global_gui->get("level_graph_screen_exit_button")->setVisible(false); m_is_paused = true; } void level_graph_screen::on_resume() noexcept { - auto widgets = m_global_gui->getWidgets(); + auto widgets = + m_global_gui->get(thr::ecs::game_state_manager::game_screen_panel_name) + ->getWidgets(); for (const auto &widget : widgets) { - if (widget->getWidgetName() == "level_graph_screen_exit_button" - || widget->getWidgetName().starts_with( + if (widget->getWidgetName().starts_with( tgui::String(thr::ecs::level_graph_render_system::widget_prefix))) { widget->setVisible(true); } } + m_global_gui->get("level_graph_screen_exit_button")->setVisible(true); m_is_paused = false; } bool level_graph_screen::handle_event(const sf::Event &event) noexcept { @@ -195,8 +178,9 @@ namespace mainhelper { void level_graph_screen::update([[maybe_unused]] thr::ecs::milliseconds_f delta_time) noexcept {} void level_graph_screen::draw() noexcept { thr::ecs::level_graph_render_system::draw( - m_registry, *m_global_gui, m_registry.ctx().get().entity, - [&](entt::entity entity) { + m_registry, + m_global_gui->get(thr::ecs::game_state_manager::game_screen_panel_name), + *m_window, m_registry.ctx().get().entity, [&](entt::entity entity) { const auto &node = m_registry.get(entity); spdlog::info("进入关卡 {}", node.name); THR_ASSERT_MSG(!node.locked, "节点不应未解锁"); @@ -213,33 +197,30 @@ namespace mainhelper { connect_dispatcher(); nlohmann::json json; // adapted from thr::ecs::configs::singleton - std::ifstream fin( - thr::get_executable_directory() - .and_then([&](std::filesystem::path path) -> std::optional { - // 先尝试可执行文件父文件夹目录下的配置文件。 - path /= std::format("{}.json", level_name); - if (std::filesystem::exists(path)) { - return path; - } - return std::nullopt; - }) - .or_else([&] -> std::optional { - // 再尝试工作目录下的配置文件。 - std::filesystem::path path = - std::filesystem::current_path() / std::format("{}.json", level_name); - if (std::filesystem::exists(path)) { - return path; - } - return std::nullopt; - }) - .value() /*实在不行就抛异常爆炸*/); + std::ifstream fin(thr::get_existing_full_path(std::format("assets/json/{}.json", level_name)) + .value() /*实在不行就抛异常爆炸*/); fin >> json; thr::ecs::level_serialization_system::deserialize_from_json(m_registry, json); m_registry.emplace( m_player_entity, m_registry.ctx().get().start_segment_entity); m_registry.emplace(m_player_entity); + if (const auto *script = m_registry.ctx().find()) { + m_lua_manager = thr::ecs::lua_bindings::lua_manager{m_registry}; + if (std::optional error = m_lua_manager->load_and_run_script_file(script->script_path)) { + spdlog::warn("Failed to load and run script file {}: {}", + script->script_path.generic_string(), *error); + } + if (std::optional error = m_lua_manager->call_function("on_level_started")) { + spdlog::warn("Failed to call to lua function on_level_started: {}", *error); + } + } } game_screen::~game_screen() noexcept { + if (m_lua_manager.has_value()) { + if (std::optional error = m_lua_manager->call_function("on_level_ended")) { + spdlog::warn("Failed to call to lua function on_level_ended: {}", *error); + } + } tgui::Button::Ptr exit_button = m_global_gui->get("exit_button"); m_global_gui->remove(exit_button); disconnect_dispatcher(); @@ -247,7 +228,9 @@ namespace mainhelper { void game_screen::init() noexcept { tgui::Button::Ptr exit_button = tgui::Button::create("退出/Esc"); + exit_button->setSize({"5%", "5%"}); exit_button->onPress([&] { m_outside_dispather->enqueue(); }); + exit_button->getRenderer()->setRoundedBorderRadius(10.f); m_global_gui->add(exit_button, "exit_button"); } void game_screen::on_pause() noexcept { m_is_paused = true; } @@ -316,17 +299,38 @@ namespace mainhelper { updated = true; } if (updated) { + // 若走过有特殊标签的实体,触发 Lua 脚本。 + if (m_lua_manager.has_value()) { + for (const auto &[entity, on_ground] : + m_registry.view().each()) { + const auto *cur_tag = + m_registry.try_get(on_ground.segment_entity); + if (cur_tag != nullptr && !cur_tag->tag_ids.empty()) { + // 走过的实体有特殊标签。 + if (std::optional error = m_lua_manager->call_function( + "on_special_segment_walked", + thr::ecs::lua_bindings::entity_wrapper{on_ground.segment_entity, + m_registry})) { + spdlog::warn( + "Failed to call to lua function on_special_segment_walked: {}", + *error); + } + } + } + } + + // 判断是否胜利。 if (std::ranges::any_of( m_registry.view(), [&](entt::entity entity) { return m_registry.get(entity).segment_entity == m_registry.ctx().get().end_segment_entity; - })) { + })) { //< 是否走到终点段。 if (std::ranges::all_of( m_registry.view(), [&](entt::entity entity) { return thr::no_nan_inf_f{ m_registry.get(entity).walked_precent} == thr::no_nan_inf_f{1}; - })) { + })) { //< 是否所有段都走完。 // 赢了。 m_winned_time = thr::ecs::clock::now(); } @@ -344,7 +348,8 @@ namespace mainhelper { - std::chrono::duration_cast( thr::ecs::clock::now() - *m_winned_time) .count()); - sf::Text text(get_sfml_font(), sf::String::fromUtf8(string.begin(), string.end())); + sf::Text text(thr::ecs::configs::singleton().get_sfml_font(), + sf::String::fromUtf8(string.begin(), string.end())); text.setLineAlignment(sf::Text::LineAlignment::Center); text.setPosition(m_window->getView().getCenter()); sf::RectangleShape rect(text.getGlobalBounds().size); @@ -378,5 +383,4 @@ namespace mainhelper { void pause_menu::connect_dispatcher() noexcept {} void pause_menu::disconnect_dispatcher() noexcept {} -} // namespace mainhelper -// NOLINTEND(cppcoreguidelines-avoid-magic-numbers) \ No newline at end of file +} // namespace mainhelper \ No newline at end of file diff --git a/main/game_states.hpp b/main/game_states.hpp index 1ebabdb..60870da 100644 --- a/main/game_states.hpp +++ b/main/game_states.hpp @@ -2,8 +2,8 @@ * @file game_states.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 声明了一些具体的游戏状态。 - * @version 0.1.0-4 - * @date 2026-07-07 + * @version 0.1.0-5 + * @date 2026-07-12 * * @copyright cpp-love * @@ -14,6 +14,7 @@ #include "thr/ecs/components/global/game_base.hpp" #include "thr/ecs/components/global/game_state_components.hpp" +#include "thr/ecs/lua_bindings/lua_manager.hpp" #include #include #include @@ -172,13 +173,14 @@ namespace mainhelper { private: /// @brief 连接调度器。 - void connect_dispatcher() noexcept; + void connect_dispatcher() noexcept; /// @brief 断开连接调度器。 - void disconnect_dispatcher() noexcept; - bool m_is_paused = false; ///< 是否暂停。 - std::optional m_winned_time; ///< 胜利时间。 - entt::registry m_registry; ///< 注册表。 - entt::entity m_player_entity; ///< 玩家实体。 + void disconnect_dispatcher() noexcept; + bool m_is_paused = false; ///< 是否暂停。 + std::optional m_winned_time; ///< 胜利时间。 + std::optional m_lua_manager; ///< Lua 管理器。 + entt::registry m_registry; ///< 注册表。 + entt::entity m_player_entity; ///< 玩家实体。 }; /// @brief 暂停界面状态类。 diff --git a/main/main.cpp b/main/main.cpp index f0f3584..20a57dd 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2,8 +2,8 @@ * @file main.cpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief *The Hidden Route* 游戏的主程序。 - * @version 0.1.0-2 - * @date 2026-06-19 + * @version 0.1.0-3 + * @date 2026-07-12 * * @copyright cpp-love * @@ -42,20 +42,17 @@ int main() { spdlog::set_default_logger(multi_sink_logger); spdlog::flush_on(spdlog::level::info); - sf::RenderWindow window(sf::VideoMode({800, 600}), "The Hidden Route"); + sf::RenderWindow window(sf::VideoMode(thr::ecs::configs::singleton().game_screen_size), + "The Hidden Route"); thr::ecs::game_state_manager manager(window); manager.push_state(std::make_unique()); auto prev = thr::ecs::clock::now(); - while (window.isOpen()) { + while (true) { // handle event - while (const std::optional event = window.pollEvent()) { - if (!manager.handle_event(*event)) { - if (event->is()) { - spdlog::info("quit the window"); - window.close(); - } - } + while (const std::optional event = window.pollEvent()) { manager.handle_event(*event); } + if (!window.isOpen()) { + break; } // update diff --git a/schema/level-v0.1.0-3.schema.json b/schema/level-v0.1.0-4.schema.json similarity index 87% rename from schema/level-v0.1.0-3.schema.json rename to schema/level-v0.1.0-4.schema.json index da5b73d..e11c616 100644 --- a/schema/level-v0.1.0-3.schema.json +++ b/schema/level-v0.1.0-4.schema.json @@ -66,6 +66,16 @@ "left", "up" ] + }, + "tags": { + "title": "路径段的标签", + "description": "用于标记路径段的标签列表。", + "type": "array", + "items": { + "title": "一个标签", + "type": "integer" + }, + "default": [] } }, "required": [ @@ -107,6 +117,15 @@ } } }, + "level_script": { + "title": "关卡对应的脚本", + "description": "关卡脚本的文件名(相对于 `assets/lua_scripts/` 目录),若没有则为 null。", + "type": [ + "string", + "null" + ], + "default": null + }, "level_info": { "title": "关卡信息", "properties": { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3fd026..78dc4d4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,54 +11,111 @@ add_library(thr ${SRC_FILES}) include(FetchContent) FetchContent_Declare( - EnTT - GIT_REPOSITORY https://github.com/skypjack/entt.git - GIT_TAG origin/main - FIND_PACKAGE_ARGS 3.15.0 CONFIG QUIET + EnTT + GIT_REPOSITORY + https://github.com/skypjack/entt.git + GIT_TAG + origin/main + FIND_PACKAGE_ARGS + 3.15.0 + QUIET ) FetchContent_MakeAvailable(EnTT) + FetchContent_Declare( - spdlog - GIT_REPOSITORY https://github.com/gabime/spdlog.git - GIT_TAG origin/v1.x - FIND_PACKAGE_ARGS 1.15.3 CONFIG QUIET + spdlog + GIT_REPOSITORY + https://github.com/gabime/spdlog.git + GIT_TAG + origin/v1.x + FIND_PACKAGE_ARGS + 1.15.3 + QUIET ) set(SPDLOG_USE_STD_FORMAT ON) FetchContent_MakeAvailable(spdlog) + FetchContent_Declare( - SFML - GIT_REPOSITORY https://github.com/SFML/SFML.git - GIT_TAG origin/master - FIND_PACKAGE_ARGS 3.0.1 COMPONENTS Graphics Window System CONFIG QUIET + SFML + GIT_REPOSITORY + https://github.com/SFML/SFML.git + GIT_TAG + origin/master + FIND_PACKAGE_ARGS + 3.0.1 + COMPONENTS + Graphics + Window + System + QUIET ) FetchContent_MakeAvailable(SFML) + FetchContent_Declare( - TGUI - GIT_REPOSITORY https://github.com/texus/tgui.git - GIT_TAG origin/1.x - FIND_PACKAGE_ARGS 1.10.0 CONFIG QUIET + TGUI + GIT_REPOSITORY + https://github.com/texus/tgui.git + GIT_TAG + origin/1.x + FIND_PACKAGE_ARGS + 1.10.0 + QUIET ) set(TGUI_BACKEND SFML_GRAPHICS) FetchContent_MakeAvailable(TGUI) + FetchContent_Declare( - nlohmann_json - GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG origin/develop - FIND_PACKAGE_ARGS 3.12.0 CONFIG QUIET + nlohmann_json + GIT_REPOSITORY + https://github.com/nlohmann/json.git + GIT_TAG + origin/develop + FIND_PACKAGE_ARGS + 3.12.0 + QUIET ) FetchContent_MakeAvailable(nlohmann_json) +find_package(Lua 5.1 REQUIRED) +if(LUA_VERSION VERSION_GREATER_EQUAL 5.5) + message(FATAL_ERROR "Lua version ${LUA_VERSION} is too high; need < 5.5") +endif() + +FetchContent_Declare( + sol2 + GIT_REPOSITORY + https://github.com/ThePhD/sol2 + GIT_TAG + origin/develop + FIND_PACKAGE_ARGS + 3.3.0 + QUIET +) +FetchContent_MakeAvailable(sol2) + +# 为 Lua 手动创建目标 +if(Lua_FOUND AND NOT TARGET Lua::Lua) + add_library(Lua::Lua INTERFACE IMPORTED) + set_target_properties(Lua::Lua + PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${LUA_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${LUA_LIBRARIES}" + ) +endif() + # 链接编译器标志 target_link_libraries(thr - PUBLIC - thr_compiler_flags - EnTT::EnTT - spdlog::spdlog - SFML::Graphics - SFML::Window - SFML::System - TGUI::TGUI - nlohmann_json::nlohmann_json + PUBLIC + thr_compiler_flags + EnTT::EnTT + spdlog::spdlog + SFML::Graphics + SFML::Window + SFML::System + TGUI::TGUI + nlohmann_json::nlohmann_json + sol2::sol2 + Lua::Lua ) # 添加安装支持 diff --git a/src/thr/base/file.cpp b/src/thr/base/file.cpp index 7cd5c99..2e0b3c3 100644 --- a/src/thr/base/file.cpp +++ b/src/thr/base/file.cpp @@ -2,8 +2,8 @@ * @file file.cpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 实现了一些与文件相关的函数。 - * @version 0.1.0-1 - * @date 2026-06-19 + * @version 0.1.0-2 + * @date 2026-07-12 * * @copyright cpp-love * @@ -83,7 +83,28 @@ namespace thr { std::optional get_executable_directory() noexcept { auto path = get_executable_path(); - return path.transform([](std::filesystem::path path) { return path.parent_path(); }); + return path.transform([](const std::filesystem::path &path) { return path.parent_path(); }); + } + + std::optional + get_existing_full_path(const std::filesystem::path &path) noexcept { + return get_executable_directory() + .and_then([&](std::filesystem::path folder_path) -> std::optional { + // 先尝试相对于可执行文件父文件夹。 + folder_path /= path; + if (std::filesystem::exists(folder_path)) { + return folder_path; + } + return std::nullopt; + }) + .or_else([&] -> std::optional { + // 再尝试相对于工作目录。 + std::filesystem::path full_path = std::filesystem::current_path() / path; + if (std::filesystem::exists(full_path)) { + return full_path; + } + return std::nullopt; + }); } } // namespace thr \ No newline at end of file diff --git a/src/thr/ecs/lua_bindings/lua_manager.cpp b/src/thr/ecs/lua_bindings/lua_manager.cpp new file mode 100644 index 0000000..b2bdb94 --- /dev/null +++ b/src/thr/ecs/lua_bindings/lua_manager.cpp @@ -0,0 +1,115 @@ +/** + * @file lua_manager.cpp + * @author cpp-love (207296385+cpp-love@users.noreply.github.com) + * @brief 实现了与 Lua 沟通的管理类。 + * @version 0.1.0-1 + * @date 2026-07-09 + * + * @copyright cpp-love + * + */ + +#include "thr/ecs/lua_bindings/lua_manager.hpp" +#include "thr/base/sfml_helper.hpp" +#include "thr/ecs/lua_bindings/entity_wrapper.hpp" +#include "thr/ecs/lua_bindings/game_api.hpp" +#include +#include +#include +#include +#include +#include + +namespace thr::ecs::lua_bindings { + + void lua_manager::init() { + // 默认初始化。 + m_lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::table, sol::lib::math, + sol::lib::string); + + // 辅助函数。 + auto concat = [tostring = m_lua["tostring"].get()](sol::variadic_args args) { + std::string msg; + for (const auto &var : args) { + if (!msg.empty()) { + msg += '\t'; + } + msg += tostring(var); + } + return msg; + }; + + // 注册一系列仿 spdlog 式的输出函数。 + sol::table logger = m_lua.create_table("Logger"); + logger.set_function("trace", [concat](sol::variadic_args args) { spdlog::trace(concat(args)); }); + logger.set_function("debug", [concat](sol::variadic_args args) { spdlog::debug(concat(args)); }); + logger.set_function("info", [concat](sol::variadic_args args) { spdlog::info(concat(args)); }); + logger.set_function("warn", [concat](sol::variadic_args args) { spdlog::warn(concat(args)); }); + logger.set_function("error", [concat](sol::variadic_args args) { spdlog::error(concat(args)); }); + logger.set_function("critical", + [concat](sol::variadic_args args) { spdlog::critical(concat(args)); }); + + // 注册 Vector2f 到 Lua。 + m_lua.new_usertype( + "Vector2", sol::constructors(), + + "x", &sf::Vector2f::x, "y", &sf::Vector2f::y, + + sol::meta_function::unary_minus, [](const sf::Vector2f &lhs) { return -lhs; }, + + sol::meta_function::addition, + [](const sf::Vector2f &lhs, const sf::Vector2f &rhs) { return lhs + rhs; }, + + sol::meta_function::subtraction, + [](const sf::Vector2f &lhs, const sf::Vector2f &rhs) { return lhs - rhs; }, + + sol::meta_function::multiplication, + sol::overload([](const sf::Vector2f &lhs, float rhs) { return lhs * rhs; }, + [](float lhs, const sf::Vector2f &rhs) { return lhs * rhs; }), + + sol::meta_function::division, [](const sf::Vector2f &lhs, float rhs) { return lhs / rhs; }, + + sol::meta_function::equal_to, + [](const sf::Vector2f &lhs, const sf::Vector2f &rhs) { return lhs == rhs; }, + + sol::meta_function::to_string, + [](const sf::Vector2f &lhs) { return std::format("{}", lhs); }); + + // 注册 Entity 包装器到 Lua。 + m_lua.new_usertype( + "Entity", sol::no_constructor, + + "valid", &entity_wrapper::valid, + + "destroy", &entity_wrapper::destroy, + + "add_tag", &entity_wrapper::add_tag, + + "remove_tag", &entity_wrapper::remove_tag, + + "query_tag", &entity_wrapper::query_tag, + + sol::meta_function::equal_to, + [](const entity_wrapper &lhs, const entity_wrapper &rhs) { return lhs == rhs; }); + + // 注册 Game API 到 Lua。 + m_lua.new_usertype( + "Game", sol::no_constructor, + + "create_entity", &game_api::create_entity, + + "query_entities_with_tag", &game_api::query_entities_with_tag, + + "add_text", + sol::overload([&](game_api &game, std::string_view text, + sf::Vector2f position) { return game.add_text(text, position); }, + [&](game_api &game, std::string_view text, sf::Vector2f position, + unsigned int character_size) { + return game.add_text(text, position, character_size); + })); + + // 将一个 `game` 全局变量放入 Lua,供脚本直接使用。 + m_lua["game"] = game_api(m_registry); + } + +} // namespace thr::ecs::lua_bindings \ No newline at end of file diff --git a/src/thr/ecs/systems/global/game_state_manager.cpp b/src/thr/ecs/systems/global/game_state_manager.cpp index 79703c0..fad9f56 100644 --- a/src/thr/ecs/systems/global/game_state_manager.cpp +++ b/src/thr/ecs/systems/global/game_state_manager.cpp @@ -2,8 +2,8 @@ * @file game_state_manager.cpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 实现了游戏状态系统。 - * @version 0.1.0-1 - * @date 2026-05-02 + * @version 0.1.0-4 + * @date 2026-07-12 * * @copyright cpp-love * @@ -12,7 +12,14 @@ #include "thr/ecs/systems/global/game_state_manager.hpp" #include "thr/base/assert_msg.hpp" #include "thr/ecs/components/global/game_state_components.hpp" +#include "thr/ecs/configs.hpp" #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -20,10 +27,17 @@ namespace thr::ecs { + const tgui::String game_state_manager::game_screen_panel_name{"game_screen_panel"}; + game_state_manager::game_state_manager(sf::RenderWindow &window) noexcept : m_window(window), m_gui(m_window) { m_dispatcher.sink().connect<&game_state_manager::on_push_state>(this); m_dispatcher.sink().connect<&game_state_manager::on_pop_state>(this); + m_gui.setFont(thr::ecs::configs::singleton().get_tgui_font()); + m_gui.setTextSize(14u); + tgui::Panel::Ptr game_screen_panel = tgui::Panel::create(); + game_screen_panel->getRenderer()->setBackgroundColor(tgui::Color::Transparent); + m_gui.add(game_screen_panel, game_screen_panel_name); } game_state_manager::~game_state_manager() noexcept { @@ -52,9 +66,50 @@ namespace thr::ecs { m_dispatcher.sink().connect<&game_state_manager::on_pop_state>(this); } bool game_state_manager::handle_event(const sf::Event &event) noexcept { + if (event.is()) { + spdlog::info("Quit the window."); + m_window.close(); + return true; + } + + if (const auto *resized = event.getIf()) { + // 更新视图。 + float window_ratio = + static_cast(resized->size.x) / static_cast(resized->size.y); + sf::View view{m_window.getView()}; + float old_view_ratio = + static_cast(view.getSize().x) / static_cast(view.getSize().y); + sf::FloatRect viewport{{0, 0}, {1, 1}}; + tgui::Panel::Ptr game_screen_panel = + m_gui.get(thr::ecs::game_state_manager::game_screen_panel_name); + + // 根据长宽比计算视口位置和大小,确保黑边方向正确。 + if (window_ratio > old_view_ratio) { + // 窗口更扁,黑边在左右。 + viewport.size.x = old_view_ratio / window_ratio; + viewport.position.x = (1 - viewport.size.x) / 2; + game_screen_panel->setPosition({viewport.position.x * resized->size.x, 0}); + game_screen_panel->setSize({viewport.size.x * resized->size.x, resized->size.y}); + } else { + // 窗口更瘦高,黑边在上下。 + viewport.size.y = window_ratio / old_view_ratio; + viewport.position.y = (1 - viewport.size.y) / 2; + game_screen_panel->setPosition({0, viewport.position.y * resized->size.y}); + game_screen_panel->setSize({resized->size.x, viewport.size.y * resized->size.y}); + } + + // 创建并应用新的视口。 + view.setViewport(viewport); + m_window.setView(view); + m_gui.setAbsoluteView( + {0, 0, static_cast(resized->size.x), static_cast(resized->size.y)}); + return true; + } + if (m_gui.handleEvent(event)) { return true; } + for (auto &state : m_states | std::views::reverse) { if (state->handle_event(event)) { return true; diff --git a/src/thr/ecs/systems/level_graph_render_system.cpp b/src/thr/ecs/systems/level_graph_render_system.cpp index 5d0fde8..b6b6509 100644 --- a/src/thr/ecs/systems/level_graph_render_system.cpp +++ b/src/thr/ecs/systems/level_graph_render_system.cpp @@ -2,8 +2,8 @@ * @file level_graph_render_system.hpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 定义了渲染关卡图的系统。 - * @version 0.1.0-2 - * @date 2026-07-07 + * @version 0.1.0-4 + * @date 2026-07-12 * * @copyright cpp-love * @@ -11,16 +11,20 @@ #include "thr/ecs/systems/level_graph_render_system.hpp" #include "thr/base/assert_msg.hpp" +#include "thr/ecs/configs.hpp" #include #include #include #include #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -28,12 +32,14 @@ #include #include #include -#include namespace thr::ecs { namespace { - constexpr sf::Vector2f button_size{80.f, 40.f}; + constexpr float button_scale = 0.075f; + const tgui::Layout2d button_size{tgui::RelativeValue(button_scale), + tgui::RelativeValue(button_scale)}; ///< 按钮大小。 + /* void remove_existing_nodes(tgui::Gui &gui) noexcept { auto widgets = gui.getWidgets(); for (const auto &widget : widgets) { @@ -43,24 +49,31 @@ namespace thr::ecs { } } } + */ - void draw_edge(sf::RenderTarget &render, const sf::Vector2f &from, const sf::Vector2f &to, + /** + * @brief 绘制线段。 + * @param [in] render 渲染目标。 + * @param [in] start 线段起点。 + * @param [in] end 线段终点。 + * @param [in] color 线段颜色。 + */ + void draw_edge(sf::RenderTarget &render, const sf::Vector2f &start, const sf::Vector2f &end, const sf::Color &color = sf::Color::White) { sf::VertexArray line(sf::PrimitiveType::Lines, 2); - line[0].position = from; - line[1].position = to; + line[0].position = start; + line[1].position = end; line[0].color = color; line[1].color = color; render.draw(line); } } // namespace - void level_graph_render_system::draw(const entt::registry ®istry, tgui::Gui &gui, - entt::entity entity, node_callback on_click) noexcept { + void level_graph_render_system::draw(const entt::registry ®istry, tgui::Container::Ptr container, + sf::RenderTarget &target, entt::entity entity, + node_callback on_click) noexcept { THR_ASSERT_MSG(registry.valid(entity), "起始实体无效。"); // 不每次销毁并重建所有按钮,复用已有 widgets,减少频繁创建影响 UI。 - sf::RenderTarget *render_target = gui.getTarget(); //< 渲染目标。 - THR_ASSERT_MSG(render_target != nullptr, "GUI 对象没有渲染目标。"); std::set visited; //< 是否访问过。 @@ -76,25 +89,32 @@ namespace thr::ecs { // 使用实体 id 作为 widget 名称,保证跨帧稳定性,便于复用。 const std::string widget_id_name = std::format("{}{}", widget_prefix, entt::to_integral(current)); - tgui::Button::Ptr button = gui.get(widget_id_name); - const sf::Vector2f pos = node.position; - if (button) { - // 复用已有按钮:只更新状态和位置,避免重新分配和重复连接回调。 - button->setText(node.locked ? std::format("{} [locked]", node.name) : node.name); - button->setPosition({pos.x, pos.y}); - button->setSize(tgui::Layout2d{button_size}); - button->setEnabled(!node.locked); - } else { - button = tgui::Button::create(node.locked ? std::format("{} [locked]", node.name) - : node.name); - button->setPosition({pos.x, pos.y}); - button->setSize(tgui::Layout2d{button_size}); - button->setEnabled(!node.locked); + + tgui::Button::Ptr button = container->get(widget_id_name); + + const tgui::Layout2d position{ + tgui::RelativeValue(node.position.x + / static_cast(configs::singleton().game_screen_size.x)), + tgui::RelativeValue(node.position.y + / static_cast(configs::singleton().game_screen_size.y))}; + + if (button == nullptr) { + button = tgui::Button::create(); + button->getRenderer()->setRoundedBorderRadius(10.f); if (on_click) { button->onPress([on_click, current] { on_click(current); }); } - gui.add(button, widget_id_name); + container->add(button, widget_id_name); } + // 复用已有按钮:只更新状态和位置,避免重新分配和重复连接回调。 + button->setPosition(position); + button->setSize(button_size); + button->setEnabled(!node.locked); + button->setText(node.locked ? std::format("{} [locked]", node.name) : node.name); + + sf::Vector2f half_button_real_size = { + static_cast(configs::singleton().game_screen_size.x) * button_scale / 2, + static_cast(configs::singleton().game_screen_size.y) * button_scale / 2}; // 向下遍历。 for (entt::entity next : node.relative_entities) { @@ -103,8 +123,8 @@ namespace thr::ecs { const auto &next_node = registry.get(next); if (!node.locked) { // 重复画没有关系,因为无效果。 - draw_edge(*render_target, node.position + button_size / 2.f, - next_node.position + button_size / 2.f, sf::Color::White); + draw_edge(target, node.position + half_button_real_size, + next_node.position + half_button_real_size, sf::Color::White); } self(next); } diff --git a/src/thr/ecs/systems/level_render_system.cpp b/src/thr/ecs/systems/level_render_system.cpp index 8805e92..776a313 100644 --- a/src/thr/ecs/systems/level_render_system.cpp +++ b/src/thr/ecs/systems/level_render_system.cpp @@ -2,8 +2,8 @@ * @file level_render_system.cpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 定义了迷宫渲染系统。 - * @version 0.1.0-2 - * @date 2026-06-19 + * @version 0.1.0-3 + * @date 2026-07-12 * * @copyright cpp-love * @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,14 @@ namespace thr::ecs { const auto &lines = registry.ctx().get(); render.draw(lines, states); + // draw texts + auto texts = registry.view(); + for (const auto &[entity, text] : texts.each()) { render.draw(text, states); } + + // draw sprites + auto sprites = registry.view(); + for (const auto &[entity, sprite] : sprites.each()) { render.draw(sprite, states); } + // draw players const auto &player_on_grounds = registry.view(); for (entt::entity player : player_on_grounds) { diff --git a/src/thr/ecs/systems/level_serialization_system.cpp b/src/thr/ecs/systems/level_serialization_system.cpp index 9e6c11c..679e6ad 100644 --- a/src/thr/ecs/systems/level_serialization_system.cpp +++ b/src/thr/ecs/systems/level_serialization_system.cpp @@ -2,22 +2,24 @@ * @file level_serialization_system.cpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief 实现了序列化迷宫的系统。 - * @version 0.1.0-3 - * @date 2026-07-06 + * @version 0.1.0-4 + * @date 2026-07-12 * * @copyright cpp-love * */ #include "thr/ecs/systems/level_serialization_system.hpp" +#include "thr/base/file.hpp" #include "thr/base/sfml_helper.hpp" +#include "thr/ecs/components/level_components.hpp" #include "thr/ecs/components/maze_components.hpp" -#include "thr/ecs/components/player_components.hpp" #include +#include #include #include #include -#include +#include #include namespace thr::ecs { @@ -45,28 +47,37 @@ namespace thr::ecs { for (auto [idx, entity] : list | std::views::enumerate) { list_map.try_emplace(entity, idx); } for (entt::entity entity : list) { const auto &seg = registry.get(entity); - segments.push_back( - {{"prev", seg.prev.and_then([&](entt::entity entity) -> std::optional { - auto it = list_map.find(entity); - if (it == list_map.end()) { - return {}; - } - return it->second; - })}, - {"next", seg.next.and_then([&](entt::entity entity) -> std::optional { - auto it = list_map.find(entity); - if (it == list_map.end()) { - return {}; - } - return it->second; - })}, - {"start_center", seg.start_center}, - {"length", seg.length}, - {"walked_precent", seg.walked_precent}, - {"dir", direction_to_name(seg.dir)}}); + auto transform_entity = [&](entt::entity entity) -> std::optional { + auto iter = list_map.find(entity); + if (iter == list_map.end()) { + return {}; + } + return iter->second; + }; + segments.push_back({{"prev", seg.prev.and_then(transform_entity)}, + {"next", seg.next.and_then(transform_entity)}, + {"start_center", seg.start_center}, + {"length", seg.length}, + {"walked_precent", seg.walked_precent}, + {"dir", direction_to_name(seg.dir)}, + {"tags", [&] { + const auto *cur_tag = registry.try_get(entity); + if (cur_tag) { + return cur_tag->tag_ids; + } + return std::set{}; + }() | std::ranges::to()}}); } json["segments"] = std::move(segments); + // serialize level_script + const auto *script = registry.ctx().find(); + if (script != nullptr) { + json["level_script"] = script->script_file_name; + } else { + json["level_script"] = nullptr; + } + // serialize level_info const auto &level_info = registry.ctx().get(); json["level_info"] = {{"start_segment_entity", level_info.start_segment_entity}, @@ -105,12 +116,11 @@ namespace thr::ecs { registry.create(segment_entities.begin(), segment_entities.end()); for (const auto &[seg_json, entity] : std::views::zip(segments_json, segment_entities)) { - segment seg; - seg.start_center = seg_json.at("start_center"); - seg.length = seg_json.at("length"); - seg.walked_precent = seg_json.value("walked_precent", 0.f); - seg.dir = name_to_direction(seg_json.at("dir").get()); - auto prev = seg_json.value("prev", nlohmann::json()); + segment seg{.start_center = seg_json.at("start_center"), + .length = seg_json.at("length"), + .walked_precent = seg_json.value("walked_precent", 0.f), + .dir = name_to_direction(seg_json.at("dir").get())}; + auto prev = seg_json.value("prev", nlohmann::json()); if (!prev.is_null()) { seg.prev = segment_entities.at(prev); } else { @@ -128,9 +138,23 @@ namespace thr::ecs { registry.emplace(node_entity, node); } + auto tags = seg_json.value("tags", std::vector()); + if (!tags.empty()) { + registry.emplace(entity, std::set(std::from_range, tags)); + } + registry.emplace(entity, seg); } + // deserialize level_script + json.value("level_script", std::optional()) + .transform([&](std::string_view script) { + registry.ctx().emplace( + get_existing_full_path(std::filesystem::path("assets/lua_scripts") / script).value(), + std::string(script)); + return 0; + }); + // deserialize level_info const auto &level_info_json = json.at("level_info"); level_info level_info{ diff --git a/src/thr/ecs/systems/player_movement_system.cpp b/src/thr/ecs/systems/player_movement_system.cpp index 99c0b4f..ad53442 100644 --- a/src/thr/ecs/systems/player_movement_system.cpp +++ b/src/thr/ecs/systems/player_movement_system.cpp @@ -14,6 +14,7 @@ #include "thr/base/floating_point_compare.hpp" #include "thr/base/overload.hpp" #include "thr/ecs/components/global/game_base.hpp" +#include "thr/ecs/components/level_components.hpp" #include "thr/ecs/components/maze_components.hpp" #include "thr/ecs/components/player_components.hpp" #include "thr/ecs/systems/global/scene_system.hpp" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 96eab33..ef0de97 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,138 +5,175 @@ enable_testing() # 默认不添加,如果启用 THR_BUILD_TESTS 则默认添加 if(THR_BUILD_TESTS) - option(THR_BUILD_BASE_TESTS "Build the tests in the tests/base directory" ON) - option(THR_BUILD_ECS_TESTS "Build the tests in the tests/ecs directory" ON) + option(THR_BUILD_BASE_TESTS "Build the tests in the tests/base directory" ON) + option(THR_BUILD_ECS_TESTS "Build the tests in the tests/ecs directory" ON) else() - option(THR_BUILD_BASE_TESTS "Build the tests in the tests/base directory" OFF) - option(THR_BUILD_ECS_TESTS "Build the tests in the tests/ecs directory" OFF) + option(THR_BUILD_BASE_TESTS "Build the tests in the tests/base directory" OFF) + option(THR_BUILD_ECS_TESTS "Build the tests in the tests/ecs directory" OFF) endif() if(THR_BUILD_BASE_TESTS) - # 设置输出目录 - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/tests) - - # 构建程序 - - # 设置链接文件 - add_executable(test_assert_msg ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_assert_msg.cpp) - add_executable(test_floating_point_compare ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_floating_point_compare.cpp) - add_executable(test_sfml_helper ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_sfml_helper.cpp) - add_executable(test_overload ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_overload.cpp) - add_executable(test_observable ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_observable.cpp) - - # 链接第三方库 - target_link_libraries(test_assert_msg - PUBLIC - thr_compiler_flags - thr - ) - - target_link_libraries(test_floating_point_compare - PUBLIC - thr_compiler_flags - thr - ) - - target_link_libraries(test_sfml_helper - PUBLIC - thr_compiler_flags - thr - ) - - target_link_libraries(test_overload - PUBLIC - thr_compiler_flags - thr - ) - - target_link_libraries(test_observable - PUBLIC - thr_compiler_flags - thr - ) - - # 添加安装支持 - install( - TARGETS test_assert_msg test_floating_point_compare - test_sfml_helper test_overload test_observable - DESTINATION bin/tests) - - # 添加测试支持 - add_test(NAME RunTestBase3 COMMAND test_floating_point_compare) - add_test(NAME RunTestBase4 COMMAND test_sfml_helper) - add_test(NAME TestBase4StandardUse COMMAND test_sfml_helper) - set_tests_properties(TestBase4StandardUse - PROPERTIES PASS_REGULAR_EXPRESSION "vec1 is \\(10.000, 10.000\\), vec2 is \\(11, 11\\), vec3 is \\(13, 10\\)\\s*" - ) - add_test(NAME RunTestBase5 COMMAND test_overload) - add_test(NAME TestBase5StandardUse COMMAND test_overload) - set_tests_properties(TestBase5StandardUse - PROPERTIES PASS_REGULAR_EXPRESSION "value: 2\ncall from lambda\nvalue: 1\ncall from lambda3\\s*" - ) - add_test(NAME RunTestBase6 COMMAND test_observable) - add_test(NAME TestBase6StandardUse COMMAND test_observable) - set_tests_properties(TestBase6StandardUse - PROPERTIES PASS_REGULAR_EXPRESSION "value changed, now is: 11\nvalue changed, now is: 22\\s*" - ) - - # todo : 解决下面的测试失败的问题 - # add_test(NAME RunTestBase2 COMMAND test_assert_msg) - # set_tests_properties(RunTestBase2 PROPERTIES WILL_FAIL TRUE) + # 设置输出目录 + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/tests) + + # 构建程序 + + # 设置链接文件 + add_executable(test_assert_msg + ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_assert_msg.cpp + ) + add_executable(test_floating_point_compare + ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_floating_point_compare.cpp + ) + add_executable(test_sfml_helper + ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_sfml_helper.cpp + ) + add_executable(test_overload + ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_overload.cpp + ) + add_executable(test_observable + ${CMAKE_CURRENT_SOURCE_DIR}/thr/base/test_observable.cpp + ) + + # 链接第三方库 + target_link_libraries(test_assert_msg + PUBLIC + thr_compiler_flags + thr + ) + + target_link_libraries(test_floating_point_compare + PUBLIC + thr_compiler_flags + thr + ) + + target_link_libraries(test_sfml_helper + PUBLIC + thr_compiler_flags + thr + ) + + target_link_libraries(test_overload + PUBLIC + thr_compiler_flags + thr + ) + + target_link_libraries(test_observable + PUBLIC + thr_compiler_flags + thr + ) + + # 添加安装支持 + install( + TARGETS + test_assert_msg + test_floating_point_compare + test_sfml_helper + test_overload + test_observable + DESTINATION bin/tests + ) + + # 添加测试支持 + add_test(NAME RunTestBase3 COMMAND test_floating_point_compare) + add_test(NAME RunTestBase4 COMMAND test_sfml_helper) + add_test(NAME TestBase4StandardUse COMMAND test_sfml_helper) + set_tests_properties(TestBase4StandardUse + PROPERTIES PASS_REGULAR_EXPRESSION "vec1 is \\(10.000, 10.000\\), vec2 is \\(11, 11\\), vec3 is \\(13, 10\\)\\s*" + ) + add_test(NAME RunTestBase5 COMMAND test_overload) + add_test(NAME TestBase5StandardUse COMMAND test_overload) + set_tests_properties(TestBase5StandardUse + PROPERTIES PASS_REGULAR_EXPRESSION "value: 2\ncall from lambda\nvalue: 1\ncall from lambda3\\s*" + ) + add_test(NAME RunTestBase6 COMMAND test_observable) + add_test(NAME TestBase6StandardUse COMMAND test_observable) + set_tests_properties(TestBase6StandardUse + PROPERTIES PASS_REGULAR_EXPRESSION "value changed, now is: 11\nvalue changed, now is: 22\\s*" + ) + + # todo : 解决下面的测试失败的问题 + # add_test(NAME RunTestBase2 COMMAND test_assert_msg) + # set_tests_properties(RunTestBase2 PROPERTIES WILL_FAIL TRUE) endif() if(THR_BUILD_ECS_TESTS) - # 设置输出目录 - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/tests) - - # 构建程序 - - # 设置链接文件 - add_executable(test_scene_system ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_scene_system.cpp) - add_executable(test_level_serialization_system ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_serialization_system.cpp) - add_executable(test_level_render_system ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_render_system.cpp) - add_executable(test_level_graph_render_system ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_graph_render_system.cpp) - add_executable(test_level_graph_serialization_system ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_graph_serialization_system.cpp) - - # 链接第三方库 - target_link_libraries(test_scene_system - PUBLIC - thr_compiler_flags - thr - ) - target_link_libraries(test_level_serialization_system - PUBLIC - thr_compiler_flags - thr - ) - target_link_libraries(test_level_render_system - PUBLIC - thr_compiler_flags - thr - ) - target_link_libraries(test_level_graph_render_system - PUBLIC - thr_compiler_flags - thr - ) - target_link_libraries(test_level_graph_serialization_system - PUBLIC - thr_compiler_flags - thr - ) - - # 添加安装支持 - install(TARGETS test_scene_system test_level_serialization_system test_level_render_system test_level_graph_render_system test_level_graph_serialization_system DESTINATION bin/tests) - install(FILES maze.json DESTINATION bin/tests) - - # 添加测试支持 - add_test(NAME RunTestEcs2 COMMAND test_scene_system) - # todo : 修复此错误 - # add_test(NAME TestEcs2StandardUse COMMAND test_scene_system) - # set_tests_properties(TestEcs2StandardUse - # PROPERTIES PASS_REGULAR_EXPRESSION "创建场景1\n将孩子1添加到场景1中\n现在孩子1的父亲有:\n\\d+\n将孩子2添加到场景1中\n现在场景1的子实体有:\n\\d+ \\d+\n将孩子1从场景1中删除\n现在孩子1的父亲有:\n创建场景2\n现在registry拥有场景:\nid: 1 entity: \\d+\nid: 2 entity: \\d+\n销毁场景2\n现在registry拥有场景:\nid: 1 entity: \\d+\n删除所有场景\n现在registry拥有场景:\\s*") - # add_test(NAME RunTestEcs3 COMMAND test_level_serialization_system) - add_test(NAME RunTestEcs4 COMMAND test_level_graph_serialization_system) - add_test(NAME RunTestEcs5 COMMAND test_level_graph_render_system) + # 设置输出目录 + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/tests) + + # 构建程序 + + # 设置链接文件 + add_executable(test_scene_system + ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_scene_system.cpp + ) + add_executable(test_level_serialization_system + ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_serialization_system.cpp + ) + add_executable(test_level_render_system + ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_render_system.cpp + ) + add_executable(test_level_graph_render_system + ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_graph_render_system.cpp + ) + add_executable(test_level_graph_serialization_system + ${CMAKE_CURRENT_SOURCE_DIR}/thr/ecs/systems/test_level_graph_serialization_system.cpp + ) + + # 链接第三方库 + target_link_libraries(test_scene_system + PUBLIC + thr_compiler_flags + thr + ) + target_link_libraries(test_level_serialization_system + PUBLIC + thr_compiler_flags + thr + ) + target_link_libraries(test_level_render_system + PUBLIC + thr_compiler_flags + thr + ) + target_link_libraries(test_level_graph_render_system + PUBLIC + thr_compiler_flags + thr + ) + target_link_libraries(test_level_graph_serialization_system + PUBLIC + thr_compiler_flags + thr + ) + + # 添加安装支持 + install( + TARGETS + test_scene_system + test_level_serialization_system + test_level_render_system + test_level_graph_render_system + test_level_graph_serialization_system + DESTINATION bin/tests + ) + install(FILES maze.json DESTINATION bin/tests) + + # 添加测试支持 + add_test(NAME RunTestEcs2 COMMAND test_scene_system) + # todo : 修复此错误 + # add_test(NAME TestEcs2StandardUse COMMAND test_scene_system) + # set_tests_properties(TestEcs2StandardUse + # PROPERTIES PASS_REGULAR_EXPRESSION "创建场景1\n将孩子1添加到场景1中\n现在孩子1的父亲有:\n\\d+\n将孩子2添加到场景1中\n现在场景1的子实体有:\n\\d+ \\d+\n将孩子1从场景1中删除\n现在孩子1的父亲有:\n创建场景2\n现在registry拥有场景:\nid: 1 entity: \\d+\nid: 2 entity: \\d+\n销毁场景2\n现在registry拥有场景:\nid: 1 entity: \\d+\n删除所有场景\n现在registry拥有场景:\\s*") + # add_test(NAME RunTestEcs3 COMMAND test_level_serialization_system) + add_test(NAME RunTestEcs4 COMMAND test_level_graph_serialization_system) + add_test( + NAME RunTestEcs5 + COMMAND test_level_graph_render_system + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) endif() diff --git a/tests/thr/ecs/systems/level_example.json b/tests/thr/ecs/systems/level_example.json deleted file mode 100644 index 8e088d6..0000000 --- a/tests/thr/ecs/systems/level_example.json +++ /dev/null @@ -1,412 +0,0 @@ -{ - "$schema": "../../../../schema/level-v0.1.0-3.schema.json", - "segments": [ - { - "start_center": { - "x": 200, - "y": 200 - }, - "length": 300, - "dir": "right", - "next": 1, - "walked_precent": 1 - }, - { - "start_center": { - "x": 500, - "y": 200 - }, - "length": 100, - "dir": "down", - "prev": 0, - "next": 2, - "walked_precent": 1 - }, - { - "start_center": { - "x": 500, - "y": 300 - }, - "length": 250, - "dir": "left", - "prev": 1, - "next": 3, - "walked_precent": 1 - }, - { - "start_center": { - "x": 250, - "y": 300 - }, - "length": 80, - "dir": "up", - "prev": 2, - "walked_precent": 1 - }, - { - "start_center": { - "x": 250, - "y": 180 - }, - "length": 80, - "dir": "up", - "next": 5, - "walked_precent": 1 - }, - { - "start_center": { - "x": 250, - "y": 100 - }, - "length": 100, - "dir": "right", - "prev": 4, - "next": 6, - "walked_precent": 1 - }, - { - "start_center": { - "x": 350, - "y": 100 - }, - "length": 80, - "dir": "down", - "prev": 5, - "walked_precent": 1 - }, - { - "start_center": { - "x": 520, - "y": 200 - }, - "length": 80, - "dir": "right", - "next": 8, - "walked_precent": 1 - }, - { - "start_center": { - "x": 600, - "y": 200 - }, - "length": 90, - "dir": "down", - "prev": 7, - "next": 9, - "walked_precent": 1 - }, - { - "start_center": { - "x": 600, - "y": 290 - }, - "length": 80, - "dir": "left", - "prev": 8, - "walked_precent": 1 - }, - { - "start_center": { - "x": 480, - "y": 290 - }, - "length": 210, - "dir": "left", - "walked_precent": 1 - }, - { - "start_center": { - "x": 250, - "y": 320 - }, - "length": 80, - "dir": "down", - "next": 12, - "walked_precent": 1 - }, - { - "start_center": { - "x": 250, - "y": 400 - }, - "length": 250, - "dir": "right", - "prev": 11, - "next": 13, - "walked_precent": 1 - }, - { - "start_center": { - "x": 500, - "y": 400 - }, - "length": 80, - "dir": "up", - "prev": 12, - "next": 14, - "walked_precent": 1 - }, - { - "start_center": { - "x": 500, - "y": 320 - }, - "length": 160, - "dir": "left", - "prev": 13, - "walked_precent": 1 - }, - { - "start_center": { - "x": 340, - "y": 270 - }, - "length": 50, - "dir": "up", - "walked_precent": 1 - }, - { - "start_center": { - "x": 340, - "y": 180 - }, - "length": 60, - "dir": "up", - "walked_precent": 1 - }, - { - "start_center": { - "x": 370, - "y": 100 - }, - "length": 230, - "dir": "right", - "walked_precent": 1 - } - ], - "line_strips": { - "vertexs": [ - [ - { - "x": 260, - "y": 210 - }, - { - "x": 260, - "y": 290 - }, - { - "x": 490, - "y": 290 - }, - { - "x": 490, - "y": 210 - }, - { - "x": 190, - "y": 210 - }, - { - "x": 190, - "y": 190 - }, - { - "x": 510, - "y": 190 - }, - { - "x": 510, - "y": 310 - }, - { - "x": 240, - "y": 310 - }, - { - "x": 240, - "y": 210 - } - ], - [ - { - "x": 240, - "y": 190 - }, - { - "x": 240, - "y": 90 - }, - { - "x": 360, - "y": 90 - }, - { - "x": 360, - "y": 190 - } - ], - [ - { - "x": 260, - "y": 190 - }, - { - "x": 260, - "y": 110 - }, - { - "x": 340, - "y": 110 - }, - { - "x": 340, - "y": 190 - } - ], - [ - { - "x": 510, - "y": 190 - }, - { - "x": 610, - "y": 190 - }, - { - "x": 610, - "y": 300 - }, - { - "x": 510, - "y": 300 - } - ], - [ - { - "x": 510, - "y": 210 - }, - { - "x": 590, - "y": 210 - }, - { - "x": 590, - "y": 280 - }, - { - "x": 510, - "y": 280 - } - ], - [ - { - "x": 490, - "y": 280 - }, - { - "x": 260, - "y": 280 - } - ], - [ - { - "x": 240, - "y": 310 - }, - { - "x": 240, - "y": 410 - }, - { - "x": 510, - "y": 410 - }, - { - "x": 510, - "y": 310 - } - ], - [ - { - "x": 260, - "y": 310 - }, - { - "x": 260, - "y": 390 - }, - { - "x": 490, - "y": 390 - }, - { - "x": 490, - "y": 330 - }, - { - "x": 330, - "y": 330 - }, - { - "x": 330, - "y": 310 - } - ], - [ - { - "x": 330, - "y": 280 - }, - { - "x": 330, - "y": 210 - } - ], - [ - { - "x": 350, - "y": 280 - }, - { - "x": 350, - "y": 210 - } - ], - [ - { - "x": 330, - "y": 190 - }, - { - "x": 330, - "y": 110 - } - ], - [ - { - "x": 360, - "y": 110 - }, - { - "x": 610, - "y": 110 - }, - { - "x": 610, - "y": 90 - }, - { - "x": 360, - "y": 90 - } - ] - ], - "width": 5 - }, - "level_info": { - "start_segment_entity": 0, - "end_segment_entity": 17 - } -} \ No newline at end of file diff --git a/tests/thr/ecs/systems/test_level_graph_render_system.cpp b/tests/thr/ecs/systems/test_level_graph_render_system.cpp index f132061..77f7dc1 100644 --- a/tests/thr/ecs/systems/test_level_graph_render_system.cpp +++ b/tests/thr/ecs/systems/test_level_graph_render_system.cpp @@ -2,8 +2,8 @@ * @file test_level_graph_render_system.cpp * @author cpp-love (207296385+cpp-love@users.noreply.github.com) * @brief `thr::ecs::level_graph_render_system` 的简单回归测试。 - * @version 0.1.0-2 - * @date 2026-07-07 + * @version 0.1.0-3 + * @date 2026-07-12 * * @copyright cpp-love * @@ -11,6 +11,7 @@ #include #include +#include #undef NDEBUG #include "thr/base/assert_msg.hpp" #include "thr/ecs/components/level_graph_components.hpp" @@ -32,12 +33,14 @@ int main() { registry.emplace(next_entity, std::vector{}, sf::Vector2f{220.f, 160.f}, "next", true); - thr::ecs::level_graph_render_system::draw(registry, gui, start_entity); + thr::ecs::level_graph_render_system::draw(registry, gui.getContainer(), render, start_entity); - auto start_button = gui.get("level_graph_node_0"); + auto start_button = gui.get(std::format( + "{}{}", thr::ecs::level_graph_render_system::widget_prefix, entt::to_integral(start_entity))); THR_ASSERT_MSG(start_button != nullptr); THR_ASSERT_MSG(start_button->getText() == "start"); - auto next_button = gui.get("level_graph_node_1"); + auto next_button = gui.get(std::format( + "{}{}", thr::ecs::level_graph_render_system::widget_prefix, entt::to_integral(next_entity))); THR_ASSERT_MSG(next_button != nullptr); THR_ASSERT_MSG(next_button->getText() == "next [locked]"); diff --git a/tests/thr/ecs/systems/test_level_render_system.cpp b/tests/thr/ecs/systems/test_level_render_system.cpp index eb7b08d..21cb620 100644 --- a/tests/thr/ecs/systems/test_level_render_system.cpp +++ b/tests/thr/ecs/systems/test_level_render_system.cpp @@ -92,14 +92,7 @@ int main() { auto prev = thr::ecs::clock::now(); while (window.isOpen()) { // handle event - while (const std::optional event = window.pollEvent()) { - if (!manager.handle_event(*event)) { - if (event->is()) { - spdlog::info("quit the window"); - window.close(); - } - } - } + while (const std::optional event = window.pollEvent()) { manager.handle_event(*event); } // update auto cur = thr::ecs::clock::now(); diff --git a/tests/thr/ecs/systems/test_level_serialization_system.cpp b/tests/thr/ecs/systems/test_level_serialization_system.cpp index 6f729d0..a7bdb66 100644 --- a/tests/thr/ecs/systems/test_level_serialization_system.cpp +++ b/tests/thr/ecs/systems/test_level_serialization_system.cpp @@ -107,14 +107,7 @@ int main() { auto prev = thr::ecs::clock::now(); while (window.isOpen()) { // handle event - while (const std::optional event = window.pollEvent()) { - if (!manager.handle_event(*event)) { - if (event->is()) { - spdlog::info("quit the window"); - window.close(); - } - } - } + while (const std::optional event = window.pollEvent()) { manager.handle_event(*event); } // update auto cur = thr::ecs::clock::now(); diff --git a/typings/entity.d.lua b/typings/entity.d.lua new file mode 100644 index 0000000..2d30303 --- /dev/null +++ b/typings/entity.d.lua @@ -0,0 +1,32 @@ +---@meta + +---实体类。 +---@class Entity +Entity = {} + +---判断实体是否合法。 +---@param self Entity 实体类。 +---@return boolean # 实体是否合法。 +function Entity:valid() end + +---销毁实体。 +---@param self Entity 实体类。 +function Entity:destroy() end + +---为实体添加标签。 +---@param self Entity 实体类。 +---@param tag_id integer 标签编号。 +---@return boolean # 添加是否成功。 +function Entity:add_tag(tag_id) end + +---从实体移除标签。 +---@param self Entity 实体类。 +---@param tag_id integer 标签编号。 +---@return boolean # 移除是否成功。 +function Entity:remove_tag(tag_id) end + +---查询实体是否拥有指定标签。 +---@param self Entity 实体类。 +---@param tag_id integer 标签编号。 +---@return boolean # 是否拥有。 +function Entity:query_tag(tag_id) end diff --git a/typings/game.d.lua b/typings/game.d.lua new file mode 100644 index 0000000..d8629ad --- /dev/null +++ b/typings/game.d.lua @@ -0,0 +1,29 @@ +---@meta + +---@class Game -- 游戏的 API 类。 +Game = {} + +---创建实体。 +---@return Entity # 创建的实体。 +function Game:create_entity() end + +---查询所有拥有指定标签的实体。 +---@param tag_id integer 标签编号。 +---@return Entity[] # 拥有指定标签的实体列表。 +function Game:query_entities_with_tag(tag_id) end + +---创建实体并为其添加文本。 +---@param text string 文本内容。 +---@param position Vector2 位置。 +---@return Entity # 创建的文本实体。 +function Game:add_text(text, position) end + +---创建实体并为其添加文本。 +---@param text string 文本内容。 +---@param position Vector2 位置。 +---@param character_size integer 字号。 +---@return Entity # 创建的文本实体。 +function Game:add_text(text, position, character_size) end + +---@type Game +game = {} diff --git a/typings/logger.d.lua b/typings/logger.d.lua new file mode 100644 index 0000000..e51e1ac --- /dev/null +++ b/typings/logger.d.lua @@ -0,0 +1,23 @@ +---@meta + +---日志类。 +---@class Logger +Logger = {} + +---@param ... any 要打印的参数。 +function Logger.trace(...) end + +---@param ... any 要打印的参数。 +function Logger.debug(...) end + +---@param ... any 要打印的参数。 +function Logger.info(...) end + +---@param ... any 要打印的参数。 +function Logger.warn(...) end + +---@param ... any 要打印的参数。 +function Logger.error(...) end + +---@param ... any 要打印的参数。 +function Logger.critical(...) end diff --git a/typings/vector2.d.lua b/typings/vector2.d.lua new file mode 100644 index 0000000..33ea060 --- /dev/null +++ b/typings/vector2.d.lua @@ -0,0 +1,31 @@ +---@meta + +---2D 向量类,多用来表示位置。 +---@class Vector2 +---x 坐标。 +---@field x number +---y 坐标。 +---@field y number +---加法运算。 +---@operator add(Vector2):Vector2 +---减法运算。 +---@operator sub(Vector2):Vector2 +---乘法运算。 +---@operator mul(number):Vector2 +---@operator mul(number):Vector2 +---除法运算。 +---@operator div(number):Vector2 +---@operator div(number):unknown +---一元减法。 +---@operator unm:Vector2 +Vector2 = {} + +---默认构造函数。 +---@return Vector2 # 返回一个新的 Vector2 对象,值为 `(0, 0)`。 +function Vector2.new() end + +---构造函数。 +---@param x number x 坐标。 +---@param y number y 坐标。 +---@return Vector2 # 返回一个新的 Vector2 对象。 +function Vector2.new(x, y) end