Go to file
SepComet 46c76ec7fc 修复 LightGame 多项 gameplay bug 并完善光敏渲染体系
- 出生点校验:load_level() 加载后 assert 验证 spawn point 是否站在固体地面且不穿墙
- Checkpoint 系统:LevelLoader 为 Checkpoint Trigger 注册 checkpoint 位置,check_triggers() 正确激活 checkpoint,respawn() 使用最后激活的 checkpoint
- Death 触发器:PlayerController 新增 kill(),TriggerAction::Death 正确触发死亡
- Tilemap 光敏 tile:新增 TileLightRule 与 mutable tile buffer,LightEffectSystem::update_tilemap() 按光照条件动态显示/隐藏 tilemap 中的 tile
- GameObject 渲染:LevelLoader 为所有对象分配 tile atlas sprite(金币、门、陷阱、平台、checkpoint 等不再隐形)
- 光敏对象双状态:LightPlatform / ShadowPlatform / Door 均支持 on/off(或 open/closed)两种 sprite 切换,should_render() 始终返回 true,视觉状态由 sprite 承担
- 渲染层级:LevelRenderer 最后绘制玩家,确保玩家始终在最上层
2026-06-11 11:33:36 +08:00
assets 收口字体渲染路径以降低 atlas 体积并直接输出 RGB565 2026-06-09 08:20:38 +08:00
cmake init 2026-06-05 17:55:22 +08:00
docs 重构键盘输入为 IKeyboardState 接口,消除 App 层对 SDL 的直接依赖 2026-06-10 15:22:30 +08:00
libs/Win init 2026-06-05 17:55:22 +08:00
src 修复 LightGame 多项 gameplay bug 并完善光敏渲染体系 2026-06-11 11:33:36 +08:00
tests 搭建 LightGame 游戏框架 2026-06-09 12:25:01 +08:00
tools 为 LightGame 提供素材转换工具和转换后的头文件 2026-06-11 09:28:20 +08:00
.gitattributes init 2026-06-05 17:55:22 +08:00
.gitignore 统一 2D 渲染链路以消除格式分叉和热路径中转 2026-06-08 18:32:04 +08:00
CMakeLists.txt 重构键盘输入为 IKeyboardState 接口,消除 App 层对 SDL 的直接依赖 2026-06-10 15:22:30 +08:00
CMakeSettings.json init 2026-06-05 17:55:22 +08:00
README.md 更新文档 2026-06-09 10:39:10 +08:00
package-lock.json 搭建 LightGame 游戏框架 2026-06-09 12:25:01 +08:00

README.md

IMX6U-Game

一个从 CPU 软光栅化渲染器向嵌入式游戏图形库迁移的项目。

核心目标是在 IMX6UARM Cortex-A7无 GPU 或极弱 GPU上运行纯 CPU 渲染的 2D/3D 图形。为此,代码保持 C++11 兼容显示后端可切换SDL2 窗口 vs Linux Framebuffer以支持跨平台开发和嵌入式部署。

编译目标与验证目的

本项目支持三套编译目标,分别对应开发流程的不同阶段:

目标平台 显示后端 验证目的
Windows (x86/x64) SDL2 窗口 验证渲染逻辑、算法正确性、快速迭代调试
Linux x86_64 SDL2 窗口 验证 Linux 兼容性、CMake 构建、SDL2 系统依赖
ARM Linux (交叉编译) SDL2 或 /dev/fb0 最终在 IMX6U 开发板上运行的版本;优先目标以后续实际部署后端为准

为什么分三套?

  • Windows 编译:开发主力机通常是 Windows有 IDE 调试、有图形窗口,渲染算法对不对一眼就能看到。这个阶段完全不关心嵌入式细节。
  • Linux x86 编译:验证代码在 GCC/Clang 下有无警告、CMake 配置是否跨平台、系统 SDL2 依赖是否正确。很多嵌入式工具链的问题在 x86 Linux 上就能提前暴露。
  • ARM 交叉编译:最终在 IMX6U 上跑。若目标板使用 SDL2则 SDL2 仅作为显示/输入适配层,时间由独立 Platform::ITimeSource 提供,核心渲染仍按 CPU framebuffer + 一次性提交设计;如需极简依赖,也保留 /dev/fb0 后端作为对照。

开发规范与性能红线

