IMX6U-Game/docs/实训日记_LightGame.md

42 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 实训日记 — IMX6U 光敏平台跳跃游戏
## 第 1 天7 月 6 日,周一):项目启动与选题
今天就要开始这个嵌入式的专业方向综合实训了。选题环节我几乎没有犹豫,直接定了做一个跑在 IMX6ULL 板子上的 2D 游戏——一方面自己之前用 C++ 写过一个跑在 CPU 上的软光栅小 demo也用 Unity 做过一些小项目,对图形和游戏这一套流程比较熟;另一方面 IMX6ULL 没有 GPUOpenGL 那一套走不通,反倒正好把之前软光栅的思路搬过来。翻了翻板子附带的手册,注意到板载有一颗 AP3216C 光敏传感器,就想干脆让传感器直接参与游戏机制,做一个"光照驱动"的平台跳跃:亮着的时候平台出现、暗下来时通道打开。今天大部分时间在读任务书、翻板载外设列表和写选题说明,正式的代码一行没写,但方向定下来之后心里踏实了不少。
## 第 2 天7 月 7 日,周二):交叉编译环境与双后端抽象
今天开始搭开发环境。先在 Ubuntu 上装了 `gcc-arm-linux-gnueabihf` 工具链,写了一份 `cmake/toolchain-arm-linux-gnueabihf.cmake`,让 CMake 能一键切到 ARM 交叉编译。考虑到板子上直接调试太慢,日常肯定要在 PC 上跑所以早早就决定把显示后端做成可切换的PC 端走 SDL2 开窗口ARM 端直接写 `/dev/fb0`。这就是 `IDisplay` 这个纯虚接口的由来,通过 CMake 里的 `USE_FRAMEBUFFER` 选项在编译期二选一,核心渲染代码完全不知道自己底下是 SDL 还是 framebuffer。傍晚把两端的 hello world 都点亮了——PC 上开出一个纯色窗口,板子的 LCD 也刷出了预期的彩色横条,虽然还只是最基础的像素输出,但双端并行的开发节奏算是搭起来了。
## 第 3 天7 月 8 日周三Core 底层库的移植与重写
之前那份软光栅是 3D 的,齐次坐标、透视除法、深度缓冲一应俱全,这次做 2D 游戏根本用不到。今天的主要工作是把 `FrameBuffer`、`DrawContext`、`Timer` 这几个通用模块从旧项目里拆出来重写,只保留 2D 需要的部分。一个关键决定是:板载 LCD 是 RGB565 的,我把 `FrameBuffer` 的像素类型从原来的 `uint32_t` 换成了 `uint16_t`Sprite atlas 也统一用 RGBA55511-bit alpha这样一帧 1024×600 只占约 1.2 MB比 32bpp 直接省一半带宽,对没有 GPU 的板子来说很重要。`Timer` 采用余数累积法维持 30 FPS避免 `int` 除法舍入导致的帧率长期漂移。热路径规则也在今天正式立了下来核心逻辑禁止浮点、禁止堆分配、SDL 相关的代码只允许出现在 `Core/Platform/` 目录,其他地方一概不许 `#include SDL.h`
## 第 4 天7 月 9 日,周四):主循环与 Physics2D 亚像素积分
今天开始搭游戏本体。主循环结构比较常规:`poll_events → update(dt) → render → present``Timer` 负责把变化的真实时间切成固定步长交给 `update`。真正花时间的是 `Physics2D`——由于禁止浮点,速度、加速度全部用 Q8 定点存储,每帧把速度累加到 `sub_pixel_x_ / sub_pixel_y_` 亚像素累加器上,只有跨过整像素时对象位置才真正变化。这样即便每帧位移不足 1 像素,累积几帧也能得到平滑的移动效果,跳跃初速度、重力、最大下落速度都能用整数精细调参。中间踩了个小坑:我一开始把 `sub_pixel_x_ / sub_pixel_y_` 挂在 `GameObject` 上,写着写着发现所有物理体都在重复同一份亚像素状态,才反应过来这两个字段本来就应该属于 `Physics2D` 组件,物理逻辑才更内聚。返工不算大,但也提醒自己接下来在划分类职责时要更谨慎一点。
## 第 5 天7 月 10 日周五PlayerController 与 Camera2D
一周的最后一天,重点是让角色真正"活"起来。`PlayerController` 用状态机组织Idle、Running、Jumping、Falling、Dead 五个状态之间根据输入和物理状态互相跳转。跳跃做成了"二段可变高"——按住跳键时持续施加上升力,松开或者达到最高高度切换到 Falling这样短按能小跳、长按能高跳手感一下就出来了。尖刺伤害判定选择了"tile 顶部向下 14 px"这样一个矩形区域,而不是整个 tile避免玩家轻轻蹭到侧面就死掉。相机跟随算法花的时间比想象中久直接线性插值会有浮点改成 `dx >> smooth_shift_` 做整数右移,但发现 `dx` 只剩 1 时右移变 0相机就再也追不上角色了只好加一个 ±1 的兜底位移。周五收工的时候,小人已经能在临时关卡里跑跳、被尖刺秒杀、然后从头再来,虽然只是最裸的白盒关卡,但看着还是很有成就感的。周末先歇两天。
## 第 6 天7 月 13 日周一Level / Room 与关卡编辑器
回来第一天,先把关卡数据结构定下来。`Level` 分三层foreground碰撞层、background装饰层、objects平台、门、检查点等交互对象房间划分用 `RoomGrid`,每个房间对应一个 `RoomBounds``Camera2D` 在房间边界内 clamp切换房间时相机做一次瞬移形成类似 Metroidvania 的分屏效果。但用代码硬写 tile 数组实在太痛苦了,改一个空气块要跑一次编译、烧一次板子,效率完全不能忍。于是今天下午专门做了一个 PC 端的 `LevelEditor`:用 ImGui 显示 tile palette鼠标左键刷 tile、右键擦除F1 切换编辑器窗口,保存直接写回源文件的关卡数据数组。这个编辑器只在 PC 且 Debug 构建下编译,靠 CMake 里的 `LIGHTGAME_EDITOR_GUARD` 宏隔离ARM Release 构建里连一个字节的 ImGui 都不会带进去,保证板端二进制体积干净。
## 第 7 天7 月 14 日周二AP3216C 攻坚
今天正式接光敏传感器,也是这个项目里最折腾的一天。板子出厂 dts 里 AP3216C 的驱动是加载好的,`/dev/ap3216c` 字符设备也在,我按最直觉的方式 `open + read`,结果一直返回 `EINVAL`。翻了半天板载 SDK 里的驱动源码也没定位到具体是哪一步出的问题,各种参数组合都试过,就是拿不到稳定的读数。走到快中午的时候干脆决定绕开驱动:既然板子上 i2c 总线是通的,那就直接 `open("/dev/i2c-0")`,用 `ioctl(fd, I2C_SLAVE_FORCE, 0x1E)` 抢占 AP3216C 的地址,然后手写寄存器——`SysConfig = 0x01` 打开 ALS-only 模式,`AlsConfig = 0x30` 选最大增益 323 lux 满量程,再从寄存器 `0x0C / 0x0D` 读两字节合成 16 位光照原始值。第一次稳定看到 lux 数值随着我用手挡光的动作实时变化时,一整天的憋闷才散了。收工时把这一坑详细记进了项目笔记,免得日后忘了为什么要用 `I2C_SLAVE_FORCE` 而不是普通的 `I2C_SLAVE`
## 第 8 天7 月 15 日,周三):光照与游戏机制的耦合
有了稳定的 lux 输入,今天把光敏和游戏机制真正串起来。原始 lux 抖动不小,先在 `LightGameApp` 里加了一层 Q8 定点 EMA 滤波,`smoothed += (target - smoothed) >> 4`,也就是 α = 1/16截止频率约 0.3 Hz、响应时间约 0.7 s既能压掉手抖引起的高频噪声也不至于慢到玩家察觉不到光变化。上层的 `LightEffectSystem` 定义了三类对象规则:`LightPlatform` 光照高于阈值时 `solid`、`ShadowPlatform` 光照低于阈值时 `solid`、`Door` 在特定区间内开启,一份规则表覆盖了几乎所有关卡玩法。地形几何显隐用 `TileLightRule` 加双缓冲实现——`original_tiles_` 永远保留关卡原始数据,`tile_buffer_` 是当前光照下重刷过的副本,光照跨越阈值时才重新生成一次 `tile_buffer_`,避免每帧都全图刷新。最后在 HUD 上加了一条光照条实时显示 smoothed luxPC 端还挂了 W/S 手动调光通道方便调试。傍晚试玩的时候,看着小人跟着我用手挡光跳上跳下、又跟着灯光变化推开一扇门,那种"环境即输入"的味道终于对上了。
## 第 9 天7 月 16 日,周四):板端联调与性能打磨
离答辩还剩一天今天必须把所有功能在板子上跑一遍。第一个冒出来的问题是帧率PC 端 30 FPS 稳如老狗,板子上从一个大房间进另一个大房间时会掉到 20+ FPS。在 `DrawContext::present` 前后加了 `gettimeofday` 打时间戳定位瓶颈,发现 `draw_tilemap_shaded` 对每个可见 tile 都做了亮度整数缩放Q7 乘法 + 位移),而当时相机可视区域外的 tile 也在参与计算。改成先按 `Camera2D` 视口裁剪、只把落在屏幕内的 tile 送进 shading 循环之后,帧率立即稳回 30 FPS甚至还有余量。第二个是 HUD 半透明RGB565 没有 alpha 通道,暂停画面的半透明遮罩暂时只能退化成不透明黑色,试了 dither 但边缘噪点比较明显,最终决定先按不透明处理,报告的"局限与展望"里如实记下来。晚上把主线路径又跑了两遍:起点出发、经过检查点、光照驱动开门、切换房间、被尖刺送回检查点,一切符合预期。收工时看了眼时间,离明天答辩还有大概 18 个小时。
## 第 10 天7 月 17 日,周五):报告收尾与答辩
今天就是答辩日了。上午把 `report_v2.md` 从大纲填成完整报告中间反复对着源码核对每一处技术细节Q8 EMA 的 α 值、AP3216C 的寄存器地址、亚像素累加公式、CMake 里的编译选项、`I2C_SLAVE_FORCE` 的宏值,尽量不让报告里出现一个和代码对不上的数字。下午答辩前又把 demo 视频重新录了一遍,特意在暗光和亮光两种环境下各跑一遍完整通关流程,把"环境即输入"这条主线讲清楚。回望这 10 天,从最开始定下"用 IMX6ULL + 软光栅 + 光敏传感器做游戏"到双后端抽象、Core 库移植、Physics2D 亚像素积分、状态机角色控制、关卡编辑器,再到 AP3216C 那一天的独自踩坑和光照耦合出效果的傍晚,每一步都能对应到具体的代码和当时的想法。写文档这类事情我一直不太喜欢,但真的一行行把这 10 天梳理出来的时候,那种"东西是自己一点点搭起来的"的踏实感确实不太一样。答辩就在下午,剩下的交给现场发挥吧。