diff --git a/CMakeLists.txt b/CMakeLists.txt index b778305..61c1aed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,96 +6,50 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) option(USE_FRAMEBUFFER "Use Linux framebuffer instead of SDL2" OFF) -<<<<<<< HEAD -set(ENGINE_SOURCES - src/Asset/ObjLoader.cpp - src/Asset/SpriteAssetLoader.cpp - src/Core/DepthBuffer.cpp - src/Core/FrameBuffer.cpp - src/Core/Renderer.cpp - game/src/app/TomGameApp.cpp - game/src/components/SpriteAnimator.cpp - game/src/audio/VoiceEffect.cpp - game/src/audio/VoicePlayer.cpp - game/src/audio/VoiceRecorder.cpp - game/src/hardware/AudioInput.cpp - game/src/hardware/AudioOutput.cpp - game/src/hardware/ButtonInput.cpp - game/src/systems/AnimationSystem.cpp - src/Rasterizer/Rasterizer.cpp - src/Rasterizer/SpriteRasterizer.cpp - src/Rasterizer/TriangleRasterizer.cpp - src/Scene/Camera.cpp - src/Shading/BlinnPhongShader.cpp -======= -set(SOURCES - src/Apps/Demo/main.cpp - src/Gfx/Asset/ObjLoader.cpp - src/Gfx/Core/DepthBuffer.cpp - src/Gfx/Core/FrameBuffer.cpp - src/Gfx/Core/Renderer.cpp - src/Gfx/Draw2D/DrawContext.cpp - src/Gfx/Rasterizer/Rasterizer.cpp - src/Gfx/Rasterizer/TriangleRasterizer.cpp - src/Gfx/Scene/Camera.cpp - src/Gfx/Shading/BlinnPhongShader.cpp ->>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5 +set(CORE_SOURCES + src/Core/Asset/ObjLoader.cpp + src/Core/Asset/SpriteAssetLoader.cpp + src/Core/Core/DepthBuffer.cpp + src/Core/Core/FrameBuffer.cpp + src/Core/Core/Renderer.cpp + src/Core/Draw2D/DrawContext.cpp + src/Core/Rasterizer/Rasterizer.cpp + src/Core/Rasterizer/TriangleRasterizer.cpp + src/Core/Scene/Camera.cpp + src/Core/Shading/BlinnPhongShader.cpp ) -set(ENGINE_INCLUDE_DIRS - src/Platform - src/Asset - src/Core - src/Math - src/Rasterizer - src/RenderData - src/Scene - src/Shading - game/src/app - game/src/audio - game/src/components - game/src/hardware - game/src/systems +set(CORE_INCLUDE_DIRS + src/Core/Platform + src/Core/Asset + src/Core/Core + src/Core/Draw2D + src/Core/Math + src/Core/Rasterizer + src/Core/RenderData + src/Core/Scene + src/Core/Shading + assets/font + assets/sprite ) set(SOURCES - src/main.cpp - ${ENGINE_SOURCES} + src/Apps/Game/Main.cpp + ${CORE_SOURCES} ) if(USE_FRAMEBUFFER) - list(APPEND SOURCES src/Gfx/Platform/FBDisplay.cpp) + list(APPEND SOURCES src/Core/Platform/FBDisplay.cpp) else() - list(APPEND SOURCES src/Gfx/Platform/SDLDisplay.cpp) + list(APPEND SOURCES src/Core/Platform/SDLDisplay.cpp) endif() add_executable(IMX6U-Game ${SOURCES}) - -<<<<<<< HEAD -target_include_directories(IMX6U-Game PRIVATE ${ENGINE_INCLUDE_DIRS}) -======= -target_include_directories(IMX6U-Game PRIVATE - src/Gfx/Platform - src/Gfx/Asset - src/Gfx/Core - src/Gfx/Draw2D - src/Gfx/Math - src/Gfx/Rasterizer - src/Gfx/RenderData - src/Gfx/Scene - src/Gfx/Shading - assets/font - assets/sprite -) ->>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5 +target_include_directories(IMX6U-Game PRIVATE ${CORE_INCLUDE_DIRS}) if(USE_FRAMEBUFFER) target_compile_definitions(IMX6U-Game PRIVATE USE_FRAMEBUFFER) else() - target_include_directories(IMX6U-Game PRIVATE - libs/Win/SDL2/include - ) - if(WIN32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(SDL2_LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libs/Win/SDL2/lib/x64") @@ -109,6 +63,7 @@ else() set(SDL2_IMAGE_DLL "${CMAKE_CURRENT_SOURCE_DIR}/libs/Win/SDL_image/lib/x86/SDL2_image.dll") endif() + target_include_directories(IMX6U-Game PRIVATE libs/Win/SDL2/include) target_link_directories(IMX6U-Game PRIVATE ${SDL2_LIB_DIR}) target_link_libraries(IMX6U-Game PRIVATE SDL2main SDL2) @@ -119,14 +74,9 @@ else() ) else() find_package(SDL2 REQUIRED) + find_package(SDL2_image QUIET) target_link_libraries(IMX6U-Game PRIVATE SDL2::SDL2) endif() - - find_package(SDL2_image QUIET) -endif() - -if(UNIX) - target_link_libraries(IMX6U-Game PRIVATE asound) endif() if(MSVC) @@ -135,46 +85,16 @@ if(MSVC) endif() if(NOT USE_FRAMEBUFFER) - set(SPRITE_ANIMATION_TEST_SOURCES - game/tests/manual/SpriteAnimationTest.cpp - ${ENGINE_SOURCES} - src/Platform/SDLDisplay.cpp - ) - - if(WIN32) - add_executable(SpriteAnimationTest ${SPRITE_ANIMATION_TEST_SOURCES}) - target_include_directories(SpriteAnimationTest PRIVATE - ${ENGINE_INCLUDE_DIRS} - libs/Win/SDL2/include - ) - target_link_directories(SpriteAnimationTest PRIVATE ${SDL2_LIB_DIR}) - target_link_libraries(SpriteAnimationTest PRIVATE SDL2main SDL2) - - add_custom_command(TARGET SpriteAnimationTest POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${SDL2_DLL}" - "$" - ) - else() - add_executable(SpriteAnimationTest ${SPRITE_ANIMATION_TEST_SOURCES}) - target_include_directories(SpriteAnimationTest PRIVATE ${ENGINE_INCLUDE_DIRS}) - target_link_libraries(SpriteAnimationTest PRIVATE SDL2::SDL2 asound) - endif() - - if(MSVC AND TARGET SpriteAnimationTest) - target_compile_options(SpriteAnimationTest PRIVATE /utf-8 /W3) - endif() - set(SPRITE_ASSET_TOOL_SOURCES - game/tools/asset_pipeline/SpriteAssetTool.cpp - src/Asset/SpriteAssetLoader.cpp + src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp + src/Core/Asset/SpriteAssetLoader.cpp ) if(WIN32) add_executable(SpriteAssetTool ${SPRITE_ASSET_TOOL_SOURCES}) target_include_directories(SpriteAssetTool PRIVATE - src/Asset - src/RenderData + src/Core/Asset + src/Core/RenderData libs/Win/SDL2/include libs/Win/SDL_image/include ) @@ -194,8 +114,8 @@ if(NOT USE_FRAMEBUFFER) elseif(SDL2_image_FOUND) add_executable(SpriteAssetTool ${SPRITE_ASSET_TOOL_SOURCES}) target_include_directories(SpriteAssetTool PRIVATE - src/Asset - src/RenderData + src/Core/Asset + src/Core/RenderData ) target_link_libraries(SpriteAssetTool PRIVATE SDL2::SDL2 SDL2_image::SDL2_image) else() @@ -206,9 +126,10 @@ if(NOT USE_FRAMEBUFFER) add_custom_target(ConvertTomSprites COMMAND $ --batch - "${CMAKE_CURRENT_SOURCE_DIR}/game/assets/raw" - "${CMAKE_CURRENT_SOURCE_DIR}/game/assets/sprites" + "src/Apps/Game/assets/raw" + "src/Apps/Game/assets/sprites" --preset tom-800x480 + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS SpriteAssetTool COMMENT "Converting PNG assets to board-ready .sprite files" VERBATIM diff --git a/README.md b/README.md index a77f172..2b79f7b 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,13 @@ IMX6U 运行时性能预算较紧,后续开发必须遵守 `docs/DEVELOPMENT_G ## 应用层与图形库拆分 -项目后续按“两个游戏 + 一个启动器 + 一套底层图形库”组织,详细边界见 `docs/APP_AND_GFX_ARCHITECTURE.md`: +项目后续按“两个游戏 + 一个启动器 + 一套底层图形库”组织,详细边界见 `docs/APP_AND_CORE_ARCHITECTURE.md`: -- `Gfx` / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、独立时间源、基础绘制能力,例如点、线、矩形、四边形、sprite、bitmap font、tilemap。 +- `Core` / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、独立时间源、基础绘制能力,例如点、线、矩形、四边形、sprite、bitmap font、tilemap。 - `Apps/Launcher`:负责游戏选择、全局设置和启动流程。 - `Apps/GameA`、`Apps/GameB`:分别维护自己的游戏规则、状态、资源和渲染调用。 - `Shared`:只放应用层共享但不属于底层图形库的 UI、存档、配置、资源索引等。 -- 依赖方向必须保持 `Apps -> Shared -> Gfx -> Platform`,底层图形库不能反向依赖具体游戏。 +- 依赖方向必须保持 `Apps -> Shared -> Core -> Platform`,底层图形库不能反向依赖具体游戏。 初期推荐单进程多应用模式:Launcher、GameA、GameB 共用同一个 SDL2 初始化、framebuffer 和主循环,通过统一 `IApp` 接口切换当前应用。 @@ -194,7 +194,7 @@ assets/font/font_atlas.h ## 显示后端架构 -显示层通过抽象接口 `Platform::IDisplay` 与渲染逻辑解耦。后续应用层和图形库拆分后,`Platform` 会收敛到 `Gfx` 的平台适配层: +显示层通过抽象接口 `Platform::IDisplay` 与渲染逻辑解耦。后续应用层和图形库拆分后,`Platform` 会收敛到 `Core` 的平台适配层: ``` ┌──────────────────────────────────────────────┐ @@ -202,7 +202,7 @@ assets/font/font_atlas.h ├──────────────────────────────────────────────┤ │ Shared:UI、存档、配置、资源索引 │ ├──────────────────────────────────────────────┤ -│ Gfx:DrawContext、FrameBuffer、Draw2D、Math │ +│ Core:DrawContext、FrameBuffer、Draw2D、Math │ ├──────────────────────────────────────────────┤ │ Platform::IDisplay │ │ - SDLDisplay : SDL2 显示/输入适配 │ @@ -230,7 +230,7 @@ IMX6U-Game/ │ ├─ gen_font_atlas.py # TTF -> bitmap font atlas/header │ └─ png_to_header.py # PNG -> uint32_t RGBA header ├─ src/ -│ ├─ Gfx/ # 底层图形库:可复用、无具体游戏规则 +│ ├─ Core/ # 底层图形库:可复用、无具体游戏规则 │ │ ├─ Draw2D/ # DrawContext 统一绘制入口 │ │ ├─ Core/ # FrameBuffer、DepthBuffer │ │ ├─ Math/ # 向量、矩阵、数学工具 @@ -245,7 +245,7 @@ IMX6U-Game/ │ └─ test_fb.cpp # 独立 fb 测试(最小示例) ├─ docs/ │ ├─ DEVELOPMENT_GUIDELINES.md # IMX6U 性能红线 -│ ├─ APP_AND_GFX_ARCHITECTURE.md # 应用层与图形库分层 +│ ├─ APP_AND_CORE_ARCHITECTURE.md # 应用层与图形库分层 │ └─ CONVENTIONS.md # 坐标系、矩阵、深度等数学约定 ├─ CMakeLists.txt └─ README.md @@ -284,8 +284,8 @@ IMX6U-Game/ - 双平台显示后端(SDL2 / Framebuffer) - 离线资源转换工具:PNG sprite -> C++ 头文件,像素字体 -> bitmap atlas/header - 基础 2D sprite、SpriteRegion、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap -- Gfx 目录规范化,代码收敛到 `src/Gfx/` -- `Gfx::DrawContext` 统一绘制入口,封装现有绘制能力 +- Core 目录规范化,代码收敛到 `src/Core/` +- `Core::DrawContext` 统一绘制入口,封装现有绘制能力 - C++11 兼容代码 - CMake 跨平台构建 @@ -293,7 +293,7 @@ IMX6U-Game/ 1. FrameBuffer 性能优化(`memset` 清屏、去掉 `at()`、定点数/NEON) 2. 应用层拆分(Launcher / GameA / GameB / Shared)和统一 `IApp` 主循环 3. SDL2 输入抽象(键盘/触摸/按键状态快照) -4. Gfx 基础 2D 绘制接口(矩形、四边形、继续完善 Sprite/Text/Tilemap 的批处理和专用快路径) +4. Core 基础 2D 绘制接口(矩形、四边形、继续完善 Sprite/Text/Tilemap 的批处理和专用快路径) 5. 纹理贴图、OBJ 模型加载与完整光照 ## 说明 diff --git a/docs/APP_AND_CORE_ARCHITECTURE.md b/docs/APP_AND_CORE_ARCHITECTURE.md new file mode 100644 index 0000000..017a615 --- /dev/null +++ b/docs/APP_AND_CORE_ARCHITECTURE.md @@ -0,0 +1,135 @@ +# 应用层与 Core 分层设计 + +本文记录当前项目的代码分层。`src/Core` 是可复用底层库,`src/Apps` 放具体应用和游戏。 + +## 目录边界 + +```text +src/ + Core/ # 底层库:渲染、数学、资源、平台适配 + Asset/ # 离线资源格式加载 + Core/ # FrameBuffer、DepthBuffer、Renderer、Timer + Draw2D/ # Core::DrawContext + Math/ # Vector、Matrix、MathUtil + Platform/ # IDisplay、SDLDisplay、FBDisplay、ITimeSource + Rasterizer/ # 线段和三角形光栅化 + RenderData/ # Color、Image、Triangle、Tilemap 等数据结构 + Scene/ # Camera、Transform、Mesh、Model + Shading/ # Shader 相关代码 + Apps/ + Demo/ # Core 能力演示 + Game/ # Tom 游戏 +``` + +`src/Core/Core` 这个二级目录保留的是原底层库里的核心运行时对象。它和顶层 `src/Core` 名称重复,但含义不同: + +- `src/Core`:整个底层库。 +- `src/Core/Core`:底层库内部的 framebuffer、depthbuffer、timer 等核心对象。 + +后续如果觉得重复命名影响阅读,可以再把 `src/Core/Core` 单独改成 `Runtime/` 或 `Buffer/`,但这次先只做旧底层库名称到 `Core` 的一致性修复。 + +## 依赖方向 + +允许: + +```text +Apps -> Shared -> Core -> Platform +Apps -> Core +``` + +禁止: + +```text +Core -> Apps +Core -> Shared +GameA -> GameB +GameB -> GameA +Platform -> Game +``` + +Core 层不应该知道具体游戏规则、场景流程、角色状态机、关卡数据或游戏专属资源路径。 + +## Core 职责 + +Core 只提供底层能力: + +- 管理 `FrameBuffer`、`DepthBuffer` 和绘制上下文。 +- 提供基础绘制接口:line、triangle、sprite、sprite region、tilemap、bitmap font。 +- 提供基础数学、颜色、图片、三角形、tilemap 等数据结构。 +- 封装 SDL2 / framebuffer 显示提交。 +- 提供独立时间源 `Platform::ITimeSource`。 +- 使用离线转换后的运行时资源,不在热路径解码 PNG/TTF。 + +Core 不做: + +- 不实现具体游戏规则。 +- 不直接读取某个游戏专属资源目录。 +- 不在核心绘制接口暴露 SDL2 类型。 +- 不在每帧热路径中执行图片、字体解码或文件 IO。 + +## 应用职责 + +`src/Apps/*` 负责具体应用流程: + +- 创建具体游戏或 Demo 的主循环。 +- 加载应用自己的资源。 +- 调用 `Core::DrawContext` 绘制画面。 +- 根据平台输入更新游戏状态。 + +当前主程序位于: + +```text +src/Apps/Game/Main.cpp +``` + +它默认启动 Tom 游戏视觉入口。 + +## DrawContext + +`Core::DrawContext` 是当前统一绘制入口,位于: + +```text +src/Core/Draw2D/DrawContext.h +src/Core/Draw2D/DrawContext.cpp +``` + +它封装: + +- `Core::FrameBuffer` +- `Core::DepthBuffer` +- `Rasterizer::Rasterizer` +- `Rasterizer::TriangleRasterizer` + +对外提供: + +```cpp +Core::DrawContext ctx(width, height); +ctx.clear(RenderData::Color(18, 18, 24, 255)); +ctx.draw_sprite(x, y, image); +ctx.draw_text(font, x, y, color, "text"); +ctx.present(display); +``` + +## 显示后端 + +显示层通过 `Platform::IDisplay` 抽象: + +```text +Platform::IDisplay + SDLDisplay # PC / SDL2 调试后端 + FBDisplay # Linux /dev/fb0 后端 +``` + +CMake 通过 `USE_FRAMEBUFFER` 选择实现: + +```cmake +-DUSE_FRAMEBUFFER=OFF # 默认,使用 SDLDisplay +-DUSE_FRAMEBUFFER=ON # 使用 FBDisplay +``` + +## 后续建议 + +1. 保持 `Core` 不依赖 `Apps`。 +2. 新增游戏逻辑放在 `src/Apps/Game` 或新的 `src/Apps/*`。 +3. 新增底层绘制、数学、资源格式、平台显示能力放在 `src/Core`。 +4. 如果继续清理命名,优先处理 `src/Core/Core` 这个重复目录名。 diff --git a/docs/APP_AND_GFX_ARCHITECTURE.md b/docs/APP_AND_GFX_ARCHITECTURE.md deleted file mode 100644 index 24f5976..0000000 --- a/docs/APP_AND_GFX_ARCHITECTURE.md +++ /dev/null @@ -1,259 +0,0 @@ -# 应用层与图形库分层设计 - -本项目后续包含三个应用层目标:两个游戏和一个启动器;同时还需要沉淀一套可复用的 IMX6U 轻量图形库。为了避免后期耦合和性能返工,必须明确区分“应用层”和“底层库”。 - -## 1. 推荐总体结构 - -推荐拆成四个逻辑层: - -```text -IMX6U-Game/ -├─ src/ -│ ├─ Gfx/ # 底层图形库:可复用、无具体游戏规则 -│ │ ├─ Draw2D/ # DrawContext 统一绘制入口(✅ 已实现) -│ │ ├─ Core/ # FrameBuffer、DepthBuffer(✅ 已实现) -│ │ ├─ Math/ # 向量、矩阵、数学工具(✅ 已实现) -│ │ ├─ Rasterizer/ # 线段、三角形光栅化(✅ 已实现) -│ │ ├─ RenderData/ # Color、Triangle 等数据结构(✅ 已实现) -│ │ ├─ Scene/ # Camera、Transform(✅ 已实现) -│ │ ├─ Shading/ # 着色器(预留) -│ │ ├─ Platform/ # SDL2 / fb0 显示适配与独立时间源(✅ 已实现) -│ │ └─ Asset/ # 资源加载(✅ 已实现) -│ ├─ Apps/ -│ │ ├─ Demo/ # 当前 3D 立方体 demo(✅ 已实现) -│ │ ├─ Launcher/ # 启动器应用(待实现) -│ │ ├─ GameA/ # 第一个游戏(待实现) -│ │ └─ GameB/ # 第二个游戏(待实现) -│ └─ Shared/ # 可选:应用层共享但不属于 Gfx 的东西 -│ ├─ Save/ # 存档格式、配置读写 -│ ├─ UI/ # 启动器和游戏共用 UI 组件 -│ └─ Assets/ # 应用层资源索引、资源命名约定 -├─ assets/ -│ ├─ font/ # 共享像素字体源文件与生成的 font_atlas -│ ├─ sprite/ # 当前 demo/test sprite 源文件与生成头文件 -│ ├─ launcher/ -│ ├─ game_a/ -│ ├─ game_b/ -│ └─ shared/ -├─ tools/ -│ ├─ gen_font_atlas.py # TTF -> bitmap font atlas/header -│ └─ png_to_header.py # PNG -> uint32_t RGBA header -└─ docs/ -``` - -~~当前项目已有 `Core/Math/Platform/Rasterizer/RenderData/Scene` 等目录,短期不必一次性搬迁;但新增代码应按上面的边界收敛。等功能稳定后,再把现有底层代码整体移动到 `src/Gfx/`。~~ - -**已完成**:底层代码已整体迁移到 `src/Gfx/`,包括 Core、Math、Rasterizer、RenderData、Scene、Shading、Platform、Asset 和新增的 Draw2D。Demo 入口位于 `src/Apps/Demo/`。 - -## 2. 四个层级的职责 - -### 2.1 Gfx:底层图形库 - -职责: - -- 管理 framebuffer、depthbuffer、渲染上下文。 -- 提供基础绘制接口:点、线、矩形、四边形、三角形、sprite、SpriteRegion、tilemap、简单文本等。 -- 提供颜色、矩形、定点数、纹理、裁剪区域等基础数据结构。 -- 封装 SDL2 / framebuffer 显示提交、输入轮询,并通过独立 `ITimeSource` 提供单调整数毫秒时间。 -- 使用离线转换后的紧凑资源数据(例如 PNG 转头文件、bitmap font atlas),运行时不直接依赖 PNG/TTF 解码。 -- 只关心“怎么画得快、怎么提交到屏幕”,不关心具体游戏规则。 - -禁止: - -- 不依赖 `GameA`、`GameB`、`Launcher`。 -- 不包含具体关卡、角色、菜单流程、游戏状态机。 -- 不直接读取某个游戏专属资源路径。 -- 不在核心绘制接口中暴露 SDL2 类型。 -- 不在每帧热路径中执行图片/字体解码;资源转换应在构建前或工具阶段完成。 - -### 2.2 Apps/GameA 与 Apps/GameB:两个游戏 - -职责: - -- 各自维护自己的游戏规则、状态机、关卡、实体、碰撞、计分、胜负逻辑。 -- 通过 Gfx 提供的接口绘制画面。 -- 通过平台输入快照读取按键/触摸状态,而不是直接调用 SDL。 -- 管理自己的资源索引和场景切换。 - -禁止: - -- 游戏之间互相 include 对方代码。 -- 游戏直接操作 SDL renderer / texture / window。 -- 游戏直接依赖 framebuffer 内存布局,除非是明确标注的性能特例。 - -### 2.3 Apps/Launcher:启动器 - -职责: - -- 展示游戏列表、图标、说明、版本信息。 -- 选择并启动 GameA 或 GameB。 -- 管理全局设置,例如音量、亮度、输入校准、语言等。 -- 可以复用 Shared/UI,但不承载具体游戏逻辑。 - -启动方式有两种可选方案: - -1. **单进程多应用模式**:Launcher、GameA、GameB 编译成一个可执行文件,内部切换当前 App。 -2. **多进程模式**:Launcher 是独立程序,选择后启动另一个游戏可执行文件。 - -对 IMX6U 初期开发,推荐先用 **单进程多应用模式**,原因: - -- 构建、部署、调试更简单。 -- SDL2 初始化、资源缓存、输入状态可以共用。 -- 避免频繁退出/启动进程带来的黑屏、资源重载和状态恢复问题。 - -等项目成熟后,如果每个游戏体积较大,或者需要独立更新,再考虑多进程拆分。 - -### 2.4 Shared:应用层共享模块 - -Shared 只放“应用层共享,但不属于底层图形库”的内容,例如: - -- UI 控件:按钮、列表、九宫格面板、菜单焦点管理。 -- 存档/设置:配置文件、最高分、解锁状态。 -- 资源清单:多个应用共用的字体、图标、音效索引。 - -Shared 可以依赖 Gfx;Gfx 不能依赖 Shared。 - -## 3. 依赖方向 - -必须保持单向依赖: - -```text -Apps/GameA ─┐ -Apps/GameB ─┼─> Shared ─> Gfx ─> Platform(SDL2/fb0/ITimeSource) -Launcher ─┘ - -Apps/GameA ─┐ -Apps/GameB ─┼──────────> Gfx -Launcher ─┘ -``` - -禁止反向依赖: - -```text -Gfx -> Apps 禁止 -Gfx -> Shared 禁止 -GameA -> GameB 禁止 -GameB -> GameA 禁止 -Platform -> Game 禁止 -``` - -## 4. 应用统一接口 - -推荐为 Launcher、GameA、GameB 提供统一应用接口,例如: - -```cpp -class IApp -{ -public: - virtual ~IApp() {} - virtual void on_enter() = 0; - virtual void on_exit() = 0; - virtual void update(uint32_t fixed_delta_ms) = 0; - virtual void render(Gfx::DrawContext& ctx) = 0; - virtual AppId next_app() const = 0; -}; -``` - -主循环只认识 `IApp`: - -```text -poll input -> update current app -> render current app -> present framebuffer -``` - -这样三个应用层共用同一个主循环、同一套 SDL2 初始化、framebuffer 提交流程和独立时间源。 - -注意:接口可以先保留虚函数,因为它只在每帧应用级调用,不在像素/顶点热路径中调用。像 `draw_rect`、`draw_quad`、`set_pixel_fast` 这类热路径函数不要虚化。 - -## 5. 底层图形库优先提供的 API - -Gfx 初期建议先做 2D 基础能力,不要一开始就做复杂引擎: - -```cpp -namespace Gfx -{ - struct RectI { int32_t x, y, w, h; }; - struct PointI { int32_t x, y; }; - struct Color32 { uint32_t rgba; }; - - class DrawContext - { - public: - void clear(Color32 color); - void draw_pixel(int32_t x, int32_t y, Color32 color); - void draw_line(PointI a, PointI b, Color32 color); - void draw_rect(RectI rect, Color32 color); - void fill_rect(RectI rect, Color32 color); - void draw_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color); - void fill_quad(PointI p0, PointI p1, PointI p2, PointI p3, Color32 color); - void draw_sprite(int32_t x, int32_t y, const RenderData::Image& image); - void draw_sprite_region(int32_t x, int32_t y, - const RenderData::SpriteRegion& region); - void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y, - RenderData::Color color, const char* text); - void draw_tilemap(const RenderData::Tilemap& tilemap, - int32_t screen_x, int32_t screen_y, - int32_t viewport_w, int32_t viewport_h, - int32_t camera_x, int32_t camera_y); - }; -} -``` - -后续再扩展: - -- `set_clip_rect` -- `set_camera_2d` -- `batch sprite/tile` - -## 6. 命名建议 - -为了避免“项目名、库名、游戏名”混乱,建议: - -- 项目仓库仍叫 `IMX6U-Game`。 -- 底层图形库命名为 `Gfx` 或 `MiniGfx`。 -- 三个应用用明确名字:`Launcher`、`GameA`、`GameB`,后续再替换成真实游戏名。 -- CMake target 可以是: - - `imx6u_gfx` 静态库 - - `imx6u_launcher` - - `imx6u_game_a` - - `imx6u_game_b` - - 或初期单可执行文件 `imx6u_suite` - -## 7. 推荐演进顺序 - -1. ~~先抽出统一 `IApp` 和 `AppManager`,让当前 demo 成为一个 app。~~ -2. ~~把 SDL2 初始化、输入、present 固定在平台层,应用层不直接碰 SDL。~~ -3. ~~建立 `Gfx::DrawContext`,先封装 clear、pixel、line、rect、quad。~~ **已完成**(`Gfx::DrawContext` 封装了 clear、draw_line、draw_triangle、draw_sprite、draw_sprite_region、draw_text、draw_tilemap、present) -4. ~~底层代码迁移到 `src/Gfx/`,Demo 入口迁移到 `src/Apps/Demo/`。~~ **已完成** -5. 新增 Launcher app,只做最小菜单和应用切换。 -6. 新增 GameA/GameB 空壳,验证三应用切换。 -7. 再逐步把现有 3D demo 或 2D 游戏逻辑迁入对应 Game 目录。 -8. 最后重构 CMake,按 `imx6u_gfx` + 应用 target 拆分。 - -## 8. 性能注意事项 - -- 应用切换不应重复销毁/创建 SDL window、renderer、texture。 -- 三个应用共用 framebuffer、输入状态和 `Platform::ITimeSource` 时间源;Display 不承担计时职责。 -- 每个应用可以有自己的资源缓存,但必须有上限和释放策略。 -- Launcher 不应常驻消耗大量纹理/音频资源;进入游戏后可释放非必要启动器资源。 -- Gfx 的绘制函数要保持小而直接,优先内联和连续内存写入。 -- UI 控件层可以面向对象;像素/quad/sprite/tile 绘制层不要过度抽象。 - -## 9. 资源转换约定 - -- 运行时资源优先使用离线转换后的简单数组,不在 IMX6U 运行时解码 PNG/TTF。 -- `tools/png_to_header.py` 将 PNG 转为 `uint32_t` RGBA 数组,适用于 sprite、小图标、测试纹理等。 -- `tools/gen_font_atlas.py` 将共享像素字体转为 ASCII bitmap atlas,并输出同名 PNG 预览和 C++ 头文件。 -- 生成头文件、源 PNG/TTF 和转换脚本应一起纳入仓库,保证资源可追溯、可再生成。 -- 生成数据目前面向简单直接的调试/小型游戏资源;后续如果资源体积增长,应再评估 1-bit/8-bit mask、RLE 或自定义资源包格式。 - -## 10. SpriteRegion 与 Tilemap 约定 - -- `RenderData::SpriteRegion` 只描述某张 atlas 中的子区域,不拥有像素数据;它通过 `const Image* atlas` 引用源图。 -- `DrawContext::draw_sprite_ex` 是底层 sprite 绘制入口,负责源区域检查、目标屏幕裁剪、scale 和 flip;`draw_sprite_region` 系列只是对 atlas 子区域的语义包装。 -- `RenderData::Tilemap` 使用 `uint16_t` tile id 保存地图网格,`Tilemap::EmptyTile` (`0xFFFF`) 表示空 tile。 -- `Tilemap` 当前只支持一个 atlas、固定 tile 宽高和固定 `atlas_columns`;tile id 通过 `tile_id % atlas_columns` / `tile_id / atlas_columns` 映射到 atlas 中的源区域。 -- `DrawContext::draw_tilemap` 的裁剪分两层: - - tilemap 层按 tile 计算需要尝试绘制的可见范围; - - sprite 层按像素裁剪每个 tile,支持 camera 像素级滚动时显示半个 tile。 -- 带 `viewport_w` / `viewport_h` 的 `draw_tilemap` 重载用于子视口绘制,`screen_x` / `screen_y` 是视口左上角;旧重载默认视口延伸到 framebuffer 右下角。 -- 当前实现优先保证清晰语义和可验证行为;后续性能优化可增加 tile region 查表、不透明 tile 行拷贝、chunk/dirty rect 或专用 tile 快路径。 diff --git a/docs/CONVENTIONS.md b/docs/CONVENTIONS.md index 670769b..819bcde 100644 --- a/docs/CONVENTIONS.md +++ b/docs/CONVENTIONS.md @@ -2,7 +2,7 @@ 本文档用于记录当前项目已经采用的坐标系、矩阵、相机和屏幕空间约定,避免后续开发时在符号、方向和乘法顺序上产生混乱。 -> 文档分工:坐标、矩阵、深度等数学语义记录在本文档;IMX6U 运行时性能红线记录在 `../docs/DEVELOPMENT_GUIDELINES.md`;两个游戏、启动器和底层图形库的分层边界记录在 `../docs/APP_AND_GFX_ARCHITECTURE.md`。新增或修改热路径、应用层边界、显示后端代码时,应同步参考对应文档。 +> 文档分工:坐标、矩阵、深度等数学语义记录在本文档;IMX6U 运行时性能红线记录在 `../docs/DEVELOPMENT_GUIDELINES.md`;两个游戏、启动器和底层图形库的分层边界记录在 `../docs/APP_AND_CORE_ARCHITECTURE.md`。新增或修改热路径、应用层边界、显示后端代码时,应同步参考对应文档。 ## 1. 通用约定 diff --git a/docs/DEVELOPMENT_GUIDELINES.md b/docs/DEVELOPMENT_GUIDELINES.md index 350f1ee..bb1fd7c 100644 --- a/docs/DEVELOPMENT_GUIDELINES.md +++ b/docs/DEVELOPMENT_GUIDELINES.md @@ -9,7 +9,7 @@ - **先按 IMX6U 运行时成本设计,再按 PC 调试便利包装。** Windows/Linux SDL 版本只是验证与调试入口,不代表最终性能预算。 - **热路径默认禁止隐式高成本操作。** 每帧、每对象、每顶点、每像素级代码必须避免动态分配、浮点、虚函数链、复杂 STL 算法和异常控制流。 - **核心逻辑保持 C++11 兼容。** 不引入需要新工具链或重型运行时支持的语言/库特性。 -- **优先复用已有类型与缓冲。** 新增抽象前先确认 `src/Gfx/Core`、`src/Gfx/Math`、`src/Gfx/RenderData` 中是否已有可复用能力。 +- **优先复用已有类型与缓冲。** 新增抽象前先确认 `src/Core/Core`、`src/Core/Math`、`src/Core/RenderData` 中是否已有可复用能力。 - **性能相关例外必须写明边界。** 如果必须违反本文红线,需要在代码附近注释说明原因、调用频率、数据规模和替代方案。 ## 2. 数值计算规范 @@ -143,7 +143,7 @@ 后续如果继续推进性能优化,优先建立这些基础设施: -1. 统一定点数类型与转换工具,集中放在 `src/Gfx/Math/`。 +1. 统一定点数类型与转换工具,集中放在 `src/Core/Math/`。 2. 帧级临时缓冲/工作区,集中管理可复用数组和 scratch memory。 3. 渲染数据的运行时紧凑格式,区分“加载期模型数据”和“运行时渲染数据”。 4. ARM release 配置下的性能开关,关闭日志、调试绘制和昂贵检查。 @@ -163,8 +163,8 @@ ### 12.1 SDL2 边界 -- SDL2 类型和调用只允许出现在 `src/Gfx/Platform/` 以及明确的平台适配层中。 -- `src/Gfx/Core`、`src/Gfx/Math`、`src/Gfx/Rasterizer`、`src/Gfx/RenderData`、`src/Gfx/Scene`、`src/Gfx/Draw2D` 不应直接包含 `SDL.h`。 +- SDL2 类型和调用只允许出现在 `src/Core/Platform/` 以及明确的平台适配层中。 +- `src/Core/Core`、`src/Core/Math`、`src/Core/Rasterizer`、`src/Core/RenderData`、`src/Core/Scene`、`src/Core/Draw2D` 不应直接包含 `SDL.h`。 - 游戏逻辑不直接处理 `SDL_Event`,应转换为项目自己的输入状态结构。 - 时间源不挂在 `IDisplay` 上;核心逻辑从 `Platform::ITimeSource` 读取单调整数毫秒,并使用 `Core::Timer` 生成整数 tick / fixed timestep,不直接依赖 float 秒数。 diff --git a/game/README.md b/game/README.md deleted file mode 100644 index 1d909cb..0000000 --- a/game/README.md +++ /dev/null @@ -1,116 +0,0 @@ -# 游戏目录结构 - -这个目录用于编写新的游戏。仓库根目录下的 `src/` 继续作为软渲染器、数学库、光栅化和平台显示层;`game/` 只放游戏自己的代码、资源、脚本和数据。 - -## 目录结构 - -```text -game/ - README.md - assets/ - sprites/ 运行时使用的 2D 精灵图片。 - textures/ 3D 材质或其他渲染用途的纹理。 - tilesets/ 瓦片图片和瓦片集元数据。 - fonts/ 位图字体或字体图集。 - audio/ - music/ 背景音乐。 - sfx/ 短音效。 - models/ 运行时使用的 3D 模型文件。 - materials/ 模型或精灵使用的材质定义。 - ui/ UI 面板、图标、光标、HUD 资源。 - raw/ 原始素材文件,不建议运行时直接加载。 - scripts/ - gameplay/ 预留给运行时玩法脚本。 - ui/ 预留给运行时 UI 流程脚本。 - tools/ 游戏开发期间使用的小型辅助脚本。 - src/ - app/ 游戏启动、场景切换、主循环衔接代码。 - scenes/ 启动、菜单、游戏、暂停、结算等场景。 - systems/ 输入、渲染、碰撞、动画、AI、音频等系统。 - entities/ 实体定义和实体工厂。 - components/ 系统使用的小型数据组件。 - ui/ HUD 和菜单代码。 - data/ - config/ 分辨率、控制、难度、平台选项等配置。 - balance/ 速度、生命、伤害、分数等数值表。 - localization/ 多语言文本表。 - tools/ - asset_pipeline/ 资产转换、压缩、打包相关工具。 - level_editor/ 可选的关卡编辑工具。 - docs/ - design/ 玩法规则、操作方式、关卡设计说明。 - technical/ 运行时约束和接入说明。 - tests/ - unit/ 纯逻辑单元测试。 - fixtures/ 测试用的小型数据文件。 -``` - -## 边界约定 - -- 仓库根目录的 `src/` 是渲染器和引擎基础层。 -- `game/src/` 可以依赖根目录 `src/`,但根目录 `src/` 不应该反向依赖 `game/`。 -- `game/assets/raw/` 只放可编辑源素材,运行时应加载转换后的资源。 -- `game/assets/sprites/` 放离线转换后的 `.sprite` 文件,PC 测试和 IMX6U 运行时都从这里读取。 -- `game/scripts/gameplay/` 预留给运行时脚本;构建、转换、检查类脚本放在 `game/scripts/tools/` 或 `game/tools/asset_pipeline/`。 -- 平台相关逻辑尽量集中在 `game/src/app/` 或 `game/data/config/`,不要散落到各个玩法模块里。 - -## 精灵资源转换 - -板端运行时不直接解码 PNG。开发时把 PNG 放在 `game/assets/raw/`,再用 PC 端工具转换为 `game/assets/sprites/*.sprite`。 - -`.sprite` 是项目自定义的简单二进制格式: - -```text -offset size 内容 -0 4 magic: "SPRT" -4 4 version: 1, little-endian uint32 -8 4 width, little-endian uint32 -12 4 height, little-endian uint32 -16 4 format: 1 表示 RGBA8888 -20 * width * height 个 RGBA8888 像素,每像素 little-endian uint32 -``` - -像素颜色沿用渲染器约定:`0xRRGGBBAA`,最低 8 位是 alpha。 - -常用命令: - -```bash -# 构建 PC 端工具 -cmake -B build-win . -cmake --build build-win --config Release --target SpriteAssetTool - -# 批量转换当前汤姆猫素材,输出适配 800x480 的板端资源 -cmake --build build-win --config Release --target ConvertTomSprites - -# 单张转换,保持原尺寸 -./build-win/Release/SpriteAssetTool.exe game/assets/raw/ui-record.png game/assets/sprites/ui-record.sprite - -# 单张转换并等比缩放到最大范围 -./build-win/Release/SpriteAssetTool.exe game/assets/raw/Tom-stand.png game/assets/sprites/Tom-stand.sprite --fit 560 360 -``` - -Linux PC 构建工具需要 `libsdl2-dev` 和 `libsdl2-image-dev`。ARM framebuffer 构建不会编译 `SpriteAssetTool`,只编译 `.sprite` 加载器。 - -启动汤姆猫资源链路测试: - -```bash -./build-win/Release/IMX6U-Game.exe --tom -``` - -部署到板端时,把可执行文件和转换后的 `game/assets/sprites/` 一起拷贝,保持相对路径即可;也可以放到 `/tmp/game/assets/sprites/` 或 `/usr/local/share/imx6u-game/sprites/`。 - -## 建议优先创建的文件 - -真正开始写游戏逻辑时,建议先从这些文件开始: - -```text -game/src/app/GameApp.h -game/src/app/GameApp.cpp -game/src/scenes/BootScene.h -game/src/scenes/BootScene.cpp -game/src/scenes/PlayScene.h -game/src/scenes/PlayScene.cpp -game/data/config/game.json -``` - -后续可以把现有的 `src/main.cpp` 改成薄启动器:只负责创建显示后端、初始化缓冲区和渲染器,然后把更新和绘制流程交给 `GameApp`。 diff --git a/src/Apps/Demo/main.cpp b/src/Apps/Demo/main.cpp index 297a5c6..e02fd34 100644 --- a/src/Apps/Demo/main.cpp +++ b/src/Apps/Demo/main.cpp @@ -1,381 +1,138 @@ -#include -#include -#include -<<<<<<< HEAD:src/main.cpp -#include -#include -#include -======= -#include -#include -#include +#include #include ->>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp -#include "Vector2.h" -#include "Vector3.h" -#include "Vector4.h" -#include "Matrix4x4.h" -#include "MathUtil.h" -#include "Color.h" -#include "Triangle.h" -#include "Camera.h" +#include #include -#include "Timer.h" -#include "Vertex.h" -<<<<<<< HEAD:src/main.cpp -#include "DepthBuffer.h" -#include "SpriteAssetLoader.h" -#include "SpriteRasterizer.h" -#include "Image.h" -#include "TomGameApp.h" -#include "AudioInput.h" -#include "AudioOutput.h" -#include "ButtonInput.h" -======= -#include "DrawContext.h" -#include "test_sprite.h" -#include "font_atlas.h" ->>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp +#include +#include +#include +#include +#include "BitmapFont.h" +#include "Color.h" #include "Display.h" +#include "DrawContext.h" +#include "Image.h" #include "TimeSource.h" +#include "Timer.h" +#include "font_atlas.h" +#include "test_sprite.h" + #ifdef USE_FRAMEBUFFER #include "FBDisplay.h" #else #include "SDLDisplay.h" #endif -const int32_t width = 800; -const int32_t height = 600; -const int32_t tomWidth = 800; -const int32_t tomHeight = 480; - -struct ProjectedVertex +namespace { - Math::Vector3 screen; - bool visible = false; -}; + const int32_t DemoWidth = 800; + const int32_t DemoHeight = 600; -struct CubeFace -{ - std::array vertices; -}; - -struct CubeTriangle -{ - std::array vertices; -}; - -static ProjectedVertex ProjectToScreen( - const Math::Vector3 &vertex, - const Math::Matrix4x4 &mvp, - const Math::Matrix4x4 &viewport) -{ - using namespace Math; - - const Vector4 clip = mvp * Vector4::Point(vertex); - if (std::abs(clip.w) < 1e-5f) + struct ProgramOptions { - return {}; - } + uint32_t target_fps; + bool show_help; - const float invW = 1.0f / clip.w; - const float ndcX = clip.x * invW; - const float ndcY = clip.y * invW; - const float ndcZ = clip.z * invW; - - if (ndcX < -1.0f || ndcX > 1.0f || ndcY < -1.0f || ndcY > 1.0f || ndcZ < -1.0f || ndcZ > 1.0f) - { - return {}; - } - - const Vector4 screen = viewport * Vector4(ndcX, ndcY, ndcZ, 1.0f); - ProjectedVertex result; - result.screen = Vector3(screen.x, screen.y, screen.z); - result.visible = true; - return result; -} - -static bool IsFaceVisible(const CubeFace &face, const std::array &viewSpaceVertices) -{ - using namespace Math; - - const Vector3 &v0 = viewSpaceVertices[face.vertices[0]]; - const Vector3 &v1 = viewSpaceVertices[face.vertices[1]]; - const Vector3 &v2 = viewSpaceVertices[face.vertices[2]]; - const Vector3 faceNormal = (v1 - v0).cross(v2 - v0); - const Vector3 faceCenter = - (viewSpaceVertices[face.vertices[0]] + - viewSpaceVertices[face.vertices[1]] + - viewSpaceVertices[face.vertices[2]] + - viewSpaceVertices[face.vertices[3]]) / - 4.0f; - - return faceNormal.dot(faceCenter) > 0.0f; -} - -static bool IsTriangleVisible(const CubeTriangle &triangle, const std::array &viewSpaceVertices) -{ - using namespace Math; - - const Vector3 &v0 = viewSpaceVertices[triangle.vertices[0]]; - const Vector3 &v1 = viewSpaceVertices[triangle.vertices[1]]; - const Vector3 &v2 = viewSpaceVertices[triangle.vertices[2]]; - const Vector3 faceNormal = (v1 - v0).cross(v2 - v0); - const Vector3 faceCenter = (v0 + v1 + v2) / 3.0f; - - return faceNormal.dot(faceCenter) > 0.0f; -} - -<<<<<<< HEAD:src/main.cpp -static bool HasArg(int argc, char* argv[], const std::string& expected) -{ - for (int i = 1; i < argc; ++i) - { - if (argv[i] != nullptr && expected == argv[i]) + ProgramOptions() + : target_fps(Core::Timer::DefaultFps), + show_help(false) { - return true; } - } - - return false; -} - -static std::string FindSpriteAssetPath(const std::string& fileName) -{ - const char* roots[] = { - "game/assets/sprites/", - "../game/assets/sprites/", - "../../game/assets/sprites/", - "../../../game/assets/sprites/", - "/tmp/game/assets/sprites/", - "/usr/local/share/imx6u-game/sprites/" }; - for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); ++i) + static Platform::IDisplay* CreateDisplay() { - const std::string path = std::string(roots[i]) + fileName; - std::ifstream file(path.c_str(), std::ios::binary); - if (file.good()) - { - return path; - } - } - - return std::string("game/assets/sprites/") + fileName; -} - -static bool LoadSpriteAsset(const std::string& fileName, RenderData::Image& image) -{ - const std::string path = FindSpriteAssetPath(fileName); - if (!Asset::SpriteAssetLoader::Load(path, image)) - { - std::cerr << "Load sprite asset failed: " << path << std::endl; - return false; - } - - return true; -} - -static int RunTomGame() -{ #ifdef USE_FRAMEBUFFER - Platform::IDisplay *display = new Platform::FBDisplay(); + return new Platform::FBDisplay(); #else - Platform::IDisplay *display = new Platform::SDLDisplay(); + return new Platform::SDLDisplay(); #endif - - if (!display->init(tomWidth, tomHeight)) - { - delete display; - return -1; } - Core::FrameBuffer frameBuffer(tomWidth, tomHeight); - Rasterizer::SpriteRasterizer spriteRasterizer(&frameBuffer); - - RenderData::Image background; - RenderData::Image tomStand; - RenderData::Image tomListen; - RenderData::Image tomSay1; - RenderData::Image tomSay2; - RenderData::Image tomSay3; - RenderData::Image tomSay4; - RenderData::Image button; - - if (!LoadSpriteAsset("background.sprite", background) || - !LoadSpriteAsset("Tom-stand.sprite", tomStand) || - !LoadSpriteAsset("Tom-listhen.sprite", tomListen) || - !LoadSpriteAsset("Tom-say1.sprite", tomSay1) || - !LoadSpriteAsset("Tom-say2.sprite", tomSay2) || - !LoadSpriteAsset("Tom-say3.sprite", tomSay3) || - !LoadSpriteAsset("Tom-say4.sprite", tomSay4) || - !LoadSpriteAsset("ui-record.sprite", button)) + static void PrintUsage(const char* program_name) { - std::cerr << "Run ConvertTomSprites before starting --tom." << std::endl; - display->shutdown(); - delete display; - return -1; + std::cout + << "Usage: " << program_name << " [--fps 30|45|60]\n" + << " " << program_name << " [--fps=30|45|60]\n"; } - Game::AudioInput audioInput; - Game::AudioOutput audioOutput; - Game::ButtonInput buttonInput; -#if defined(__linux__) - buttonInput.init(); -#endif - - Game::TomGameAssets assets; - assets.background = &background; - assets.tomStand = &tomStand; - assets.tomListen = &tomListen; - assets.tomSay1 = &tomSay1; - assets.tomSay2 = &tomSay2; - assets.tomSay3 = &tomSay3; - assets.tomSay4 = &tomSay4; - assets.button = &button; - - Game::TomGameApp app(&frameBuffer, &spriteRasterizer, &audioInput, &audioOutput, &buttonInput); - app.set_assets(assets); - if (!app.is_ready()) + static ProgramOptions ParseProgramOptions(int argc, char* argv[]) { - std::cerr << "TomGameApp assets are incomplete." << std::endl; - display->shutdown(); - delete display; - return -1; + ProgramOptions options; + + for (int i = 1; i < argc; ++i) + { + const char* arg = argv[i]; + const char* fps_value = nullptr; + + if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0) + { + options.show_help = true; + return options; + } + else if (std::strcmp(arg, "--fps") == 0) + { + if (i + 1 < argc) + { + fps_value = argv[++i]; + } + else + { + std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n"; + } + } + else if (std::strncmp(arg, "--fps=", 6) == 0) + { + fps_value = arg + 6; + } + + if (fps_value != nullptr) + { + const uint32_t parsed_fps = static_cast(std::strtoul(fps_value, nullptr, 10)); + if (Core::Timer::is_supported_fps(parsed_fps)) + { + options.target_fps = parsed_fps; + } + else + { + std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n"; + options.target_fps = Core::Timer::DefaultFps; + } + } + } + + return options; } - bool isRunning = true; - uint32_t lastTime = display->get_time_ms(); - while (isRunning) + static void SleepRemainingFrameTime(const Core::Timer& timer, const Platform::ITimeSource& time_source) { - display->poll_events(isRunning); - const uint32_t currentTime = display->get_time_ms(); - const float deltaTime = static_cast(currentTime - lastTime) * 0.001f; - lastTime = currentTime; - - app.update(deltaTime); - frameBuffer.clear(RenderData::Color(18, 18, 24, 255)); - app.draw(); - display->present(&frameBuffer); + const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms()); + if (sleep_ms > 0u) + { + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); + } } - - buttonInput.shutdown(); - audioInput.shutdown(); - audioOutput.shutdown(); - display->shutdown(); - delete display; - return 0; -======= -static void PrintUsage(const char *program_name) -{ - std::cout - << "Usage: " << program_name << " [--fps 30|45|60]\n" - << " " << program_name << " [--fps=30|45|60]\n"; } -struct ProgramOptions +int main(int argc, char* argv[]) { - uint32_t target_fps; - bool show_help; - - ProgramOptions() - : target_fps(Core::Timer::DefaultFps), - show_help(false) - { - } -}; - -static ProgramOptions ParseProgramOptions(int argc, char *argv[]) -{ - ProgramOptions options; - - for (int i = 1; i < argc; ++i) - { - const char *arg = argv[i]; - const char *fps_value = nullptr; - - if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0) - { - options.show_help = true; - return options; - } - else if (std::strcmp(arg, "--fps") == 0) - { - if (i + 1 < argc) - { - fps_value = argv[++i]; - } - else - { - std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n"; - } - } - else if (std::strncmp(arg, "--fps=", 6) == 0) - { - fps_value = arg + 6; - } - - if (fps_value != nullptr) - { - const uint32_t parsed_fps = static_cast(std::strtoul(fps_value, nullptr, 10)); - if (Core::Timer::is_supported_fps(parsed_fps)) - { - options.target_fps = parsed_fps; - } - else - { - std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n"; - options.target_fps = Core::Timer::DefaultFps; - } - } - } - - return options; -} - -static void SleepRemainingFrameTime(const Core::Timer &timer, const Platform::ITimeSource &time_source) -{ - const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms()); - if (sleep_ms > 0u) - { - std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); - } ->>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp -} - -int main(int argc, char *argv[]) -{ -<<<<<<< HEAD:src/main.cpp - if (HasArg(argc, argv, "--tom")) - { - return RunTomGame(); - } -======= const ProgramOptions options = ParseProgramOptions(argc, argv); if (options.show_help) { PrintUsage(argv[0]); return 0; } - Core::Timer timer(options.target_fps); - Platform::SteadyTimeSource time_source; ->>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp -#ifdef USE_FRAMEBUFFER - Platform::IDisplay *display = new Platform::FBDisplay(); -#else - Platform::IDisplay *display = new Platform::SDLDisplay(); -#endif - - if (!display->init(width, height)) + Platform::IDisplay* display = CreateDisplay(); + if (!display->init(DemoWidth, DemoHeight)) { delete display; return -1; } - Gfx::DrawContext ctx(width, height); - std::cout << "Target FPS: " << timer.target_fps() << std::endl; + Core::DrawContext ctx(DemoWidth, DemoHeight); + Core::Timer timer(options.target_fps); + Platform::SteadyTimeSource time_source; RenderData::BitmapFont font; font.atlas = RenderData::Image(font_atlas_pixels, font_atlas_width, font_atlas_height); @@ -384,164 +141,56 @@ int main(int argc, char *argv[]) font.columns = font_columns; font.first_char = font_first_char; - RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000); - RenderData::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height); - - const std::array tileIds = { - 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, - RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, - 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, - RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0}; - RenderData::Tilemap testTilemap(tileIds.data(), 8, 4, &sprite_img, sprite_img.width, sprite_img.height, 1); - - Scene::Camera camera; - camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f); - camera.transform.rotation = Math::Vector3(0.0f, 3.1415926535f, 0.0f); - - const std::array cubeVertices = { - Math::Vector3(-0.5f, -0.5f, -0.5f), - Math::Vector3(0.5f, -0.5f, -0.5f), - Math::Vector3(0.5f, 0.5f, -0.5f), - Math::Vector3(-0.5f, 0.5f, -0.5f), - Math::Vector3(-0.5f, -0.5f, 0.5f), - Math::Vector3(0.5f, -0.5f, 0.5f), - Math::Vector3(0.5f, 0.5f, 0.5f), - Math::Vector3(-0.5f, 0.5f, 0.5f)}; - - const std::array cubeFaces = { - CubeFace{{0, 3, 2, 1}}, - CubeFace{{4, 5, 6, 7}}, - CubeFace{{0, 4, 7, 3}}, - CubeFace{{1, 2, 6, 5}}, - CubeFace{{0, 1, 5, 4}}, - CubeFace{{3, 7, 6, 2}}}; - const std::array cubeTriangles = { - CubeTriangle{{0, 3, 2}}, CubeTriangle{{0, 2, 1}}, - CubeTriangle{{4, 5, 6}}, CubeTriangle{{4, 6, 7}}, - CubeTriangle{{0, 4, 7}}, CubeTriangle{{0, 7, 3}}, - CubeTriangle{{1, 2, 6}}, CubeTriangle{{1, 6, 5}}, - CubeTriangle{{0, 1, 5}}, CubeTriangle{{0, 5, 4}}, - CubeTriangle{{3, 7, 6}}, CubeTriangle{{3, 6, 2}}}; - - const RenderData::Color clearColor(18, 18, 24, 255); - const RenderData::Color cubeColor(240, 240, 240, 255); - const RenderData::Color fpsColor(0, 255, 80, 255); - const RenderData::Color fpsBg(0, 0, 0, 200); - const float aspectRatio = static_cast(width) / static_cast(height); + RenderData::Image sprite(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000u); int32_t fps = 0; int32_t frame_count = 0; uint32_t last_fps_time = 0; + uint32_t animation_time_ms = 0; char fps_text[32]; - bool isRunning = true; - uint32_t animation_time_ms = 0; - while (isRunning) + std::cout << "Demo started. Target FPS: " << timer.target_fps() << std::endl; + + bool is_running = true; + while (is_running) { timer.begin_frame(time_source.get_time_ms()); - const uint32_t fixed_delta_ms = timer.fixed_delta_ms(); - animation_time_ms += fixed_delta_ms; + animation_time_ms += timer.fixed_delta_ms(); - display->poll_events(isRunning); - - ctx.clear(clearColor); - - const float animation_time = static_cast(animation_time_ms) / 1000.0f; - const Math::Matrix4x4 model = - Math::MathUtil::get_rotation_matrix_y(animation_time) * - Math::MathUtil::get_rotation_matrix_x(static_cast(animation_time_ms * 6u) / 10000.0f); - const Math::Matrix4x4 view = camera.get_view_matrix(); - const Math::Matrix4x4 modelView = view * model; - const Math::Matrix4x4 projection = camera.get_perspective_projection_matrix(aspectRatio); - const Math::Matrix4x4 viewport = camera.get_viewport_matrix(static_cast(width), static_cast(height)); - const Math::Matrix4x4 mvp = projection * modelView; - - std::array viewSpaceVertices; - std::array projectedVertices; - for (size_t i = 0; i < cubeVertices.size(); ++i) + bool should_quit = false; + display->poll_events(should_quit); + if (should_quit) { - viewSpaceVertices[i] = (modelView * Math::Vector4::Point(cubeVertices[i])).to_vector3(); - projectedVertices[i] = ProjectToScreen(cubeVertices[i], mvp, viewport); + is_running = false; } - std::array visibleFaces = {}; - for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex) - { - visibleFaces[faceIndex] = IsFaceVisible(cubeFaces[faceIndex], viewSpaceVertices); - } + ctx.clear(RenderData::Color(18, 18, 24, 255)); + ctx.fill_rect(48, 72, 704, 360, RenderData::Color(42, 48, 60, 255)); + ctx.fill_rect(64, 88, 672, 328, RenderData::Color(24, 28, 36, 255)); - std::array drawTriangles; - size_t drawCommandCount = 0; - for (const CubeTriangle &cubeTriangle : cubeTriangles) - { - if (!IsTriangleVisible(cubeTriangle, viewSpaceVertices)) - { - continue; - } + const int32_t travel = std::max(1, DemoWidth - sprite.width - 128); + const int32_t sprite_x = 64 + static_cast((animation_time_ms / 8u) % static_cast(travel)); + const int32_t sprite_y = 220; + ctx.draw_sprite(sprite_x, sprite_y, sprite); + ctx.draw_text(font, 64, 96, RenderData::Color(240, 240, 240, 255), "GFX DEMO"); - const ProjectedVertex &v0 = projectedVertices[cubeTriangle.vertices[0]]; - const ProjectedVertex &v1 = projectedVertices[cubeTriangle.vertices[1]]; - const ProjectedVertex &v2 = projectedVertices[cubeTriangle.vertices[2]]; - if (!v0.visible || !v1.visible || !v2.visible) - { - continue; - } - - drawTriangles[drawCommandCount++] = - RenderData::Triangle( - Scene::Vertex(v0.screen), - Scene::Vertex(v1.screen), - Scene::Vertex(v2.screen)); - } - - for (size_t i = 0; i < drawCommandCount; ++i) - { - ctx.draw_triangle(drawTriangles[i], cubeColor); - } - - for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex) - { - if (!visibleFaces[faceIndex]) - { - continue; - } - - const CubeFace &face = cubeFaces[faceIndex]; - for (size_t edgeOffset = 0; edgeOffset < face.vertices.size(); ++edgeOffset) - { - const int startIndex = face.vertices[edgeOffset]; - const int endIndex = face.vertices[(edgeOffset + 1) % face.vertices.size()]; - const ProjectedVertex &start = projectedVertices[startIndex]; - const ProjectedVertex &end = projectedVertices[endIndex]; - if (!start.visible || !end.visible) - { - continue; - } - - ctx.draw_line( - Math::Vector2(start.screen.x, start.screen.y).to_vector2Int(), - Math::Vector2(end.screen.x, end.screen.y).to_vector2Int(), - clearColor); - } - } - - // sprite 测试 - ctx.draw_sprite(10, 10, sprite_img); - ctx.draw_sprite_region_ex(30, 10, sprite_region, 2, false, false); - ctx.draw_sprite_region_ex(10, 30, sprite_region, 3, true, false); - ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast(animation_time_ms / 20u) % 32, 0); - - // FPS 计数 ++frame_count; const uint32_t now = time_source.get_time_ms(); - if (now - last_fps_time >= 1000) + if (now - last_fps_time >= 1000u) { fps = frame_count; frame_count = 0; last_fps_time = now; } + std::snprintf(fps_text, sizeof(fps_text), "FPS: %d", fps); - ctx.draw_text(font, 4, 4, fpsColor, fpsBg, fps_text); + ctx.draw_text( + font, + 4, + 4, + RenderData::Color(0, 255, 80, 255), + RenderData::Color(0, 0, 0, 200), + fps_text); ctx.present(display); SleepRemainingFrameTime(timer, time_source); diff --git a/src/Apps/Game/Main.cpp b/src/Apps/Game/Main.cpp new file mode 100644 index 0000000..1dd41e6 --- /dev/null +++ b/src/Apps/Game/Main.cpp @@ -0,0 +1,301 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Color.h" +#include "Display.h" +#include "DrawContext.h" +#include "Image.h" +#include "SpriteAssetLoader.h" +#include "TimeSource.h" +#include "Timer.h" + +#ifdef USE_FRAMEBUFFER +#include "FBDisplay.h" +#else +#include "SDLDisplay.h" +#endif + +namespace +{ + const int32_t ScreenWidth = 800; + const int32_t ScreenHeight = 480; + + struct ProgramOptions + { + uint32_t target_fps; + bool show_help; + + ProgramOptions() + : target_fps(Core::Timer::DefaultFps), + show_help(false) + { + } + }; + + struct SpriteImage + { + std::vector pixels; + RenderData::Image image; + + bool is_valid() const + { + return image.pixels != nullptr && image.width > 0 && image.height > 0; + } + }; + + static Platform::IDisplay* CreateDisplay() + { +#ifdef USE_FRAMEBUFFER + return new Platform::FBDisplay(); +#else + return new Platform::SDLDisplay(); +#endif + } + + static void PrintUsage(const char* program_name) + { + std::cout + << "Usage: " << program_name << " [--fps 30|45|60]\n" + << " " << program_name << " [--fps=30|45|60]\n"; + } + + static ProgramOptions ParseProgramOptions(int argc, char* argv[]) + { + ProgramOptions options; + + for (int i = 1; i < argc; ++i) + { + const char* arg = argv[i]; + const char* fps_value = nullptr; + + if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0) + { + options.show_help = true; + return options; + } + else if (std::strcmp(arg, "--fps") == 0) + { + if (i + 1 < argc) + { + fps_value = argv[++i]; + } + else + { + std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n"; + } + } + else if (std::strncmp(arg, "--fps=", 6) == 0) + { + fps_value = arg + 6; + } + + if (fps_value != nullptr) + { + const uint32_t parsed_fps = static_cast(std::strtoul(fps_value, nullptr, 10)); + if (Core::Timer::is_supported_fps(parsed_fps)) + { + options.target_fps = parsed_fps; + } + else + { + std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n"; + options.target_fps = Core::Timer::DefaultFps; + } + } + } + + return options; + } + + static void SleepRemainingFrameTime(const Core::Timer& timer, const Platform::ITimeSource& time_source) + { + const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms()); + if (sleep_ms > 0u) + { + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); + } + } + + static std::string FindSpriteAssetPath(const std::string& file_name) + { + const char* roots[] = { + "src/Apps/Game/assets/sprites/", + "../src/Apps/Game/assets/sprites/", + "../../src/Apps/Game/assets/sprites/", + "../../../src/Apps/Game/assets/sprites/", + "/usr/local/share/imx6u-game/sprites/" + }; + + for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); ++i) + { + const std::string path = std::string(roots[i]) + file_name; + std::ifstream file(path.c_str(), std::ios::binary); + if (file.good()) + { + return path; + } + } + + return std::string("src/Apps/Game/assets/sprites/") + file_name; + } + + static void EnableAlphaColorKey(SpriteImage& sprite) + { + for (size_t i = 0; i < sprite.pixels.size(); ++i) + { + if ((sprite.pixels[i] & 0xFFu) == 0u) + { + sprite.pixels[i] = 0x00000000u; + } + } + + sprite.image = RenderData::Image( + sprite.pixels.data(), + sprite.image.width, + sprite.image.height, + 0x00000000u); + } + + static bool LoadSpriteAsset(const std::string& file_name, SpriteImage& sprite, bool use_alpha_key) + { + const std::string path = FindSpriteAssetPath(file_name); + if (!Asset::SpriteAssetLoader::Load(path, sprite.pixels, sprite.image)) + { + std::cerr << "Load sprite asset failed: " << path << std::endl; + return false; + } + + if (use_alpha_key) + { + EnableAlphaColorKey(sprite); + } + + return sprite.is_valid(); + } + + static const RenderData::Image& SelectTomFrame( + uint32_t animation_time_ms, + const SpriteImage& stand, + const SpriteImage& listen, + const SpriteImage* const* speaking_frames, + size_t speaking_frame_count) + { + const uint32_t phase = (animation_time_ms / 1000u) % 6u; + if (phase < 2u) + { + return stand.image; + } + if (phase < 3u) + { + return listen.image; + } + + const size_t frame_index = (animation_time_ms / 120u) % speaking_frame_count; + return speaking_frames[frame_index]->image; + } +} + +int main(int argc, char* argv[]) +{ + const ProgramOptions options = ParseProgramOptions(argc, argv); + if (options.show_help) + { + PrintUsage(argv[0]); + return 0; + } + + Platform::IDisplay* display = CreateDisplay(); + if (!display->init(ScreenWidth, ScreenHeight)) + { + delete display; + return -1; + } + + SpriteImage background; + SpriteImage tom_stand; + SpriteImage tom_listen; + SpriteImage tom_say1; + SpriteImage tom_say2; + SpriteImage tom_say3; + SpriteImage tom_say4; + SpriteImage record_button; + + if (!LoadSpriteAsset("background.sprite", background, false) || + !LoadSpriteAsset("Tom-stand.sprite", tom_stand, true) || + !LoadSpriteAsset("Tom-listhen.sprite", tom_listen, true) || + !LoadSpriteAsset("Tom-say1.sprite", tom_say1, true) || + !LoadSpriteAsset("Tom-say2.sprite", tom_say2, true) || + !LoadSpriteAsset("Tom-say3.sprite", tom_say3, true) || + !LoadSpriteAsset("Tom-say4.sprite", tom_say4, true) || + !LoadSpriteAsset("ui-record.sprite", record_button, true)) + { + std::cerr << "Missing Tom sprite assets. Run the ConvertTomSprites target first.\n"; + display->shutdown(); + delete display; + return -1; + } + + const SpriteImage* speaking_frames[] = { + &tom_say1, + &tom_say2, + &tom_say3, + &tom_say4, + &tom_say3, + &tom_say2 + }; + const size_t speaking_frame_count = sizeof(speaking_frames) / sizeof(speaking_frames[0]); + + Core::DrawContext ctx(ScreenWidth, ScreenHeight); + Core::Timer timer(options.target_fps); + Platform::SteadyTimeSource time_source; + + std::cout << "Tom game started. Target FPS: " << timer.target_fps() << std::endl; + + bool is_running = true; + uint32_t animation_time_ms = 0; + while (is_running) + { + timer.begin_frame(time_source.get_time_ms()); + animation_time_ms += timer.fixed_delta_ms(); + + bool should_quit = false; + display->poll_events(should_quit); + if (should_quit) + { + is_running = false; + } + + ctx.clear(RenderData::Color(18, 18, 24, 255)); + ctx.draw_sprite(0, 0, background.image); + + const RenderData::Image& tom = SelectTomFrame( + animation_time_ms, + tom_stand, + tom_listen, + speaking_frames, + speaking_frame_count); + + const int32_t tom_x = (ScreenWidth - tom.width) / 2; + const int32_t tom_y = std::max(0, ScreenHeight - tom.height - 72); + ctx.draw_sprite(tom_x, tom_y, tom); + + const int32_t button_x = (ScreenWidth - record_button.image.width) / 2; + const int32_t button_y = ScreenHeight - record_button.image.height - 16; + ctx.draw_sprite(button_x, button_y, record_button.image); + + ctx.present(display); + SleepRemainingFrameTime(timer, time_source); + } + + display->shutdown(); + delete display; + return 0; +} diff --git a/src/Apps/Game/README.md b/src/Apps/Game/README.md new file mode 100644 index 0000000..700a094 --- /dev/null +++ b/src/Apps/Game/README.md @@ -0,0 +1,74 @@ +# Tom Game + +`src/Apps/Game` 是 Tom 游戏应用目录。仓库根目录下的 `src/Core` 继续作为渲染、数学、光栅化和平台显示层;Game 目录只放游戏入口、游戏逻辑、素材和素材转换工具。 + +## 当前入口 + +主构建目标 `IMX6U-Game` 现在从下面的文件启动: + +```text +src/Apps/Game/Main.cpp +``` + +运行后默认启动 Tom,不再需要 `--tom` 参数。可选参数只保留帧率: + +```bash +IMX6U-Game.exe --fps 30 +IMX6U-Game.exe --fps=60 +``` + +## 素材目录 + +```text +src/Apps/Game/assets/raw/ PNG 源素材 +src/Apps/Game/assets/sprites/ 转换后的 .sprite 运行时素材 +src/Apps/Game/tools/ 素材转换等开发工具 +src/Apps/Game/src/ 旧版 TomGameApp、音频、动画等游戏模块 +src/Apps/Game/tests/ 手工测试或后续测试代码 +``` + +板端运行时不直接解码 PNG。开发时把 PNG 放在 `src/Apps/Game/assets/raw/`,再转换为 `src/Apps/Game/assets/sprites/*.sprite`。 + +## 素材转换 + +`.sprite` 是项目自定义的 RGBA8888 二进制格式: + +```text +offset size content +0 4 magic: "SPRT" +4 4 version: 1, little-endian uint32 +8 4 width, little-endian uint32 +12 4 height, little-endian uint32 +16 4 format: 1 means RGBA8888 +20 * width * height pixels, little-endian uint32, 0xRRGGBBAA +``` + +常用命令: + +```bash +cmake --build build-win --config Release --target SpriteAssetTool +cmake --build build-win --config Release --target ConvertTomSprites +``` + +单张转换示例: + +```bash +./build-win/Release/SpriteAssetTool.exe src/Apps/Game/assets/raw/ui-record.png src/Apps/Game/assets/sprites/ui-record.sprite +./build-win/Release/SpriteAssetTool.exe src/Apps/Game/assets/raw/Tom-stand.png src/Apps/Game/assets/sprites/Tom-stand.sprite --fit 560 360 +``` + +## 运行 + +Windows PC: + +```bash +./build-win/Release/IMX6U-Game.exe +``` + +Linux framebuffer / IMX6U: + +```bash +./IMX6U-Game --fps 30 +``` + +部署到板端时,把可执行文件和 `src/Apps/Game/assets/sprites/` 一起拷贝,并保持相对路径;也可以把 `.sprite` 放到 `/usr/local/share/imx6u-game/sprites/`。 diff --git a/game/assets/audio/music/.gitkeep b/src/Apps/Game/assets/audio/music/.gitkeep similarity index 100% rename from game/assets/audio/music/.gitkeep rename to src/Apps/Game/assets/audio/music/.gitkeep diff --git a/game/assets/audio/sfx/.gitkeep b/src/Apps/Game/assets/audio/sfx/.gitkeep similarity index 100% rename from game/assets/audio/sfx/.gitkeep rename to src/Apps/Game/assets/audio/sfx/.gitkeep diff --git a/game/assets/fonts/.gitkeep b/src/Apps/Game/assets/fonts/.gitkeep similarity index 100% rename from game/assets/fonts/.gitkeep rename to src/Apps/Game/assets/fonts/.gitkeep diff --git a/game/assets/materials/.gitkeep b/src/Apps/Game/assets/materials/.gitkeep similarity index 100% rename from game/assets/materials/.gitkeep rename to src/Apps/Game/assets/materials/.gitkeep diff --git a/game/assets/models/.gitkeep b/src/Apps/Game/assets/models/.gitkeep similarity index 100% rename from game/assets/models/.gitkeep rename to src/Apps/Game/assets/models/.gitkeep diff --git a/game/assets/raw/.gitkeep b/src/Apps/Game/assets/raw/.gitkeep similarity index 100% rename from game/assets/raw/.gitkeep rename to src/Apps/Game/assets/raw/.gitkeep diff --git a/game/assets/raw/Tom-listhen.png b/src/Apps/Game/assets/raw/Tom-listhen.png similarity index 100% rename from game/assets/raw/Tom-listhen.png rename to src/Apps/Game/assets/raw/Tom-listhen.png diff --git a/game/assets/raw/Tom-openmouse1.png b/src/Apps/Game/assets/raw/Tom-openmouse1.png similarity index 100% rename from game/assets/raw/Tom-openmouse1.png rename to src/Apps/Game/assets/raw/Tom-openmouse1.png diff --git a/game/assets/raw/Tom-openmouse2.png b/src/Apps/Game/assets/raw/Tom-openmouse2.png similarity index 100% rename from game/assets/raw/Tom-openmouse2.png rename to src/Apps/Game/assets/raw/Tom-openmouse2.png diff --git a/game/assets/raw/Tom-say1.png b/src/Apps/Game/assets/raw/Tom-say1.png similarity index 100% rename from game/assets/raw/Tom-say1.png rename to src/Apps/Game/assets/raw/Tom-say1.png diff --git a/game/assets/raw/Tom-say2.png b/src/Apps/Game/assets/raw/Tom-say2.png similarity index 100% rename from game/assets/raw/Tom-say2.png rename to src/Apps/Game/assets/raw/Tom-say2.png diff --git a/game/assets/raw/Tom-say3.png b/src/Apps/Game/assets/raw/Tom-say3.png similarity index 100% rename from game/assets/raw/Tom-say3.png rename to src/Apps/Game/assets/raw/Tom-say3.png diff --git a/game/assets/raw/Tom-say4.png b/src/Apps/Game/assets/raw/Tom-say4.png similarity index 100% rename from game/assets/raw/Tom-say4.png rename to src/Apps/Game/assets/raw/Tom-say4.png diff --git a/game/assets/raw/Tom-stand.png b/src/Apps/Game/assets/raw/Tom-stand.png similarity index 100% rename from game/assets/raw/Tom-stand.png rename to src/Apps/Game/assets/raw/Tom-stand.png diff --git a/game/assets/raw/background.png b/src/Apps/Game/assets/raw/background.png similarity index 100% rename from game/assets/raw/background.png rename to src/Apps/Game/assets/raw/background.png diff --git a/game/assets/raw/ui-fat.png b/src/Apps/Game/assets/raw/ui-fat.png similarity index 100% rename from game/assets/raw/ui-fat.png rename to src/Apps/Game/assets/raw/ui-fat.png diff --git a/game/assets/raw/ui-hand.png b/src/Apps/Game/assets/raw/ui-hand.png similarity index 100% rename from game/assets/raw/ui-hand.png rename to src/Apps/Game/assets/raw/ui-hand.png diff --git a/game/assets/raw/ui-i.png b/src/Apps/Game/assets/raw/ui-i.png similarity index 100% rename from game/assets/raw/ui-i.png rename to src/Apps/Game/assets/raw/ui-i.png diff --git a/game/assets/raw/ui-record.png b/src/Apps/Game/assets/raw/ui-record.png similarity index 100% rename from game/assets/raw/ui-record.png rename to src/Apps/Game/assets/raw/ui-record.png diff --git a/game/assets/raw/ui-tom.png b/src/Apps/Game/assets/raw/ui-tom.png similarity index 100% rename from game/assets/raw/ui-tom.png rename to src/Apps/Game/assets/raw/ui-tom.png diff --git a/game/assets/sprites/.gitkeep b/src/Apps/Game/assets/sprites/.gitkeep similarity index 100% rename from game/assets/sprites/.gitkeep rename to src/Apps/Game/assets/sprites/.gitkeep diff --git a/src/Apps/Game/assets/sprites/Tom-listhen.sprite b/src/Apps/Game/assets/sprites/Tom-listhen.sprite new file mode 100644 index 0000000..83c65d9 Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-listhen.sprite differ diff --git a/src/Apps/Game/assets/sprites/Tom-openmouse1.sprite b/src/Apps/Game/assets/sprites/Tom-openmouse1.sprite new file mode 100644 index 0000000..f04a8f6 Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-openmouse1.sprite differ diff --git a/src/Apps/Game/assets/sprites/Tom-openmouse2.sprite b/src/Apps/Game/assets/sprites/Tom-openmouse2.sprite new file mode 100644 index 0000000..ce302a8 Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-openmouse2.sprite differ diff --git a/src/Apps/Game/assets/sprites/Tom-say1.sprite b/src/Apps/Game/assets/sprites/Tom-say1.sprite new file mode 100644 index 0000000..5efded5 Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-say1.sprite differ diff --git a/src/Apps/Game/assets/sprites/Tom-say2.sprite b/src/Apps/Game/assets/sprites/Tom-say2.sprite new file mode 100644 index 0000000..81efeea Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-say2.sprite differ diff --git a/src/Apps/Game/assets/sprites/Tom-say3.sprite b/src/Apps/Game/assets/sprites/Tom-say3.sprite new file mode 100644 index 0000000..97d595e Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-say3.sprite differ diff --git a/src/Apps/Game/assets/sprites/Tom-say4.sprite b/src/Apps/Game/assets/sprites/Tom-say4.sprite new file mode 100644 index 0000000..e626f8b Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-say4.sprite differ diff --git a/src/Apps/Game/assets/sprites/Tom-stand.sprite b/src/Apps/Game/assets/sprites/Tom-stand.sprite new file mode 100644 index 0000000..16dd005 Binary files /dev/null and b/src/Apps/Game/assets/sprites/Tom-stand.sprite differ diff --git a/src/Apps/Game/assets/sprites/background.sprite b/src/Apps/Game/assets/sprites/background.sprite new file mode 100644 index 0000000..2ba54a0 Binary files /dev/null and b/src/Apps/Game/assets/sprites/background.sprite differ diff --git a/src/Apps/Game/assets/sprites/ui-fat.sprite b/src/Apps/Game/assets/sprites/ui-fat.sprite new file mode 100644 index 0000000..7527e53 Binary files /dev/null and b/src/Apps/Game/assets/sprites/ui-fat.sprite differ diff --git a/src/Apps/Game/assets/sprites/ui-hand.sprite b/src/Apps/Game/assets/sprites/ui-hand.sprite new file mode 100644 index 0000000..26a7e6c Binary files /dev/null and b/src/Apps/Game/assets/sprites/ui-hand.sprite differ diff --git a/src/Apps/Game/assets/sprites/ui-i.sprite b/src/Apps/Game/assets/sprites/ui-i.sprite new file mode 100644 index 0000000..b30cd18 Binary files /dev/null and b/src/Apps/Game/assets/sprites/ui-i.sprite differ diff --git a/src/Apps/Game/assets/sprites/ui-record.sprite b/src/Apps/Game/assets/sprites/ui-record.sprite new file mode 100644 index 0000000..16e6d64 Binary files /dev/null and b/src/Apps/Game/assets/sprites/ui-record.sprite differ diff --git a/src/Apps/Game/assets/sprites/ui-tom.sprite b/src/Apps/Game/assets/sprites/ui-tom.sprite new file mode 100644 index 0000000..ef36c44 Binary files /dev/null and b/src/Apps/Game/assets/sprites/ui-tom.sprite differ diff --git a/game/assets/textures/.gitkeep b/src/Apps/Game/assets/textures/.gitkeep similarity index 100% rename from game/assets/textures/.gitkeep rename to src/Apps/Game/assets/textures/.gitkeep diff --git a/game/assets/tilesets/.gitkeep b/src/Apps/Game/assets/tilesets/.gitkeep similarity index 100% rename from game/assets/tilesets/.gitkeep rename to src/Apps/Game/assets/tilesets/.gitkeep diff --git a/game/assets/ui/.gitkeep b/src/Apps/Game/assets/ui/.gitkeep similarity index 100% rename from game/assets/ui/.gitkeep rename to src/Apps/Game/assets/ui/.gitkeep diff --git a/game/docs/design/.gitkeep b/src/Apps/Game/docs/design/.gitkeep similarity index 100% rename from game/docs/design/.gitkeep rename to src/Apps/Game/docs/design/.gitkeep diff --git a/game/docs/technical/.gitkeep b/src/Apps/Game/docs/technical/.gitkeep similarity index 100% rename from game/docs/technical/.gitkeep rename to src/Apps/Game/docs/technical/.gitkeep diff --git a/game/src/app/.gitkeep b/src/Apps/Game/src/app/.gitkeep similarity index 100% rename from game/src/app/.gitkeep rename to src/Apps/Game/src/app/.gitkeep diff --git a/game/src/app/TomGameApp.cpp b/src/Apps/Game/src/app/TomGameApp.cpp similarity index 97% rename from game/src/app/TomGameApp.cpp rename to src/Apps/Game/src/app/TomGameApp.cpp index d09f467..d24f7f7 100644 --- a/game/src/app/TomGameApp.cpp +++ b/src/Apps/Game/src/app/TomGameApp.cpp @@ -3,9 +3,9 @@ #include "../hardware/AudioInput.h" #include "../hardware/AudioOutput.h" #include "../hardware/ButtonInput.h" -#include "../../../src/Core/FrameBuffer.h" -#include "../../../src/Rasterizer/SpriteRasterizer.h" -#include "../../../src/RenderData/Image.h" +#include "FrameBuffer.h" +#include "SpriteRasterizer.h" +#include "Image.h" namespace Game { diff --git a/game/src/app/TomGameApp.h b/src/Apps/Game/src/app/TomGameApp.h similarity index 98% rename from game/src/app/TomGameApp.h rename to src/Apps/Game/src/app/TomGameApp.h index 14beae5..f68e8d4 100644 --- a/game/src/app/TomGameApp.h +++ b/src/Apps/Game/src/app/TomGameApp.h @@ -4,7 +4,7 @@ #include "../audio/VoicePlayer.h" #include "../audio/VoiceRecorder.h" #include "../components/SpriteAnimator.h" -#include "../../../src/RenderData/Sprite.h" +#include "Sprite.h" namespace Core { diff --git a/game/src/audio/VoiceEffect.cpp b/src/Apps/Game/src/audio/VoiceEffect.cpp similarity index 100% rename from game/src/audio/VoiceEffect.cpp rename to src/Apps/Game/src/audio/VoiceEffect.cpp diff --git a/game/src/audio/VoiceEffect.h b/src/Apps/Game/src/audio/VoiceEffect.h similarity index 100% rename from game/src/audio/VoiceEffect.h rename to src/Apps/Game/src/audio/VoiceEffect.h diff --git a/game/src/audio/VoicePlayer.cpp b/src/Apps/Game/src/audio/VoicePlayer.cpp similarity index 100% rename from game/src/audio/VoicePlayer.cpp rename to src/Apps/Game/src/audio/VoicePlayer.cpp diff --git a/game/src/audio/VoicePlayer.h b/src/Apps/Game/src/audio/VoicePlayer.h similarity index 100% rename from game/src/audio/VoicePlayer.h rename to src/Apps/Game/src/audio/VoicePlayer.h diff --git a/game/src/audio/VoiceRecorder.cpp b/src/Apps/Game/src/audio/VoiceRecorder.cpp similarity index 100% rename from game/src/audio/VoiceRecorder.cpp rename to src/Apps/Game/src/audio/VoiceRecorder.cpp diff --git a/game/src/audio/VoiceRecorder.h b/src/Apps/Game/src/audio/VoiceRecorder.h similarity index 100% rename from game/src/audio/VoiceRecorder.h rename to src/Apps/Game/src/audio/VoiceRecorder.h diff --git a/game/src/components/.gitkeep b/src/Apps/Game/src/components/.gitkeep similarity index 100% rename from game/src/components/.gitkeep rename to src/Apps/Game/src/components/.gitkeep diff --git a/game/src/components/SpriteAnimator.cpp b/src/Apps/Game/src/components/SpriteAnimator.cpp similarity index 100% rename from game/src/components/SpriteAnimator.cpp rename to src/Apps/Game/src/components/SpriteAnimator.cpp diff --git a/game/src/components/SpriteAnimator.h b/src/Apps/Game/src/components/SpriteAnimator.h similarity index 94% rename from game/src/components/SpriteAnimator.h rename to src/Apps/Game/src/components/SpriteAnimator.h index 03ddd1b..a25929b 100644 --- a/game/src/components/SpriteAnimator.h +++ b/src/Apps/Game/src/components/SpriteAnimator.h @@ -1,7 +1,7 @@ #pragma once #include #include -#include "../../../src/RenderData/Sprite.h" +#include "Sprite.h" namespace Game { diff --git a/game/src/hardware/AudioInput.cpp b/src/Apps/Game/src/hardware/AudioInput.cpp similarity index 100% rename from game/src/hardware/AudioInput.cpp rename to src/Apps/Game/src/hardware/AudioInput.cpp diff --git a/game/src/hardware/AudioInput.h b/src/Apps/Game/src/hardware/AudioInput.h similarity index 100% rename from game/src/hardware/AudioInput.h rename to src/Apps/Game/src/hardware/AudioInput.h diff --git a/game/src/hardware/AudioOutput.cpp b/src/Apps/Game/src/hardware/AudioOutput.cpp similarity index 100% rename from game/src/hardware/AudioOutput.cpp rename to src/Apps/Game/src/hardware/AudioOutput.cpp diff --git a/game/src/hardware/AudioOutput.h b/src/Apps/Game/src/hardware/AudioOutput.h similarity index 100% rename from game/src/hardware/AudioOutput.h rename to src/Apps/Game/src/hardware/AudioOutput.h diff --git a/game/src/hardware/ButtonInput.cpp b/src/Apps/Game/src/hardware/ButtonInput.cpp similarity index 100% rename from game/src/hardware/ButtonInput.cpp rename to src/Apps/Game/src/hardware/ButtonInput.cpp diff --git a/game/src/hardware/ButtonInput.h b/src/Apps/Game/src/hardware/ButtonInput.h similarity index 100% rename from game/src/hardware/ButtonInput.h rename to src/Apps/Game/src/hardware/ButtonInput.h diff --git a/game/src/scenes/.gitkeep b/src/Apps/Game/src/scenes/.gitkeep similarity index 100% rename from game/src/scenes/.gitkeep rename to src/Apps/Game/src/scenes/.gitkeep diff --git a/game/src/scenes/TomScene.cpp b/src/Apps/Game/src/scenes/TomScene.cpp similarity index 100% rename from game/src/scenes/TomScene.cpp rename to src/Apps/Game/src/scenes/TomScene.cpp diff --git a/game/src/scenes/TomScene.h b/src/Apps/Game/src/scenes/TomScene.h similarity index 100% rename from game/src/scenes/TomScene.h rename to src/Apps/Game/src/scenes/TomScene.h diff --git a/game/src/systems/.gitkeep b/src/Apps/Game/src/systems/.gitkeep similarity index 100% rename from game/src/systems/.gitkeep rename to src/Apps/Game/src/systems/.gitkeep diff --git a/game/src/systems/AnimationSystem.cpp b/src/Apps/Game/src/systems/AnimationSystem.cpp similarity index 97% rename from game/src/systems/AnimationSystem.cpp rename to src/Apps/Game/src/systems/AnimationSystem.cpp index 967b74b..a6e6e4c 100644 --- a/game/src/systems/AnimationSystem.cpp +++ b/src/Apps/Game/src/systems/AnimationSystem.cpp @@ -1,5 +1,5 @@ #include "AnimationSystem.h" -#include "../../../src/Rasterizer/SpriteRasterizer.h" +#include "SpriteRasterizer.h" namespace Game { diff --git a/game/src/systems/AnimationSystem.h b/src/Apps/Game/src/systems/AnimationSystem.h similarity index 100% rename from game/src/systems/AnimationSystem.h rename to src/Apps/Game/src/systems/AnimationSystem.h diff --git a/game/tests/fixtures/.gitkeep b/src/Apps/Game/tests/fixtures/.gitkeep similarity index 100% rename from game/tests/fixtures/.gitkeep rename to src/Apps/Game/tests/fixtures/.gitkeep diff --git a/game/tests/manual/SpriteAnimationTest.cpp b/src/Apps/Game/tests/manual/SpriteAnimationTest.cpp similarity index 96% rename from game/tests/manual/SpriteAnimationTest.cpp rename to src/Apps/Game/tests/manual/SpriteAnimationTest.cpp index 5e6f5e4..55a0f3d 100644 --- a/game/tests/manual/SpriteAnimationTest.cpp +++ b/src/Apps/Game/tests/manual/SpriteAnimationTest.cpp @@ -18,10 +18,10 @@ static std::string FindAssetPath(const std::string& fileName) { const char* roots[] = { - "game/assets/sprites/", - "../game/assets/sprites/", - "../../game/assets/sprites/", - "../../../game/assets/sprites/" + "src/Apps/Game/assets/sprites/", + "../src/Apps/Game/assets/sprites/", + "../../src/Apps/Game/assets/sprites/", + "../../../src/Apps/Game/assets/sprites/" }; for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); ++i) @@ -34,7 +34,7 @@ static std::string FindAssetPath(const std::string& fileName) } } - return std::string("game/assets/sprites/") + fileName; + return std::string("src/Apps/Game/assets/sprites/") + fileName; } static bool LoadSpriteImage(const std::string& fileName, RenderData::Image& image) diff --git a/game/tests/unit/.gitkeep b/src/Apps/Game/tests/unit/.gitkeep similarity index 100% rename from game/tests/unit/.gitkeep rename to src/Apps/Game/tests/unit/.gitkeep diff --git a/game/tools/asset_pipeline/SpriteAssetTool.cpp b/src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp similarity index 86% rename from game/tools/asset_pipeline/SpriteAssetTool.cpp rename to src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp index 092e08c..35c70c3 100644 --- a/game/tools/asset_pipeline/SpriteAssetTool.cpp +++ b/src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp @@ -41,6 +41,23 @@ namespace TransformOptions() : mode(TransformMode::None), width(0), height(0) {} }; + struct LoadedImage + { + std::vector pixels; + RenderData::Image image; + + void assign(int32_t width, int32_t height, const std::vector& sourcePixels) + { + pixels = sourcePixels; + image = RenderData::Image(pixels.empty() ? nullptr : pixels.data(), width, height); + } + + bool is_valid() const + { + return image.pixels != nullptr && image.width > 0 && image.height > 0; + } + }; + static bool IsPathSeparator(char value) { return value == '/' || value == '\\'; @@ -274,7 +291,7 @@ namespace } } -static bool LoadPngImage(const std::string& path, RenderData::Image& image) +static bool LoadPngImage(const std::string& path, LoadedImage& image) { SDL_Surface* loadedSurface = IMG_Load(path.c_str()); if (loadedSurface == nullptr) @@ -291,7 +308,7 @@ static bool LoadPngImage(const std::string& path, RenderData::Image& image) return false; } - std::vector pixels(static_cast(rgbaSurface->w) * rgbaSurface->h, 0); + std::vector pixels(static_cast(rgbaSurface->w) * static_cast(rgbaSurface->h), 0); if (SDL_LockSurface(rgbaSurface) != 0) { std::cerr << "SDL_LockSurface failed: " << SDL_GetError() << std::endl; @@ -310,45 +327,48 @@ static bool LoadPngImage(const std::string& path, RenderData::Image& image) } SDL_UnlockSurface(rgbaSurface); - image = RenderData::Image(rgbaSurface->w, rgbaSurface->h, pixels); + image.assign(rgbaSurface->w, rgbaSurface->h, pixels); SDL_FreeSurface(rgbaSurface); return image.is_valid(); } -static RenderData::Image ResizeImageNearest(const RenderData::Image& source, int32_t width, int32_t height) +static LoadedImage ResizeImageNearest(const LoadedImage& source, int32_t width, int32_t height) { if (!source.is_valid() || width <= 0 || height <= 0) { - return RenderData::Image(); + return LoadedImage(); } - RenderData::Image result(width, height); + std::vector pixels(static_cast(width) * static_cast(height), 0); for (int32_t y = 0; y < height; ++y) { - const int32_t sourceY = y * source.get_height() / height; + const int32_t sourceY = y * source.image.height / height; for (int32_t x = 0; x < width; ++x) { - const int32_t sourceX = x * source.get_width() / width; - result.set_pixel(x, y, source.get_pixel_fast(sourceX, sourceY)); + const int32_t sourceX = x * source.image.width / width; + pixels[static_cast(y) * static_cast(width) + static_cast(x)] = + source.image.pixels[static_cast(sourceY) * static_cast(source.image.width) + static_cast(sourceX)]; } } + LoadedImage result; + result.assign(width, height, pixels); return result; } -static RenderData::Image ResizeImageToFit(const RenderData::Image& source, int32_t maxWidth, int32_t maxHeight) +static LoadedImage ResizeImageToFit(const LoadedImage& source, int32_t maxWidth, int32_t maxHeight) { if (!source.is_valid() || maxWidth <= 0 || maxHeight <= 0) { - return RenderData::Image(); + return LoadedImage(); } - const float scaleX = static_cast(maxWidth) / source.get_width(); - const float scaleY = static_cast(maxHeight) / source.get_height(); + const float scaleX = static_cast(maxWidth) / source.image.width; + const float scaleY = static_cast(maxHeight) / source.image.height; const float scale = std::min(scaleX, scaleY); - const int32_t width = std::max(1, static_cast(source.get_width() * scale)); - const int32_t height = std::max(1, static_cast(source.get_height() * scale)); + const int32_t width = std::max(1, static_cast(source.image.width * scale)); + const int32_t height = std::max(1, static_cast(source.image.height * scale)); return ResizeImageNearest(source, width, height); } @@ -385,7 +405,7 @@ static TransformOptions ResolveTransformForInput( return resolved; } -static bool ApplyTransform(const std::string& inputPath, const TransformOptions& options, RenderData::Image& image) +static bool ApplyTransform(const std::string& inputPath, const TransformOptions& options, LoadedImage& image) { const TransformOptions resolved = ResolveTransformForInput(inputPath, options); switch (resolved.mode) @@ -413,7 +433,7 @@ static bool ConvertFile( const std::string& outputPath, const TransformOptions& transformOptions) { - RenderData::Image image; + LoadedImage image; if (!LoadPngImage(inputPath, image)) { return false; @@ -430,14 +450,14 @@ static bool ConvertFile( return false; } - if (!Asset::SpriteAssetLoader::Save(outputPath, image)) + if (!Asset::SpriteAssetLoader::Save(outputPath, image.image)) { std::cerr << "Save failed: " << outputPath << std::endl; return false; } std::cout << inputPath << " -> " << outputPath - << " (" << image.get_width() << "x" << image.get_height() << ")" << std::endl; + << " (" << image.image.width << "x" << image.image.height << ")" << std::endl; return true; } @@ -532,7 +552,7 @@ static void PrintUsage() std::cout << " SpriteAssetTool input.png output.sprite [--fit maxWidth maxHeight]" << std::endl; std::cout << " SpriteAssetTool --batch inputDir outputDir [--resize width height]" << std::endl; std::cout << " SpriteAssetTool --batch inputDir outputDir [--fit maxWidth maxHeight]" << std::endl; - std::cout << " SpriteAssetTool --batch game/assets/raw game/assets/sprites --preset tom-800x480" << std::endl; + std::cout << " SpriteAssetTool --batch src/Apps/Game/assets/raw src/Apps/Game/assets/sprites --preset tom-800x480" << std::endl; } int main(int argc, char* argv[]) diff --git a/src/Gfx/Asset/ObjLoader.cpp b/src/Core/Asset/ObjLoader.cpp similarity index 100% rename from src/Gfx/Asset/ObjLoader.cpp rename to src/Core/Asset/ObjLoader.cpp diff --git a/src/Gfx/Asset/ObjLoader.h b/src/Core/Asset/ObjLoader.h similarity index 100% rename from src/Gfx/Asset/ObjLoader.h rename to src/Core/Asset/ObjLoader.h diff --git a/src/Core/Asset/SpriteAssetLoader.cpp b/src/Core/Asset/SpriteAssetLoader.cpp new file mode 100644 index 0000000..3ac2892 --- /dev/null +++ b/src/Core/Asset/SpriteAssetLoader.cpp @@ -0,0 +1,182 @@ +#include "SpriteAssetLoader.h" +#include +#include +#include + +namespace +{ + static const char SpriteMagic[4] = { 'S', 'P', 'R', 'T' }; + static const size_t SpriteHeaderSize = 20; + static const uint32_t SpriteVersion = 1; + static const uint32_t SpriteFormatRgba8888 = 1; + static const uint64_t MaxSpritePixels = 8192ull * 8192ull; + + static bool ReadExact(std::ifstream& file, char* data, size_t size) + { + file.read(data, static_cast(size)); + return static_cast(file.gcount()) == size; + } + + static bool ReadU32LE(const std::vector& bytes, size_t offset, uint32_t& value) + { + if (offset + 4 > bytes.size()) + { + return false; + } + + value = + static_cast(bytes[offset]) | + (static_cast(bytes[offset + 1]) << 8) | + (static_cast(bytes[offset + 2]) << 16) | + (static_cast(bytes[offset + 3]) << 24); + return true; + } + + static void WriteU32LE(std::ofstream& file, uint32_t value) + { + const char bytes[4] = { + static_cast(value & 0xFF), + static_cast((value >> 8) & 0xFF), + static_cast((value >> 16) & 0xFF), + static_cast((value >> 24) & 0xFF) + }; + file.write(bytes, sizeof(bytes)); + } + + static bool CheckPixelCount(uint32_t width, uint32_t height, size_t& pixelCount) + { + if (width == 0 || height == 0) + { + return false; + } + + const uint64_t total = static_cast(width) * static_cast(height); + if (total > MaxSpritePixels || + total > static_cast(std::numeric_limits::max() / sizeof(uint32_t))) + { + return false; + } + + pixelCount = static_cast(total); + return true; + } + + static bool ReadPixels(std::ifstream& file, std::vector& pixels) + { + std::vector bytes(pixels.size() * sizeof(uint32_t), 0); + if (!ReadExact(file, reinterpret_cast(bytes.data()), bytes.size())) + { + return false; + } + + for (size_t i = 0; i < pixels.size(); ++i) + { + uint32_t value = 0; + if (!ReadU32LE(bytes, i * sizeof(uint32_t), value)) + { + return false; + } + pixels[i] = value; + } + + return true; + } +} + +namespace Asset +{ + bool SpriteAssetLoader::Load( + const std::string& path, + std::vector& pixels, + RenderData::Image& image) + { + std::ifstream file(path.c_str(), std::ios::binary); + if (!file.good()) + { + return false; + } + + std::vector header(SpriteHeaderSize, 0); + if (!ReadExact(file, reinterpret_cast(header.data()), header.size())) + { + return false; + } + + if (header[0] != SpriteMagic[0] || + header[1] != SpriteMagic[1] || + header[2] != SpriteMagic[2] || + header[3] != SpriteMagic[3]) + { + return false; + } + + uint32_t version = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t format = 0; + if (!ReadU32LE(header, 4, version) || + !ReadU32LE(header, 8, width) || + !ReadU32LE(header, 12, height) || + !ReadU32LE(header, 16, format)) + { + return false; + } + + size_t pixelCount = 0; + if (version != SpriteVersion || + format != SpriteFormatRgba8888 || + !CheckPixelCount(width, height, pixelCount)) + { + return false; + } + + std::vector loadedPixels(pixelCount, 0); + if (!ReadPixels(file, loadedPixels)) + { + return false; + } + + char trailingByte = 0; + file.read(&trailingByte, 1); + if (file.gcount() != 0) + { + return false; + } + + pixels.swap(loadedPixels); + image = RenderData::Image(pixels.data(), static_cast(width), static_cast(height)); + return image.pixels != nullptr && image.width > 0 && image.height > 0; + } + + bool SpriteAssetLoader::Save(const std::string& path, const RenderData::Image& image) + { + if (image.pixels == nullptr || image.width <= 0 || image.height <= 0) + { + return false; + } + + size_t pixelCount = 0; + if (!CheckPixelCount(static_cast(image.width), static_cast(image.height), pixelCount)) + { + return false; + } + + std::ofstream file(path.c_str(), std::ios::binary); + if (!file.good()) + { + return false; + } + + file.write(SpriteMagic, sizeof(SpriteMagic)); + WriteU32LE(file, SpriteVersion); + WriteU32LE(file, static_cast(image.width)); + WriteU32LE(file, static_cast(image.height)); + WriteU32LE(file, SpriteFormatRgba8888); + for (size_t i = 0; i < pixelCount; ++i) + { + WriteU32LE(file, image.pixels[i]); + } + + return file.good(); + } +} diff --git a/src/Core/Asset/SpriteAssetLoader.h b/src/Core/Asset/SpriteAssetLoader.h new file mode 100644 index 0000000..e432a2a --- /dev/null +++ b/src/Core/Asset/SpriteAssetLoader.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include "Image.h" + +namespace Asset +{ + class SpriteAssetLoader + { + public: + static const char* GetFileExtension() { return ".sprite"; } + + static bool Load(const std::string& path, std::vector& pixels, RenderData::Image& image); + static bool Save(const std::string& path, const RenderData::Image& image); + }; +} diff --git a/src/Gfx/Core/DepthBuffer.cpp b/src/Core/Core/DepthBuffer.cpp similarity index 100% rename from src/Gfx/Core/DepthBuffer.cpp rename to src/Core/Core/DepthBuffer.cpp diff --git a/src/Gfx/Core/DepthBuffer.h b/src/Core/Core/DepthBuffer.h similarity index 100% rename from src/Gfx/Core/DepthBuffer.h rename to src/Core/Core/DepthBuffer.h diff --git a/src/Gfx/Core/FrameBuffer.cpp b/src/Core/Core/FrameBuffer.cpp similarity index 100% rename from src/Gfx/Core/FrameBuffer.cpp rename to src/Core/Core/FrameBuffer.cpp diff --git a/src/Gfx/Core/FrameBuffer.h b/src/Core/Core/FrameBuffer.h similarity index 100% rename from src/Gfx/Core/FrameBuffer.h rename to src/Core/Core/FrameBuffer.h diff --git a/src/Gfx/Core/Renderer.cpp b/src/Core/Core/Renderer.cpp similarity index 100% rename from src/Gfx/Core/Renderer.cpp rename to src/Core/Core/Renderer.cpp diff --git a/src/Gfx/Core/Renderer.h b/src/Core/Core/Renderer.h similarity index 100% rename from src/Gfx/Core/Renderer.h rename to src/Core/Core/Renderer.h diff --git a/src/Gfx/Core/Timer.h b/src/Core/Core/Timer.h similarity index 100% rename from src/Gfx/Core/Timer.h rename to src/Core/Core/Timer.h diff --git a/src/Gfx/Draw2D/DrawContext.cpp b/src/Core/Draw2D/DrawContext.cpp similarity index 99% rename from src/Gfx/Draw2D/DrawContext.cpp rename to src/Core/Draw2D/DrawContext.cpp index b6d9317..7cc5010 100644 --- a/src/Gfx/Draw2D/DrawContext.cpp +++ b/src/Core/Draw2D/DrawContext.cpp @@ -6,7 +6,7 @@ #include "Display.h" #include -namespace Gfx +namespace Core { DrawContext::DrawContext(int32_t width, int32_t height) { diff --git a/src/Gfx/Draw2D/DrawContext.h b/src/Core/Draw2D/DrawContext.h similarity index 99% rename from src/Gfx/Draw2D/DrawContext.h rename to src/Core/Draw2D/DrawContext.h index dcc029f..296eb65 100644 --- a/src/Gfx/Draw2D/DrawContext.h +++ b/src/Core/Draw2D/DrawContext.h @@ -25,7 +25,7 @@ namespace Platform class IDisplay; } -namespace Gfx +namespace Core { class DrawContext { diff --git a/src/Gfx/Math/MathUtil.h b/src/Core/Math/MathUtil.h similarity index 100% rename from src/Gfx/Math/MathUtil.h rename to src/Core/Math/MathUtil.h diff --git a/src/Gfx/Math/Matrix4x4.h b/src/Core/Math/Matrix4x4.h similarity index 100% rename from src/Gfx/Math/Matrix4x4.h rename to src/Core/Math/Matrix4x4.h diff --git a/src/Gfx/Math/Vector2.h b/src/Core/Math/Vector2.h similarity index 100% rename from src/Gfx/Math/Vector2.h rename to src/Core/Math/Vector2.h diff --git a/src/Gfx/Math/Vector3.h b/src/Core/Math/Vector3.h similarity index 100% rename from src/Gfx/Math/Vector3.h rename to src/Core/Math/Vector3.h diff --git a/src/Gfx/Math/Vector4.h b/src/Core/Math/Vector4.h similarity index 100% rename from src/Gfx/Math/Vector4.h rename to src/Core/Math/Vector4.h diff --git a/src/Gfx/Platform/Display.h b/src/Core/Platform/Display.h similarity index 100% rename from src/Gfx/Platform/Display.h rename to src/Core/Platform/Display.h diff --git a/src/Gfx/Platform/FBDisplay.cpp b/src/Core/Platform/FBDisplay.cpp similarity index 100% rename from src/Gfx/Platform/FBDisplay.cpp rename to src/Core/Platform/FBDisplay.cpp diff --git a/src/Gfx/Platform/FBDisplay.h b/src/Core/Platform/FBDisplay.h similarity index 100% rename from src/Gfx/Platform/FBDisplay.h rename to src/Core/Platform/FBDisplay.h diff --git a/src/Gfx/Platform/SDLDisplay.cpp b/src/Core/Platform/SDLDisplay.cpp similarity index 100% rename from src/Gfx/Platform/SDLDisplay.cpp rename to src/Core/Platform/SDLDisplay.cpp diff --git a/src/Gfx/Platform/SDLDisplay.h b/src/Core/Platform/SDLDisplay.h similarity index 100% rename from src/Gfx/Platform/SDLDisplay.h rename to src/Core/Platform/SDLDisplay.h diff --git a/src/Gfx/Platform/TimeSource.h b/src/Core/Platform/TimeSource.h similarity index 100% rename from src/Gfx/Platform/TimeSource.h rename to src/Core/Platform/TimeSource.h diff --git a/src/Gfx/Rasterizer/Rasterizer.cpp b/src/Core/Rasterizer/Rasterizer.cpp similarity index 100% rename from src/Gfx/Rasterizer/Rasterizer.cpp rename to src/Core/Rasterizer/Rasterizer.cpp diff --git a/src/Gfx/Rasterizer/Rasterizer.h b/src/Core/Rasterizer/Rasterizer.h similarity index 100% rename from src/Gfx/Rasterizer/Rasterizer.h rename to src/Core/Rasterizer/Rasterizer.h diff --git a/src/Gfx/Rasterizer/TriangleRasterizer.cpp b/src/Core/Rasterizer/TriangleRasterizer.cpp similarity index 100% rename from src/Gfx/Rasterizer/TriangleRasterizer.cpp rename to src/Core/Rasterizer/TriangleRasterizer.cpp diff --git a/src/Gfx/Rasterizer/TriangleRasterizer.h b/src/Core/Rasterizer/TriangleRasterizer.h similarity index 100% rename from src/Gfx/Rasterizer/TriangleRasterizer.h rename to src/Core/Rasterizer/TriangleRasterizer.h diff --git a/src/Gfx/RenderData/BitmapFont.h b/src/Core/RenderData/BitmapFont.h similarity index 100% rename from src/Gfx/RenderData/BitmapFont.h rename to src/Core/RenderData/BitmapFont.h diff --git a/src/Gfx/RenderData/BoundingBox.h b/src/Core/RenderData/BoundingBox.h similarity index 100% rename from src/Gfx/RenderData/BoundingBox.h rename to src/Core/RenderData/BoundingBox.h diff --git a/src/Gfx/RenderData/Color.h b/src/Core/RenderData/Color.h similarity index 100% rename from src/Gfx/RenderData/Color.h rename to src/Core/RenderData/Color.h diff --git a/src/Gfx/RenderData/Image.h b/src/Core/RenderData/Image.h similarity index 100% rename from src/Gfx/RenderData/Image.h rename to src/Core/RenderData/Image.h diff --git a/src/Gfx/RenderData/SpriteRegion.h b/src/Core/RenderData/SpriteRegion.h similarity index 100% rename from src/Gfx/RenderData/SpriteRegion.h rename to src/Core/RenderData/SpriteRegion.h diff --git a/src/Gfx/RenderData/Tilemap.h b/src/Core/RenderData/Tilemap.h similarity index 100% rename from src/Gfx/RenderData/Tilemap.h rename to src/Core/RenderData/Tilemap.h diff --git a/src/Gfx/RenderData/Triangle.h b/src/Core/RenderData/Triangle.h similarity index 100% rename from src/Gfx/RenderData/Triangle.h rename to src/Core/RenderData/Triangle.h diff --git a/src/Gfx/Scene/Camera.cpp b/src/Core/Scene/Camera.cpp similarity index 100% rename from src/Gfx/Scene/Camera.cpp rename to src/Core/Scene/Camera.cpp diff --git a/src/Gfx/Scene/Camera.h b/src/Core/Scene/Camera.h similarity index 100% rename from src/Gfx/Scene/Camera.h rename to src/Core/Scene/Camera.h diff --git a/src/Gfx/Scene/Mesh.h b/src/Core/Scene/Mesh.h similarity index 100% rename from src/Gfx/Scene/Mesh.h rename to src/Core/Scene/Mesh.h diff --git a/src/Gfx/Scene/Model.h b/src/Core/Scene/Model.h similarity index 100% rename from src/Gfx/Scene/Model.h rename to src/Core/Scene/Model.h diff --git a/src/Gfx/Scene/Transform.h b/src/Core/Scene/Transform.h similarity index 100% rename from src/Gfx/Scene/Transform.h rename to src/Core/Scene/Transform.h diff --git a/src/Gfx/Scene/Vertex.h b/src/Core/Scene/Vertex.h similarity index 100% rename from src/Gfx/Scene/Vertex.h rename to src/Core/Scene/Vertex.h diff --git a/src/Gfx/Shading/BlinnPhongShader.cpp b/src/Core/Shading/BlinnPhongShader.cpp similarity index 100% rename from src/Gfx/Shading/BlinnPhongShader.cpp rename to src/Core/Shading/BlinnPhongShader.cpp diff --git a/src/Gfx/Shading/BlinnPhongShader.h b/src/Core/Shading/BlinnPhongShader.h similarity index 100% rename from src/Gfx/Shading/BlinnPhongShader.h rename to src/Core/Shading/BlinnPhongShader.h diff --git a/src/Gfx/Shading/ShaderTypes.h b/src/Core/Shading/ShaderTypes.h similarity index 100% rename from src/Gfx/Shading/ShaderTypes.h rename to src/Core/Shading/ShaderTypes.h diff --git a/src/Rasterizer/SpriteRasterizer.cpp b/src/Rasterizer/SpriteRasterizer.cpp index 84a30db..836a25f 100644 --- a/src/Rasterizer/SpriteRasterizer.cpp +++ b/src/Rasterizer/SpriteRasterizer.cpp @@ -1,5 +1,5 @@ #include "SpriteRasterizer.h" -#include "../Core/FrameBuffer.h" +#include "FrameBuffer.h" #include "../RenderData/Image.h" #include "../RenderData/Sprite.h"