IMX6U 运行时性能预算较紧,后续开发必须遵守 docs/DEVELOPMENT_GUIDELINES.md。如果目标板使用 SDL2仍然要把 SDL2 限制在平台适配层,核心逻辑和渲染热路径不直接依赖 SDL

  • 核心逻辑和热路径默认不新增 float / double,需要小数时使用项目统一定点数;只在显示、调试、导入导出等边界层转换成浮点。
  • 主循环、每对象、每三角形/顶点/像素级代码中不得频繁创建 std::vector / std::string 等动态分配容器,应复用缓冲或使用固定容量结构。
  • PC/SDL 版本用于调试验证,不能把调试便利写法扩散到 ARM release 核心路径。
  • SDL2 后端只做显示、输入和最终 framebuffer 提交;时间源独立于显示后端,不在核心渲染/游戏逻辑中直接调用 SDL API。
  • 新增核心代码前按规范文档中的检查清单自查。

应用层与图形库拆分

项目后续按“两个游戏 + 一个启动器 + 一套底层图形库”组织,详细边界见 docs/APP_AND_CORE_ARCHITECTURE.md

  • Core / 底层图形库:只提供 framebuffer、SDL2/fb0 平台适配、独立时间源、基础绘制能力例如点、线、矩形、四边形、sprite、bitmap font、tilemap。
  • Apps/Launcher:负责游戏选择、全局设置和启动流程。
  • Apps/GameAApps/GameB:分别维护自己的游戏规则、状态、资源和渲染调用。
  • Shared:只放应用层共享但不属于底层图形库的 UI、存档、配置、资源索引等。
  • 依赖方向必须保持 Apps -> Shared -> Core -> Platform,底层图形库不能反向依赖具体游戏。

初期推荐单进程多应用模式Launcher、GameA、GameB 共用同一个 SDL2 初始化、framebuffer 和主循环,通过统一 IApp 接口切换当前应用。

构建说明

通用前置

# 克隆仓库后,确保子目录完整
cd IMX6U-Game

WindowsVisual Studio / MSVC

仓库已自带 SDL2 开发库(libs/Win/SDL2),无需额外安装。

cmake -B build-win .
cmake --build build-win --config Release

只构建某个 App

cmake --build build-win --config Release --target IMX6U-Game
cmake --build build-win --config Release --target IMX6U-Demo

构建 IMX6U-Game 时会自动重新生成 Tom 的 atlas 头文件;也可以单独执行:

cmake --build build-win --config Release --target GenerateTomAtlasHeader

运行:

./build-win/Release/IMX6U-Game.exe

可选帧率档位:

./build-win/Release/IMX6U-Game.exe --fps 30
./build-win/Release/IMX6U-Game.exe --fps 45
./build-win/Release/IMX6U-Game.exe --fps 60

当前只接受 304560 三档。主循环从 Platform::ITimeSource 读取单调整数毫秒时间,由 Core::Timer 生成固定步长 tick并通过 33/33/3422/22/22/22/2316/17/17 这类整数节奏逼近对应目标帧率,避免核心时间源依赖 float 秒。

Linux x86_64Ubuntu / WSL2

需要系统 SDL2

sudo apt-get install libsdl2-dev libsdl2-image-dev cmake g++

构建:

cmake -B build-linux .
cmake --build build-linux

只构建某个 App

cmake --build build-linux --target IMX6U-Game
cmake --build build-linux --target IMX6U-Demo

构建 IMX6U-Game 时会自动重新生成 Tom 的 atlas 头文件;也可以单独执行:

cmake --build build-linux --target GenerateTomAtlasHeader

运行:

./build-linux/IMX6U-Game

ARM 交叉编译IMX6U

需要交叉编译工具链:

sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf

本项目 ARM 端保留两类后端:

  • SDL2 后端目标是后续游戏主路径SDL2 负责显示、输入和最终 framebuffer 提交,时间源使用独立的 Platform::ITimeSource
  • Framebuffer 后端:作为极简依赖和显示通路对照测试。

构建Framebuffer 对照后端):

cmake -B build-arm-fb \
    -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-arm-linux-gnueabihf.cmake \
    -DUSE_FRAMEBUFFER=ON .
cmake --build build-arm-fb

说明单配置生成器Makefile/Ninja默认使用 Release 构建ARM / framebuffer 性能测试必须确认 CMAKE_BUILD_TYPE=Release,否则逐像素绘制和 /dev/fb0 提交会因未优化构建出现数量级偏差。

注意Tom 的 atlas 头文件生成工具是主机侧离线工具,依赖 PC/Linux 主机上的 SDL2_image主机构建 IMX6U-Game 时会自动执行ARM 交叉编译过程不会执行它。如果修改了 src/Apps/Game/assets/raw/ 里的 PNG必须先在 Windows 或 Linux x86 构建目录执行 GenerateTomAtlasHeader 或构建一次 IMX6U-Game,再进行 ARM 交叉编译。ARM 构建只消费已经生成好的 src/Apps/Game/generated/tom_atlas.h

