鸿蒙 ArkTS 实战:Canvas 向量绘制 + @StorageLink 打造纯端侧翻牌记忆游戏

举报
yd_225266885 发表于 2026/11/08 00:41:24 2026/11/08
【摘要】 以一个纯端侧鸿蒙翻牌记忆游戏为例,拆解 Canvas 向量绘制牌面、rotate 与 animation 实现 3D 翻牌、@StorageLink 跨页状态同步以及 TransitionEffect 结算弹窗的实践要点,并总结纯端侧应用的取舍。

很多开发者入门 HarmonyOS 应用开发时,习惯从待办清单做起,但待办清单里几乎都是列表渲染与增删改查,很难覆盖动画、Canvas 绘制、状态跨页同步、持久化这些更能体现 ArkUI 能力的点。HarmonyOS-APP-memory-card-game 案例反其道而行:以点击翻牌、配对消除的经典玩法为输入,把全部逻辑与渲染收拢在设备本地,最终产出一个纯端侧、无云端依赖、无账号体系、无网络请求的完整 App。麻雀虽小,五脏俱全。

一、整体架构:三层拆分

工程只有三类文件,职责边界清晰:model 层负责牌堆生成、洗牌、翻开与配对状态流转,不接触任何 UI 组件;view 层负责牌面渲染、翻牌动画、结算弹窗;entry 页只做状态装配与页面跳转,通过 @StorageLink 拿到全局游戏状态。这样拆分的好处是游戏规则可以脱离 UI 单独验证,UI 改动也不会污染状态机。

二、Canvas 向量绘制:不依赖任何图片素材

案例最有意思的一点是牌面完全不使用图片:6 种几何形状 × 8 种高对比颜色,全部由 Canvas 向量路径实时生成。核心思路是把形状抽成一个绘制函数,入参是几何类型与颜色:

private drawShape(ctx: CanvasRenderingContext2D, kind: number, color: string, r: number) {
  ctx.fillStyle = color;
  ctx.beginPath();
  switch (kind) {
    case 0: // 圆形
      ctx.arc(0, 0, r, 0, Math.PI * 2);
      break;
    case 1: // 三角
      ctx.moveTo(0, -r);
      ctx.lineTo(r * 0.866, r * 0.5);
      ctx.lineTo(-r * 0.866, r * 0.5);
      ctx.closePath();
      break;
    default: // 多边形
      for (let i = 0; i < kind; i++) {
        const a = (Math.PI * 2 * i) / kind - Math.PI / 2;
        const px = Math.cos(a) * r;
        const py = Math.sin(a) * r;
        i === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
      }
      ctx.closePath();
  }
  ctx.fill();
}

这样做有三个收益:安装包体积几乎不增加;任意分辨率下都清晰不糊;配色与形状随时可调,做无障碍适配也更容易,色盲用户可依赖形状区分。实践中有两个坑:Canvas 的 onReady 回调里必须按组件实际尺寸换算像素比,否则高分屏会发虚;重绘要按帧节流,避免动画期间每帧重建全部牌面。

三、3D 翻牌:rotate 与 animation 的最小实现

翻牌动效没有引入任何三方动画库,只用属性 rotate 配合 animation:

Text('')
  .width(this.cardW)
  .height(this.cardH)
  .rotate({ y: this.flipped ? 180 : 0, centerX: '50%', centerY: '50%' })
  .animation({ duration: 300, curve: Curves.easeInOut })

经验是 duration 控制在 250 到 350 毫秒之间手感最好,同时在翻转过程中切换正反面内容,用条件渲染或透明度过渡,避免出现翻到一半看到背面内容的穿帮。

四、@StorageLink:跨页状态同步的省心方案

游戏状态(关卡、得分、最佳记录)需要被多个页面共享,案例直接使用 @StorageLink 而不是层层传参:

@StorageLink('gameState') gameState: GameState = new GameState();

与 @State 相比,@StorageLink 天然具备跨页面、跨组件的能力,写入即全局可见;再配合 Preferences 落盘,重启应用后最佳成绩依然保留。这里有一个容易踩的坑:绑定的对象如果整体替换,会触发整树刷新,动画可能被打断;更稳妥的做法是只修改对象内部字段,让框架做细粒度更新。

五、TransitionEffect:结算弹窗的入场

结算弹窗使用 TransitionEffect 做入场与退场,把透明度与缩放组合起来,再挂上动画时长。要注意为节点加延迟销毁,等动画跑完再移除,否则会出现弹窗瞬间消失、动画看不到的情况。

六、纯端侧带来的取舍

没有网络、没有账号,意味着不需要处理登录态,不用考虑接口超时与重试,工程复杂度大幅下降;代价是所有数据都在本机,做排行榜、跨设备同步这类需求时要额外设计。对于练习 ArkUI 基本功、验证交互与动画方案来说,纯端侧是非常合适的起点。

七、小结

这个案例覆盖了 ArkTS 日常开发的绝大多数场景:Canvas 向量绘制、属性动画、跨页状态同步、本地持久化、@Builder 组件复用。如果你也在准备鸿蒙相关实践任务,建议直接把这个仓库拉下来跑一遍,再按自己的需求改造牌面与规则,收获会比看十篇教程都实在。项目地址可在鸿蒙实践中心的案例列表中检索 memory-card-game 找到。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。