最近在鼓捣 Flutter 与 OpenHarmony 的跨端开发,顺手做了一个游戏集合 App 里的第一个小游戏:记忆翻牌表情图案。这个项目看起来不大,但踩了一路的坑,也把 Flutter 在 OpenHarmony 上的适配、状态管理、动画刷帧、打包调试这些环节都过了一遍。我觉得挺适合作为新手了解 Flutter 跨端能力的练手项目,也适合已经在做 OpenHarmony 应用、想尝试用 Flutter 快速出界面的团队参考。
记忆翻牌的核心玩法很经典:一组表情图案卡片被打乱后背面朝上排列,玩家每次翻开两张,图案相同则消除并得分,不同则翻回背面继续,直到全部配对完成。正因为规则简单,它反而非常适合用来摸清一套跨端框架的脾气——棋盘格布局、卡片翻转动画、配对逻辑、计时计分状态、音效反馈,几乎所有移动端 App 的常见交互都能在这一个游戏里练到。
我想说的不只是“怎么做游戏”,更是“怎么在 OpenHarmony 上用 Flutter 做好一个游戏”。所以这篇文章会从工程环境说起,到状态管理选型、动画实现、OpenHarmony 适配,最后把我在真机上踩过的坑和排查过程一起整理出来。
1. 项目设计与方案选型
1.1 为什么游戏集合 App 适合从 Flutter 起步
OpenHarmony 目前的应用形态还在快速演进,很多团队会纠结:到底用 ArkTS 原生写,还是用跨端框架?我的看法是,如果团队本身有 Flutter 经验,或者希望后续把同样的代码复用到 Android、iOS、Windows 等平台,那 Flutter 是一个很务实的选择。像游戏集合这种体量的 App,里面每一个小游戏都不重、界面交互各自独立,如果全部用原生 ArkTS 重写一遍,成本会明显上升。
我这套游戏集合 App 的架构规划是:外层壳工程负责导航、游戏列表、全局设置,壳工程可以先用 Flutter 做,也可以把 OpenHarmony 的 Ability 作为宿主导入 Flutter 容器;每个小游戏作为独立的 Flutter 页面模块注入,互不干扰。记忆翻牌作为第一个实战模块,正好用来验证 Flutter 页面在 OpenHarmony 里的承载能力和交互性能。
实际测试下来,Flutter 页面在 OpenHarmony 设备上的运行流畅度已经能到可用的水平。普通 4x4 棋盘的翻牌动画配合页面跳转,体感上没有明显掉帧。对游戏集合这种对 3D 重型渲染没什么要求的场景,Flutter 完全够用。
1.2 记忆翻牌的核心玩法拆解
做游戏之前先把玩法拆清楚,这是我每次写交互逻辑前的习惯。记忆翻牌表面上是翻卡片,背后其实是一个有限状态机加一套经典的配对算法。
游戏的完整流程可以分成几个状态:待开始、游戏中、暂停、通关结算。卡片本身则有三种基础状态:背面朝上、正面朝上待比对、永久消除。在这个状态机上,最关键的一条规则是同一时刻最多只能有两张卡片处于正面朝上状态,第三张卡片点击时必须先触发前两张的判定与回退逻辑,否则会出现逻辑混乱。
对比逻辑不复杂:两张翻开的卡片若表情标识相同,则视为配对成功,进入消除动画;若不同,则等待短暂提示时间后翻回背面。计时、步数、得分这三项数据共同驱动游戏的紧张感——步数越少、耗时越短,通关评分越高。
表情图案在这里不仅是为了好看。相比使用图片资源,Emoji 字符有一个天然优势:不需要打包大量切图资源,跨平台字体渲染也相对统一,开发阶段调整牌面内容只需要改一个 Unicode 集合。后续如果要加新主题,只需替换表情字符集合,低成本高灵活性。
1.3 技术选型:状态管理、路由与本地存储
Flutter 里的状态管理方案很多,setState、Provider、Riverpod、Bloc/Cubit 各有拥趸。记忆翻牌这种小体量游戏,我之前用 setState 也完全能写,但考虑到这是游戏集合 App 的第一块拼图,后面还会持续加游戏模块,我决定引入状态管理框架来统一维护“页面状态”和“最小化到后台再恢复”这类场景。
最终选了 Cubit 而不是全套 Bloc。原因是游戏的交互状态虽然多,但都是同步的 UI 状态流转,用 Cubit 的轻量方式足够。Bloc 全家桶的事件驱动模型更适合复杂业务流,这里用有一点杀鸡用牛刀。Cubit 的写法也更容易让团队里的新人快速上手,代码可读性更高。
路由层面,因为壳工程需要承载多个游戏入口,Flutter 端用了命名路由管理每个游戏的页面路径,配合路由守卫做页面间参数传递。记忆翻牌页面接收一个主题 ID 参数,根据主题 ID 从本地配置里读取对应的表情集合和难度配置。这种解耦方式让后续新增游戏时不需要改动壳工程的路由表主体结构。
本地存储方面,游戏的通关记录和最高分用 shared_preferences 落盘。OpenHarmony 平台适配层对 shared_preferences 做了兼容,实测写入和读取都没有问题。需要注意的是 OpenHarmony 的沙箱目录权限与 Android 存在差异,shared_preferences 内部封装的路径获取逻辑在 OpenHarmony 上工作正常,但如果你直接写文件路径访问,一定要通过框架提供的路径 API,不要硬编码绝对路径。
2. 环境搭建与工程适配要点
2.1 Flutter SDK 版本与 OpenHarmony 适配现状
这是刚接触 Flutter for OpenHarmony 的人最容易卡住的地方。OpenHarmony 官方通过 OpenHarmony SIG 维护了一套 Flutter 引擎的分支,你直接去 flutter.dev 下载的官方 Flutter SDK 是不能直接构建 OpenHarmony 应用的,必须使用 OpenHarmony 版本的 Flutter SDK 和对应的引擎包。
我在环境初始化时遇到一个非常常见的警告:the current configured flutter sdk is not known to be fully supported. please check your configuration and ensure the version matches. 这个警告的意思是当前项目配置的 Flutter SDK 版本与 OpenHarmony 插件期望的版本不匹配。多数时候是多版本 Flutter 并存时命令解析到了错误版本。我建议在项目根目录放一个 .fvmrc 或者直接用 FVM 管理 Flutter SDK 版本,确保 flutter doctor 和构建命令使用的是同一套 SDK 路径。
OpenHarmony 的 Flutter 支持列表与上游 Flutter 版本存在滞后,热门搜索里提到的 flutter 3.47.5 之类其实是 Android/iOS 侧的版本号,OpenHarmony 分支对应关系不一定能对上。这里最容易踩的坑是:你用新的官方 Flutter 版本初始化工程后,再切换到 OpenHarmony 分支 SDK,会出现引擎二进制不兼容的报错。我的做法是在项目一开始就锁定 OpenHarmony 官方推荐的 SDK 分支版本,不随意升级。
2.2 DevEco Studio 与工程目录的双工程结构
OpenHarmony 应用开发目前主要依赖 DevEco Studio,而 Flutter 代码的开发调试又离不开 Android Studio 或 VS Code,这就注定了项目需要一套双工具链的协作方式。
我的目录结构是这样的:仓库根目录下有一个 Flutter 工程目录,存放 Dart 源码、pubspec.yaml 和 Flutter 侧的资源配置;另外有一个 OpenHarmony 工程目录,里面是 DevEco Studio 生成的 Ability 壳工程。Flutter 工程的构建产物通过 ohos 平台的构建脚本输出到 OpenHarmony 工程的 AppScope 或 module 目录下,再由 DevEco 打包成 HAP。
这种结构最怕的是构建脚本和产物路径失配。我第二次重新拉代码时就因为产物输出目录变了,导致 OpenHarmony 工程里加载不到 Flutter 的 so 库文件,启动后白屏。建议把 Flutter 到 OpenHarmony 的产物拷贝步骤写成一个独立的 shell 脚本放进仓库,同时在 README 里明确标注 DevEco Studio 与 Flutter SDK 的版本组合,避免团队成员对不上号。
2.3 集成 Flutter 模块时常见的 Gradle 插件报错
热词里有一条非常典型:you are applying flutter's main gradle plugin imperatively using the apply script. 这个问题在把 Flutter 模块作为依赖集成到原生 Android 工程时很常见,OpenHarmony 的工程结构虽然不同,但大家在 Android 侧搜索解决方案时容易混淆。
这条报错的本质是 Gradle 插件应用方式变了,Flutter 3.x 之后推荐使用 declarative 方式在 settings.gradle 里声明插件,而不是在 build.gradle 里用 apply plugin 强制注入。如果你在 Android 侧集成 Flutter 模块,需要检查 settings.gradle 中是否用 pluginManagement 正确声明了 Flutter SDK 的插件路径。OpenHarmony 侧虽然没有 Gradle 插件的问题,但如果你用 Flutter 官方模板生成工程后再手动改过 android 目录,回来构建 OpenHarmony 时会因为配置残留而报错。我的做法是:OpenHarmony 构建目录与 Flutter 的 android 目录之间保持隔离,构建 OpenHarmony 之前先执行 flutter clean 清理掉其他平台的 build 缓存,否则可能串产物。
2.4 OpenHarmony 上的 Flutter 渲染引擎与启动优化
热词里提到的 flutter impeller 是 Flutter 3.7 之后默认启用或可选的渲染引擎。Impeller 的核心目标是解决 Skia 在部分 GPU 驱动上出现的着色器编译卡顿问题,给用户更稳定的帧率表现。OpenHarmony 对 Flutter 的支持也在逐步跟进 Impeller 的适配,但并不是所有设备都默认开启。
我在 OpenHarmony 真机上测试时发现,默认配置下动画第一次运行时会有轻微的掉帧,随后趋于稳定。这个现象是因为着色器编译缓存在首次运行后建立,后续运行就正常了。如果遇到类似问题,可以尝试在 Flutter 引擎初始化参数里开启 Impeller 并重启验证,但要注意旧设备 GPU 驱动兼容性,如果出现渲染异常马上去掉。启动图方面,Flutter 默认的白屏时间在 OpenHarmony 设备上比 Android 长一些,原因是 OpenHarmony 的 FlutterEngine 初始化需要加载的 so 库体积较大。可以在 OpenHarmony 原生侧配置启动闪屏图覆盖首帧之前的空白窗口,体感上会快很多。
3. 记忆翻牌核心逻辑与页面实现
3.1 数据模型与牌组生成
牌组生成是整个游戏逻辑里的地基。我的核心数据结构是 CardModel,包含唯一标识 id、表情字符 emoji、卡片状态 state、配对的 groupId。这里特意加了一个 groupId 而不是直接用 emoji 字符做配对键,是为了未来扩展多表情主题时,即便不同主题里出现同一个 Unicode 字符,也能保证配对关系在数据库和埋点上报里的统一。从工程角度来说,持久化字段用抽象的组 ID 比直接用展示字段更稳妥。
牌组生成算法分三步:
- 按难度传入牌对数,比如 4x4 难度对应 8 对牌,6x4 对应 12 对牌。
- 从表情主题配置里按顺序取前 N 个不重复的字符。
- 每个字符生成两张卡牌,加入列表后执行 Fisher-Yates 洗牌。
Fisher-Yates 洗牌是我个人的强迫症,它保证每种排列出现的概率均匀,而且实现只需要一次遍历。这里不推荐用 sort(() => Math.random() - 0.5) 这类依赖不稳定排序的写法,既慢又不均匀。
3.2 状态机与配对比对的实现细节
配对比对的逻辑如果直接用一堆 if-else 写在点击回调里,后期加功能(比如连击提示、特效奖励)会很难维护。我选择用显式状态字段 currentPhase 来标识游戏的比对阶段:选第一张、选第二张、判定中、消除中、完成。
点击一张卡片时,先做合法性检查,比如卡片是否已经消除、是否正处于翻转动画中、当前是否在判定中的锁定状态。这里有一个新手容易忽略的点:动画播放期间用户快速点击其他卡片会导致状态错乱,必须在动画期间加一个 inputLock 标志位。等动画结束后再释放。我当时在 6x4 难度下复现过快点点穿的问题,根源就是锁状态没做好。
判定逻辑上,当第二张卡翻开后,先做延迟 800ms 的展示再判定,这样能让玩家记住图案位置,这是记忆游戏的核心体验,不能省。如果配对成功,播放一个缩放渐消动画并把状态置为消除;如果失败,两张卡同时翻回背面,节奏保持一致。
3.3 翻转动画与表情渲染
记忆翻牌最关键的视觉效果就是翻牌动作。Flutter 里做 3D 翻转效果通常用 AnimationController 加 Matrix4 的透视旋转。
我实现的方案是一个 AnimatedBuilder 监听同一个 controller,对卡片前后两面使用 Transform 做 rotateY 变换。0 到 0.5 的动画区间显示背面,到达 0.5 时切换显示正面,0.5 到 1.0 继续从 90 度旋转到 0 度。为了让旋转看起来有立体感,要给 Matrix4 加上透视参数,最简单的做法是:
final angle = _controller.value * 3.14159265; final transform = Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateY(angle);setEntry(3, 2, 0.001) 这行是透视效果的关键,没有它旋转看起来就像是在平面里压缩,有它才有厚度感。表情文字的渲染直接用 Text 组件加 fontSize 控制,不需要任何图片。但要注意 OpenHarmony 设备上的系统字体对 emoji 的支持不完全一致,部分字符可能显示为豆腐块。我在选择表情主题时避开了生僻符号,优先使用 Unifont 和主流厂商都覆盖的基础几何类与动物类符号。
3.4 状态管理落地:Cubit 的实战用法
前面提到我选了 Cubit,这里给出一个简化的状态类写法,方便读者理解:
class MemoryGameCubit extends Cubit<MemoryGameState> { MemoryGameCubit() : super(const MemoryGameState()); void flipCard(CardModel card) { if (state.isLocked || card.isMatched) return; // 翻转卡片加入翻转列表 // 判断是否两张翻开 emit(state.copyWith(...)); } void checkMatch() { // 延迟判定后用新的状态替换旧状态 } }Cubit 的好处是状态对象是不可变的,每次通过 copyWith 生成新状态,UI 层通过 BlocBuilder 监听变化后重建对应组件。这样做最大的收益是逻辑可测试。你可以不依赖 Widget 环境,直接在单元测试里调 flipCard 验证状态流转是否符合预期。我在项目里给翻转逻辑和洗牌逻辑都写了测试,后面改代码时心里踏实很多。
3.5 音效反馈与触觉震动
游戏没有声音会显得很干。Flutter 官方没有内置音频播放能力,我用的是 audioplayers 插件。OpenHarmony 插件市场里同样音效包并不多,我选择了把音效资源作为 Flutter 侧的 assets 打包,而不是走系统音频文件访问,这样移植性更可控。
配对成功和配对失败使用两个短音频,时长控制在 200ms 以内,避免反馈滞后。触觉方面,成功时调用 HapticFeedback.mediumImpact(),失败时使用 HapticFeedback.lightImpact(),这两类反馈能显著提升操作临场感。需要留意的是,OpenHarmony 对 HapticFeedback 的支持跟 Android 原生的反馈通道不完全一致,实测 mediumImpact 在部分设备上无效果,必要时可用 vibrate 方法做降级。
4. 界面布局与视觉动效处理
4.1 自适应棋盘与安全区处理
记忆翻牌的牌桌需要适配不同屏幕尺寸和折叠屏。我用 GridView.builder 实现网格,布局时设置 childAspectRatio,让卡片宽高比固定为 0.8,这样不管屏幕宽窄,卡片都能在视觉上保持一致。4x4 难度下每张卡宽度约等于屏宽的四分之一减去间距,6x4 难度则将卡片调小,排列更紧凑。
折叠屏和横屏场景下,用一个 LayoutBuilder 包裹整个牌桌区域,根据可用宽高动态计算网格的交叉轴数量。横屏时如果继续按竖屏的 4 列布局,卡片会被拉得过高,体验很差。我的做法是横屏时使用 6x4 布局,竖屏使用 4x4,这样两个方向都能保证卡片接近正方形。状态栏和手势区域用 MediaQuery.paddingOf 处理,避免卡片被刘海区或侧滑手势区遮挡。
4.2 计时进度条与得分反馈动效
游戏进行中,顶部显示计时与得分信息。计时用每秒更新一次 StreamBuilder 驱动,每隔一秒更新当前耗时文本和进度条宽度。进度条表示最大时间预算的剩余百分比,比如限定 120 秒,剩余 30 秒时卡片背景会变成含警示语义的浅色。这个设计给玩家一个直观的紧迫感。
得分反馈用叠层式动画:当配对成功时,在棋盘上方浮动一个得分文本,从当前位置向上飘出并淡出,持续约 600ms。这是一个很常见的游戏反馈模式,用 Flutter 实现也很顺手,每次增加分数时往 Stack 里插入一个得分浮层组件,动画完成后移除。注意频繁触发时移除逻辑不要依赖列表索引,否则会出现动画错乱的问题。
4.3 游戏结束与排行榜记录
消除完最后一张卡后,进入结算面板。结算面板展示本轮步数、耗时、评分星级,以及是否打破历史最佳纪录。为了减少用户打扰,结算面板弹出前先播放一个 300ms 的完成动画,让玩家看到最后一张牌被消除的瞬间,再展示成绩。
历史最佳纪录用 shared_preferences 保存,key 里带上主题 ID 和棋盘难度。评分算法比较简单:在规定步数内完成且耗时低于一定阈值时为三星,超时三分之一为两星,再超时为三星以下。写这一段时让我意识到一个事:排行榜数据模型尽量设计成结构体数组,而不是单独的 key 拼接字符串,因为游戏集合后面会扩展到十多个游戏,通用排名数据结构能大量减少重复代码。
5. OpenHarmony 平台适配与性能优化
5.1 真机调试与日志排查流程
OpenHarmony 真机调试行业里常见的痛点是日志采集链路。Flutter 侧的 debugPrint 日志可以正常输出到控制台,但 OpenHarmony 原生侧的 hiLog 与 Flutter 侧日志是两条独立链路。如果问题同时涉及两侧交互,需要分别查看两个日志窗口才能定位。我遇到过一次点击卡片无反应,Flutter 侧日志完全正常,最后发现是 OpenHarmony Ability 在多窗口模式下没有正确传递触摸事件。这个问题只靠 Flutter 侧是看不出来的。
排查建议分两步走:先确认 Flutter 页面是否正常渲染与响应,排除层叠窗口抢占事件的可能;再检查 OpenHarmony 从 Ability 到 Flutter 容器的事件转发逻辑。建议定期抓取 hilog 并加关键字过滤,同时打开 Flutter 的 debug 模式观察帧耗时,用两边的数据对照分析。
5.2 Flutter 与 OpenHarmony 原生能力通信
游戏里后续要接系统能力时,会用到 Platform Channel。OpenHarmony 为 Flutter 提供的通道机制与 Android 类似,MethodChannel 可以在 Dart 侧和 OpenHarmony 的 ArkTS 侧进行双向调用。我建议把所有系统能力调用放到一个统一的 PlatformService 模块里,比如获取设备型号、震动反馈、读取本地图片等,避免每个页面直接创建 MethodChannel。
Channel 的名字必须唯一且与原生侧注册一致,否则会出现空实现报错。调试时开通日志通道,对每次 invokeMethod 的入参和返回值都做打印,这样排查起来非常高效。有一个经验是:不要在 Platform Channel 里传大对象,比如图片的 Base64 字符串,模块间通信有性能损耗,传输耗时会让动画掉帧。
5.3 Impeller 与 60fps 目标
热词里的“Flutter 60fps”是所有 Flutter 开发者共同的追求。内存和 CPU 占用是影响帧率的两大因素。在记忆翻牌里,最影响性能的是卡片较多时的重建问题。我在第一版里把整个棋盘 GridView 放在一个 BlocBuilder 里,任何一步状态变化都会触发全部卡片重建。初期牌少不明显,但 6x4 难度下会明显掉帧。
优化方案是把牌桌区域拆成多个组件,翻转状态变化只重建受影响的卡片组件,统计信息单独组件化。上述优化做完后,6x4 难度真机 60fps 稳定。另外 OpenHarmony 设备建议在 release 模式下做帧率采样,debug 模式的 JIT 和检查开销会让结果失真。
5.4 布局兼容与字体兼容
OpenHarmony 目前覆盖的设备形态从手机到平板不等。我用 MediaQuery 判断设备类型,在手机和平板上使用不同的卡片间距与字体大小。平板端卡片可以更大些,保证一屏内铺满但不过于拥挤。字体方面,OpenHarmony 系统字体对 emoji 的像素渲染风格与 Android 不同,部分表情符号在叠加了半透明阴影之后观感模糊。遇到这种情况,我的处理是把阴影移除,保证主体轮廓清晰。
这里还遇到过一个坑:Flutter 的新版本 Text 组件在部分 OpenHarmony 设备上中文字体回退失败,显示成系统默认字体。虽然不影响功能,但界面的统一性会打折扣。解决办法是在 MaterialApp 的主题里显式指定 fontFamily,把常用的中文字体作为 fallback 加入,这样各设备的渲染一致性会更好。
6. 常见问题与排查实录
6.1 启动白屏与引擎加载过慢
白屏是 Flutter 在 OpenHarmony 上最常见的首帧问题。主要原因有两类:一是 Flutter 引擎初始化较慢,尤其是 debug 模式,二是 OpenHarmony 侧的窗口没等待 Flutter 首帧绘制完成就显示,出现空白阶段。
我的解决思路是双重优化。原生侧延迟 Flutter 容器 window 的可见时间,用启动闪屏图填充等待期,同时 Flutter 侧精简 main() 函数里启动前的耗时任务,把不需要首帧展示的资源懒加载。这样调整后启动到首帧的时间体感缩短了一截。
6.2 SocketException 与网络权限问题
热词里的 flutter socketexception 常见场景是应用有网络请求,但 OpenHarmony 的模块没有申请 ohos.permission.INTERNET 权限,导致连接失败或异步异常。记忆翻牌目前没有用到网络,但我把游戏登录和排行榜云同步作为后续规划,提前加了网络状态工具类。碰到 SocketException 时,优先检查模块配置文件里是否申请了 INTERNET 权限,再看代理设置与目标地址连通性。
OpenHarmony 对网络权限的管理比 Android 更严格,即使调试模式,未声明权限也会抛异常。建议在开发初期就配置好,后续增加网络功能时不会反复踩权限的坑。
6.3 Flutter Web 引擎启动慢的误读
热词里有条 flutter web 引擎启动慢,这其实是 Flutter Web 端的性能问题,与 OpenHarmony 移动端没有直接关系。很多人在搜索 OpenHarmony 启动慢时误入了 web 端的解决方案,浪费了时间。
两者性能瓶颈完全不同:Web 端启动慢受浏览器引擎初始化影响,OpenHarmony 端启动慢主要是原生容器与引擎二进制加载耗时。建议遇到问题时先确认自己的目标平台再搜索,避免被不相关的方案带偏。
6.4 热重载在 OpenHarmony 上的失效情况
Flutter 开发时热重载是好帮手,但在 OpenHarmony 上热重载有时不生效。我发现具有 OpenHarmony 插件版本更新或原生资源变化时,热重载后容易出现运行时异常甚至白屏。原因是热重载只更新 Dart 层,原生侧 so 库或资源文件不会重载,状态不一致就会出现问题。
方法很简单:修改过原生侧相关内容后,先别急着热重载,直接 stop 再重新 flutter run,干净启动后再走热重载流程。日常不改原生侧时热重载还是能用的,只是需要留心它的局限性。
6.5 SDK 版本校验警告处理
环境配置时那个 the current configured flutter sdk is not known to be fully supported 警告,很多人问要不要处理。如果只是提示但不影响构建,可以暂时忽略。但当它出现时,建议先确认当前 Flutter SDK 分支是否与 OpenHarmony 插件版本匹配,最简单的验证方法是跑一下 OpenHarmony 官方 demo 工程,能跑通就说明整体环境可用。
如果警告伴随着构建失败,优先检查 PATH 环境变量是否同时存在多个 Flutter SDK。多个 SDK 混用时,命令行解析到的版本很可能不是 OpenHarmony 分支,这样的构建失败最常见。
6.6 常见问题速查表
| 问题现象 | 可能原因 | 解决建议 |
|---|---|---|
| 启动白屏 | 原生窗口展示早于 Flutter 首帧 | 延迟容器可见时间,加启动闪屏 |
| 点击卡片无响应 | 输入锁定未释放或事件层被遮挡 | 检查 isLocked 标志,查看原生窗口层级 |
| 动画偶发掉帧 | 整棋盘重建或 debug 模式开销 | 组件化拆分卡片,release 模式采样 |
| 中文/Emoji 字体异常 | 字体回退失败 | 显式配置 fontFamily 和 fallback |
| SocketException | 缺少 INTERNET 权限 | 检查 ohos 模块配置文件权限声明 |
| 热重载后状态错乱 | 原生侧与 Dart 侧资源不一致 | 修改原生后 clean 重启 |
| SDK 警告 | SDK 与插件版本不匹配 | FVM 固定 SDK 版本,跑通官方 demo |
7. 后续扩展与个人经验
做完记忆翻牌这个模块,我对 Flutter 在 OpenHarmony 上做游戏集合 App 的信心更足了。剩下的游戏模块里,我打算优先加入 2048、扫雷、拼图这三类经典玩法,原因很简单:它们的核心逻辑都不依赖重型 3D 渲染,而更考验状态管理和交互流畅度,这正是 Flutter 的强项。
后续的架构规划里,我会把游戏中通用的部分抽成独立包:棋盘组件、计时组件、评分面板、音效管理、排行榜模块。这样新增一个游戏时,只需要专注于玩法逻辑和界面,不用从零搭基础工具。对游戏集合这种持续扩展的项目,先把基础设施做扎实,比一次性铺开所有游戏更重要。
另外,在把记忆翻牌跑上 OpenHarmony 热更新验证之后,我准备尝试把同一套代码构建到 Android 上对比性能。实测下来 Flutter 的抽象层确实做到了编译期隔离,同一个 lib 里没有任何平台相关的 import,切平台构建时没有改一行 Dart 代码。这也是跨端框架最值得的地方。
最后分享一个个人经验:做这种小体量项目,不要一上来就想做大会员、排行榜、云同步,先把最核心的玩法闭环跑通,再一层层加外围功能。我就吃过这个亏,第一版花了很多时间搭账号系统,游戏本身反而粗糙。后来砍掉外围,专注打磨翻转动画和配对反馈,体验反而直线上升。记住核心玩法带来的乐趣,才是用户留存的关键。