构建SDL2 后端,要求工具链/sysroot 可找到目标板 SDL2 开发库):

cmake -B build-arm-sdl \
    -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-arm-linux-gnueabihf.cmake \
    -DUSE_FRAMEBUFFER=OFF .
cmake --build build-arm-sdl

部署到开发板:

scp build-arm-sdl/IMX6U-Game root@imx6u:/tmp/
# 或部署 framebuffer 对照版本:
scp build-arm-fb/IMX6U-Game root@imx6u:/tmp/

板子上运行:

/tmp/IMX6U-Game

Framebuffer 对照版本可能需要 root 权限访问 /dev/fb0。SDL2 版本是否需要额外环境变量或权限,取决于目标板 SDL2 视频驱动和显示栈配置。

资源转换工具

项目运行时不依赖 PNG/TTF 解码库。图片和字体资源在离线阶段转换成 C++ 头文件,但运行时格式按资源类型区分:

  • sprite / atlasuint16_t RGBA55515-bit R/G/B + 1-bit A
  • bitmap fontuint8_t 1-bit mask8 个像素打包为 1 个字节)

sprite 运行时像素格式统一为 RGBA555116-bit内部 backbuffer 统一为 RGB565;透明仅支持 1-bit alpha testA=0 跳过,A=1 覆写)。

当前转换工具位于 tools/,需要 Python 和 Pillow

pip install pillow

Sprite 转换

普通 PNG sprite 使用 tools/png_to_header.py 转换:

python tools/png_to_header.py assets/sprite/test_sprite.png assets/sprite/test_sprite.h test_sprite

生成的头文件会包含:

test_sprite_width
test_sprite_height
test_sprite_pixels  // uint16_t RGBA5551 数组

透明像素转换为 RGBA5551 后alpha 仅 1-bitA=0 透明,A=1 不透明)。当前 demo 通过 RenderData::Image 的 color_key 机制跳过指定颜色值。

Tom 游戏资源使用 SpriteAssetTool 从固定目录 src/Apps/Game/assets/raw/ 读取原始 PNG一步生成 atlas 头文件。主机侧构建 IMX6U-Game 时 CMake 会自动执行 GenerateTomAtlasHeader;也可以单独执行:

cmake --build build-win --config Release --target GenerateTomAtlasHeader

生成结果位于:

src/Apps/Game/generated/tom_atlas.h

该头文件包含 tom_atlas_pixels 和每张图的 RenderData::Sprite,因此板端运行 Tom 游戏时不需要额外部署图片资源文件。尺寸规则、region 名称和 PNG 文件名统一记录在 src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cpp 顶部的 Sources 表里CMake 不再重复维护每张 PNG 的路径。

assets/sprite/ 用于存放测试用 PNG sprite 源文件及转换后的头文件Tom 主游戏不依赖它。Tom 游戏的 atlas 资源由 src/Apps/Game/tools/asset_pipeline/SpriteAssetTool.cppsrc/Apps/Game/assets/raw/ 读取原始 PNG 生成。

Bitmap Font 转换

像素字体图集使用 tools/gen_font_atlas.py 生成:

python tools/gen_font_atlas.py assets/font

脚本默认优先读取:

assets/font/ndrtyyl-prqyys-undertale-hebrew-uppercase.ttf

输出:

assets/font/font_atlas.png
assets/font/font_atlas.h

字体范围为 ASCII 32~126按 16 列排列。生成端会先把字体 alpha 阈值化为 0/255再按整张 atlas 逐行打包成 row-major、MSB-first 的 1-bit mask

  • font_atlas_mask[] 中每个 uint8_t 表示连续 8 个像素
  • bit=1 表示该像素需要绘制
  • bit=0 表示跳过

运行时 DrawContext::draw_text 不再读取字体 atlas 的 RGB/A 颜色,而是直接把 mask 中 bit=1 的像素写成调用方传入文字颜色对应的 RGB565

显示后端架构

显示层通过抽象接口 Platform::IDisplay 与渲染逻辑解耦。后续应用层和图形库拆分后,Platform 会收敛到 Core 的平台适配层:

