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

10 KiB
Raw Permalink Blame History

实训日记 — 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 游戏根本用不到。今天的主要工作是把 FrameBufferDrawContextTimer 这几个通用模块从旧项目里拆出来重写,只保留 2D 需要的部分。一个关键决定是:板载 LCD 是 RGB565 的,我把 FrameBuffer 的像素类型从原来的 uint32_t 换成了 uint16_tSprite 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 → presentTimer 负责把变化的真实时间切成固定步长交给 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,每个房间对应一个 RoomBoundsCamera2D 在房间边界内 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 光照高于阈值时 solidShadowPlatform 光照低于阈值时 solidDoor 在特定区间内开启,一份规则表覆盖了几乎所有关卡玩法。地形几何显隐用 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 天梳理出来的时候,那种"东西是自己一点点搭起来的"的踏实感确实不太一样。答辩就在下午,剩下的交给现场发挥吧。