Flipper Zero 上的 DnD Dice:桌面角色扮演骰子应用与 FAP 源码全解析
【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper
DnD Dice是一款运行在Flipper Zero上的骰子投掷应用,专为龙与地下城(D&D)等桌面角色扮演游戏的投骰场景设计。本文将以 DnD Dice 官方 README 为骨架,结合其完整源码(dice_app.c、constants.h、application.fam),带你掌握这款应用的操作方式、界面设计、底层状态机与动画实现,并手把手完成从源码到.fap安装包的编译与部署。
一、应用简介:一枚随身携带的虚拟骰袋
实体骰子容易丢失、滚动不可控,在旅途中开团时尤其不便。DnD Dice 把桌面角色扮演中最常用的骰子全部装进 Flipper Zero,只需按几下按键就能完成投掷并自动累加结果。根据 README 与源码中的dice_types[]表(constants.h),应用共内置8 种骰型:
| 骰型 | 面数(type) | 说明 |
|---|---|---|
| Coin | 2 | 硬币正反,用于判定类场景 |
| d4 | 4 | 四面体骰 |
| d6 | 6 | 六面骰,最常见的标准骰 |
| d8 | 8 | 八面体骰 |
| d10 | 10 | 十面骰 |
| d12 | 12 | 十二面体骰 |
| d20 | 20 | 二十面骰,D&D 的核心检定骰 |
| d100 | 100 | 百面骰,常以两个 d10 模拟 |
每种骰型在源码中都携带type(面数)、坐标x/y与显示名称name,界面上的所有骰子图标都由assets/目录下的像素图提供(如assets/d4_1.png、assets/d20_4.png等,均为 35×35 的 XBM 资源),并配套保留了sources/目录下的.pixil源文件供二次绘制。
二、界面与操作指南
应用采用全屏 GUI(GuiLayerFullscreen)与像素风界面,主界面布局如下:
主界面从上到下依次是:当前骰型图标与名称、左右切换箭头、骰子数量设置、底部 EXIT / OK ROLL 按钮。完整按键操作逻辑对应 dice_app.c 中的按键分发:
- 左 / 右方向键:在 8 种骰型之间切换。切换时会触发
SwipeLeftState/SwipeRightState滑动动画,代码以SWIPE_DIST = 11像素步进、DICE_GAP = 44像素间距让骰子图标整体平移(见 constants.h)。 - 上 / 下方向键:调整单次投掷的骰子数量,范围1 ~ 10(上限由
MAX_DICE_COUNT = 10定义)。特别地,isOneDice()会锁定Coin 与 d100两种骰型的数量恒为 1(constants.h),因为这两者本身就是"单枚"语义。 - OK 键(中心键):触发投掷,进入动画播放与结果展示。
- BACK 键:在结果界面按下返回骰子选择;在选择界面按下则退出应用。
投掷完成后的结果界面如下:
结果界面用大字号显示点数和,并在其下方以次级字体列出每一枚骰子的单独结果(如4,4,1,3,2,6),多枚投掷时清晰可读,符合跑团记录习惯。
三、源码结构一览
整个应用只有四个源码/配置文件,结构非常精简,非常适合作为 Flipper Zero 应用开发的入门范本:
| 文件 | 职责 |
|---|---|
| dice_app.c | 应用入口、事件循环、状态更新、UI 绘制 |
| constants.h | 常量、骰型表、动画帧表、状态机定义与工具函数 |
| application.fam | FAP 应用清单(构建元数据) |
| LICENSE.md | GPL v3 开源许可证 |
其中application.fam是 Flipper 构建工具 fbt 识别应用的清单文件,内容如下:
App( appid="DND_Dice_app", name="DnD Dice [Ka3u6y6a]", apptype=FlipperAppType.EXTERNAL, entry_point="dice_dnd_app", cdefines=["APP_DICE"], requires=["gui"], stack_size=1 * 1024, order=90, fap_icon="icon.png", fap_category="Games", fap_icon_assets="assets", )各字段含义:
- appid / name:应用唯一标识与显示名;
- apptype = EXTERNAL:编译为独立
.fap插件而非烧入固件; - entry_point:入口函数名
dice_dnd_app,对应源码中的int32_t dice_dnd_app(void* p)(dice_app.c); - requires = ["gui"]:声明依赖 GUI 子系统;
- stack_size:任务栈 1KB;
- fap_category = "Games":决定安装到 SD 卡后出现在
apps/Games分类下(README 的安装路径正是该目录); - fap_icon / fap_icon_assets:应用图标与图标资源目录。
四、核心实现深度解析
4.1 六状态状态机
应用的全部逻辑建立在一个简洁的状态机上,枚举定义在 constants.h:
SelectState → SwipeLeftState / SwipeRightState(骰型滑动切换) → AnimState(投掷动画)→ AnimResultState(结果滑入动画)→ ResultState(结果展示)每个定时器 tick(见下文 4.4)都会驱动update()(dice_app.c)推进状态:滑动状态逐帧平移骰子直到归位;AnimState播放骰子旋转帧;AnimResultState让结果边框沿result_frame_pos_y[] = {-30, -20, -10, 0}逐帧下落,落到0时进入最终ResultState。
4.2 随机数与点数计算
投掷核心逻辑在roll()(dice_app.c):
state->rolled_dices[i] = (rand() % dice_types[state->dice_index].type) + 1; state->roll_result += state->rolled_dices[i];每一枚骰子的点数通过rand() % 面数 + 1生成(d4 得到 1~4,d20 得到 1~20,Coin 得到 1~2),单枚结果存入rolled_dices[MAX_DICE_COUNT]数组,同时累加到roll_result作为点数总和。结果界面即同时展示总和与逐枚明细。
4.3 双套动画帧系统
动画分为硬币与骰子两套(constants.h):
- 硬币:
MAX_COIN_FRAMES = 9帧。正/反面结果由coin_set_start()/coin_set_end()在投掷前动态改写首尾帧——正面结果为"正-正"头尾、反面为"反-反"头尾,从而让动画结束时正确落定在结果面上; - 骰子:每种骰型
MAX_DICE_FRAMES = 4帧旋转动画,按(骰型序号 - 1) * 4 + 帧号索引到dice_frames[]表中,d4/d6/d8/d10/d12/d20/d100 共 28 帧。
所有帧都是assets/下的 35×35 像素图标(I_d4_1、I_d6_3……),由 fbt 构建时自动从fap_icon_assets="assets"生成头文件引用。
4.4 事件驱动架构与渲染
应用遵循 Flipper Zero 标准的事件驱动范式(dice_app.c):
- 分配
FuriMessageQueue事件队列(容量 8),事件类型分为EventTypeTick(定时器)与EventTypeKey(按键); - 创建
ValueMutex保护共享的State,避免绘制与逻辑线程竞争; - 注册
ViewPort的绘制回调draw_callback与输入回调input_callback,并以furi_timer_alloc(FuriTimerTypePeriodic, ...)建立0.2 秒周期的动画 tick(furi_kernel_get_tick_frequency() * 0.2); - 主循环
furi_message_queue_get()阻塞取事件,超时 100ms;按键事件里按InputTypePress处理方向键、OK 与 BACK; - 每次循环末尾调用
view_port_update()触发重绘; - 退出时按序释放定时器、队列、ViewPort 并
furi_record_close(RECORD_GUI)。
绘制层将 UI 拆成draw_ui(骰名、箭头、数量、按钮)、draw_dice(骰子图标)与draw_results(结果框与文字)三个函数,按状态机决定绘制哪部分——例如ResultState下不再绘制骰子而绘制结果。
五、编译与部署:从源码到 FAP 插件
README 给出的完整编译流程如下(以官方固件为例,其他固件同理):
准备固件源码:克隆 flipperzero-firmware(FlipperDevices 官方仓库),或你所使用的第三方固件(例如 Unleashed 系固件)源码;
建立符号链接:在固件仓库的
applications_user目录下创建一个名为dice的符号链接,指向本应用的源码目录(即Applications/Official/source-OLDER/grnch/dice2/),例如执行ln -s /path/to/dice2 dice;执行编译:在固件仓库根目录运行:
./fbt fap_dice_dnd_appfbt 会根据
application.fam中的appid="DND_Dice_app"与entry_point="dice_dnd_app"自动定位入口函数并产出插件;安装到设备:将产物
build/f7-firmware-D/.extapps/dice_dnd_app.fap复制到 SD 卡的
apps/Games目录(与application.fam的fap_category="Games"对应),或通过qFlipper桌面工具安装到设备。
安装后进入 Flipper Zero 的Applications(应用)菜单即可找到该游戏。需要说明两点适用前提:
- FAP 与固件的 API 版本强绑定。按照本仓库 Applications/ReadMe.md 的说明,RogueMaster 与 Unleashed 固件之间的 FAP 文件可以互换,但由于 API 差异,它们与官方固件之间不通用——这也是 README 要求"为你的固件分别编译"的根本原因;
- 如果不想搭建完整固件环境,也可以使用uFBT(micro Flipper Build Tool,FlipperDevices 提供的轻量构建工具)直接编译独立的 FAP,这是仓库 README 中推荐的另一种构建路径。
六、在仓库中的版本脉络
本目录位于 Applications/Official/source-OLDER 下的grnch/dice2/,对应作者 grnch 维护的官方固件兼容版本。仓库中还保留了相关演化痕迹,可供对照学习:
grnch/dice/:同一作者的早期单文件版本(dice.c);kyhwana/dice2/:与grnch/dice2同源的镜像版本(kyhwana/dice2/README.md);xMasterX/下的dice/、rmdice/与flipperzero-yatzee-main/:同主题的骰子/游戏变体实现。
对比阅读这些版本,可以直观看到从单文件到constants.h抽离常量、从简单投掷到完整动画状态机的演进思路。
七、开源许可
本应用以GNU General Public License v3.0(GPL v3)授权发布,完整许可文本见 LICENSE.md。这意味着你可以自由使用、修改与再分发,但衍生作品需以相同许可证开源。
结语
DnD Dice 虽是一个体量极小的 Flipper Zero 应用,却完整覆盖了"骰型配置 → 数量设置 → 随机投掷 → 动画反馈 → 结果展示"的闭环,其六状态状态机、双套动画帧表与事件驱动 GUI 范式,是学习 Flipper Zero FAP 开发的高质量范例。无论你是想用它替代实体骰子,还是想以此为模板写出自己的第一个像素风应用,都可以从本仓库的这份源码直接上手。
【免费下载链接】FlipperPlayground (and dump) of stuff I make or modify for the Flipper Zero项目地址: https://gitcode.com/GitHub_Trending/fl/Flipper
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考