┌──────────────────────────────────────────────┐
│ AppsLauncher / GameA / GameB               │
├──────────────────────────────────────────────┤
│ SharedUI、存档、配置、资源索引              │
├──────────────────────────────────────────────┤
│ CoreDrawContext、FrameBuffer、Draw2D、Math   │
├──────────────────────────────────────────────┤
│ Platform::IDisplay                           │
│  - SDLDisplay : SDL2 显示/输入适配            │
│  - ITimeSource: 单调整数毫秒时间源            │
│  - FBDisplay  : /dev/fb0 对照后端             │
└──────────────────────────────────────────────┘

切换显示后端不应影响应用层和核心绘制逻辑;当前 CMake 通过 USE_FRAMEBUFFER 在 SDL2 与 framebuffer 后端间切换。

目录结构

IMX6U-Game/
├─ cmake/
│  └─ toolchain-arm-linux-gnueabihf.cmake  # ARM 交叉编译工具链
├─ libs/
│  └─ Win/
│     ├─ SDL2/                             # Windows 用 SDL2 库(头文件 + lib + DLL
│     └─ SDL_image/                        # SDL2_image 库(头文件 + lib + DLL
├─ assets/
│  ├─ font/                                # 像素字体源文件、font_atlas.png、font_atlas.h
│  └─ sprite/                              # PNG sprite 源文件及转换后的头文件
├─ tools/
│  ├─ gen_font_atlas.py                    # TTF -> bitmap font atlas/header
│  └─ png_to_header.py                     # PNG -> uint16_t RGBA5551 header
├─ src/
│  ├─ Core/                                 # 底层图形库:可复用、无具体游戏规则
│  │  ├─ Draw2D/                           # DrawContext 统一绘制入口
│  │  ├─ Core/                             # FrameBuffer、DepthBuffer、Renderer、Timer
│  │  ├─ Math/                             # 向量、矩阵、数学工具
│  │  ├─ Rasterizer/                       # 线段、三角形光栅化
│  │  ├─ RenderData/                       # Color、Image、Sprite、Tilemap、BitmapFont 等数据结构
│  │  ├─ Scene/                            # Camera、Transform、Mesh、Model
│  │  ├─ Shading/                          # BlinnPhongShader 等着色器
│  │  ├─ Platform/                         # IDisplay、SDLDisplay、FBDisplay、ITimeSource
│  │  │                                    # IAudioInput/Output、IButtonInput、IPointerInput
│  │  │                                    # ALSA / evdev / SDL2 后端、DefaultHardware
│  │  └─ Asset/                            # ObjLoader、SpriteAssetLoader
│  ├─ Apps/
│  │  ├─ Demo/                             # 2D sprite/tilemap 性能测试入口
│  │  └─ Game/                             # Tom 游戏
│  │     ├─ Main.cpp                       # 游戏入口
│  │     ├─ generated/tom_atlas.h          # 自动生成的 atlas 头文件
│  │     ├─ tools/asset_pipeline/          # SpriteAssetTool离线资源转换
│  │     └─ src/
│  │        ├─ app/                        # TomGameApp
│  │        ├─ audio/                      # VoiceEffect、VoicePlayer、VoiceRecorder
│  │        ├─ components/                 # SpriteAnimator
│  │        ├─ scenes/                     # TomScene
│  │        └─ systems/                    # AnimationSystem
│  └─ test_fb.cpp                          # 独立 fb 测试(最小示例)
├─ docs/
│  ├─ DEVELOPMENT_GUIDELINES.md            # IMX6U 性能红线
│  ├─ APP_AND_CORE_ARCHITECTURE.md          # 应用层与图形库分层
│  └─ CONVENTIONS.md                       # 坐标系、矩阵、深度等数学约定
├─ CMakeLists.txt
└─ README.md

模块说明

Draw2D

  • DrawContext:统一绘制入口,封装 FrameBuffer、DepthBuffer、Rasterizer、TriangleRasterizer对外提供 clearclear_colorclear_depthdraw_linedraw_triangledraw_spritedraw_sprite_exdraw_textdraw_tilemapfill_rectpresent 接口

RenderData

  • Image:通用图像数据结构,持有 const void* pixelsPixelFormat(当前统一 RGBA5551),支持 color_key 透明跳过
  • Sprite:描述 atlas 中的子区域,通过 const Image* atlas 引用源图,是对外 sprite 绘制单位
  • Tilemap:使用 uint16_t tile id 引用 atlas 中的固定大小 tileEmptyTile (0xFFFF) 表示空 tile
  • BitmapFontbitmap 字体数据,持有 uint8_t* mask_bitsrow-major、MSB-first 1-bit mask支持 ASCII 范围字符绘制
  • ColorRGBA8888 颜色值用于绘制接口参数和调试sprite 运行时像素格式仍为 RGBA5551

Core

  • FrameBufferCPU 侧 RGB565 颜色缓冲,渲染结果先写在这里
  • DepthBuffer:深度测试用 Z-buffer
  • Renderer:渲染器辅助工具
  • Timer:整数毫秒固定步长 tick 生成器,支持 30/45/60 FPS 档位和每帧剩余时间计算

Math

  • 通用数学类型:Vector2/3/4Matrix4x4
  • 纯头文件实现,无动态分配

Rasterizer

  • RasterizerBresenham 线段光栅化,入口做快速全屏可见性检查,屏幕内走 set_pixel_unsafe 快路径,屏幕外走 Cohen-Sutherland 裁剪
  • TriangleRasterizer:扫描线三角形填充 + 定点深度插值(增量式整数边缘函数,内层循环无 float 运算)

Platform

  • IDisplay:显示后端抽象,解耦渲染与输出
  • SDLDisplaySDL2 后端PC 调试和 IMX6U SDL2 目标路径共用这一类适配思想
  • FBDisplay/dev/fb0 对照后端,用于极简显示通路验证
  • ITimeSource / SteadyTimeSource独立时间源接口与单调时钟实现Linux/IMX6U 使用 clock_gettime(CLOCK_MONOTONIC)Windows 使用 std::chrono::steady_clockDisplay 不再承担计时职责
  • IAudioInput / IAudioOutput:音频输入/输出抽象接口SDL2 和 ALSA 两套后端
  • IButtonInput按键输入抽象接口SDL2 键盘和 Linux evdev 两套后端
  • IPointerInput:触摸/指针输入抽象接口SDL2 和 Linux evdev 两套后端
  • DefaultHardware:根据编译配置自动选择默认音频、按键、指针后端的 typedef

Framebuffer 性能说明

FBDisplay 是直接写 /dev/fb0 的对照后端。当前实现会从 CPU 侧 RGB565 FrameBuffer 提交到系统 framebuffer并针对常见像素格式提供快速路径

  • RGB565直接行拷贝
  • ARGB8888 / XRGB8888 类 32bpp使用专用 RGB565 -> 32-bit 转换;
  • 其他格式:走统一 display conversion。

板端性能测试必须使用 Release 构建。一次测试中,未优化 ARM 构建曾导致 Frame:81ms / Present:69ms,开启 Release 后同一轻量 2D demo 可达到约 76 FPS。该结果说明 /dev/fb0 整屏提交仍是关键热点,但构建类型会极大影响结论。后续优化优先级:

  1. 对 32-bit fb0 增加更快的 RGB565 -> 目标格式 批量转换;
  2. 2D 场景使用 dirty rect / 局部提交,避免每帧整屏写入;
  3. 避免无 3D 内容时清理 depth buffer
  4. 对 tile/sprite 增加不透明行拷贝、预转换资源或专用批处理路径。

当前状态与后续

已完成:

  • 可旋转立方体的 3D 渲染MVP 变换、背面剔除、扫描线填充、深度测试)
  • 双平台显示后端SDL2 / Framebuffer
  • 离线资源转换工具PNG sprite -> RGBA5551 C++ 头文件,像素字体 -> bitmap atlas/header
  • 基础 2D sprite、atlas 子图 sprite、bitmap font 文本绘制和 tilemap 视口绘制,当前 demo 显示 FPS 文本、测试 sprite 和小型滚动 tilemap
  • Core 目录规范化,代码收敛到 src/Core/
  • Core::DrawContext 统一绘制入口,封装现有绘制能力
  • C++11 兼容代码
  • CMake 跨平台构建

待完成(按优先级):

  1. FrameBuffer / FBDisplay 性能优化(目标像素格式 backbuffer、dirty rect、专用 tile/sprite 快路径、NEON
  2. 应用层拆分Launcher / GameA / GameB / Shared和统一 IApp 主循环
  3. SDL2 输入抽象(键盘/触摸/按键状态快照)
  4. Core 基础 2D 绘制接口(矩形、四边形、继续完善 Sprite/Text/Tilemap 的批处理和专用快路径)
  5. 纹理贴图、OBJ 模型加载与完整光照

说明

  • 代码标准:C++11,确保兼容嵌入式老工具链
  • test_fb.cpp 是一个完全独立的 Linux framebuffer 最小测试,不依赖项目其他代码,用于快速验证板子显示通路
  • Windows、Linux x86 和目标板 SDL2 路径共享 SDLDisplay 适配思想;FBDisplay 保留为 framebuffer 对照后端