用户2914143
用户2914143
  • 发布:2025-10-26 16:46
  • 更新:2025-10-26 16:46
  • 阅读:21

使用 uni-app x 开发2048游戏适配鸿蒙6

分类:鸿蒙Next

使用 uni-app x 开发2048游戏适配鸿蒙6

作者:坚果派小雨
发布时间:2025年10月
技术栈:uni-app x、UTS、HarmonyOS 6

📖 前言

2048是一款风靡全球的益智游戏,简单而富有策略性。本文将详细介绍如何使用 uni-app x 框架从零开始开发一款2048游戏,并实现深色模式适配、数据持久化等进阶功能。最终产品完美支持鸿蒙HarmonyOS 6、Android、iOS等多个平台。

为什么选择 uni-app x?

  • 🚀 原生性能:UTS 语言编译为原生代码,性能接近原生应用
  • 📱 一次开发,多端运行:支持鸿蒙、Android、iOS、Web等平台
  • 💪 类型安全:基于 TypeScript,享受完整的类型检查
  • 🎯 鸿蒙首选:官方支持鸿蒙6,是开发鸿蒙应用的最佳选择之一

🎯 项目目标

我们将实现以下功能:

  1. ✅ 完整的2048游戏逻辑
  2. ✅ 流畅的触摸手势控制
  3. ✅ 精美的动画效果
  4. ✅ 深色模式自动适配
  5. ✅ 最高分本地存储
  6. ✅ 多平台支持(重点支持鸿蒙6)

📐 架构设计

数据结构设计

游戏的核心是一个 4x4 的二维数组,用于存储每个格子的数值:

// 游戏网格数据  
grid: number[][] = [  
  [0, 0, 0, 0],  
  [0, 0, 0, 0],  
  [0, 0, 0, 0],  
  [0, 0, 0, 0]  
]

为了实现平滑的动画效果,我们需要一个独立的 Tile 数据结构:

type Tile = {  
  id: number          // 唯一标识  
  value: number       // 方块数值  
  row: number         // 行位置  
  col: number         // 列位置  
  isNew: boolean      // 是否是新生成的  
  isMerged: boolean   // 是否刚合并  
}

状态管理

使用 Vue 3 的响应式系统管理游戏状态:

data() {  
  return {  
    grid: [] as number[][],        // 游戏网格  
    tiles: [] as Tile[],           // 显示的方块  
    score: 0,                      // 当前分数  
    bestScore: 0,                  // 最高分  
    gameOver: false,               // 游戏结束  
    gameWon: false,                // 游戏胜利  
    keepPlaying: false,            // 继续游戏  
    tileIdCounter: 0,              // 方块ID计数器  
    isDarkMode: false              // 深色模式  
  }  
}

🔧 核心功能实现

1. 游戏初始化

游戏开始时需要初始化网格并随机生成两个方块:

initGame() {  
  // 初始化4x4网格  
  this.grid = []  
  for (let i = 0; i < 4; i++) {  
    this.grid.push([0, 0, 0, 0])  
  }  

  // 重置状态  
  this.tiles = []  
  this.score = 0  
  this.gameOver = false  
  this.gameWon = false  
  this.tileIdCounter = 0  

  // 添加两个初始方块  
  this.addRandomTile()  
  this.addRandomTile()  
}

2. 随机生成方块

90%概率生成2,10%概率生成4,这是经典2048的设定:

addRandomTile() {  
  // 找出所有空格子  
  const emptyCells = [] as {row: number, col: number}[]  
  for (let i = 0; i < 4; i++) {  
    for (let j = 0; j < 4; j++) {  
      if (this.grid[i][j] === 0) {  
        emptyCells.push({row: i, col: j})  
      }  
    }  
  }  

  if (emptyCells.length > 0) {  
    // 随机选择一个空格子  
    const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)]  

    // 90%概率生成2,10%概率生成4  
    const value = Math.random() < 0.9 ? 2 : 4  
    this.grid[randomCell.row][randomCell.col] = value  

    // 创建新的方块对象  
    const tile: Tile = {  
      id: this.tileIdCounter++,  
      value: value,  
      row: randomCell.row,  
      col: randomCell.col,  
      isNew: true,  
      isMerged: false  
    }  
    this.tiles.push(tile)  

    // 200ms后移除新方块标记,触发动画  
    setTimeout(() => {  
      tile.isNew = false  
    }, 200)  
  }  
}

3. 触摸手势处理

实现流畅的滑动手势检测:

// 记录触摸起点  
touchStart(e: UniTouchEvent) {  
  this.touchStartX = e.touches[0].pageX  
  this.touchStartY = e.touches[0].pageY  
}  

// 计算滑动方向  
touchEnd(e: UniTouchEvent) {  
  this.touchEndX = e.changedTouches[0].pageX  
  this.touchEndY = e.changedTouches[0].pageY  
  this.handleSwipe()  
}  

// 判断滑动方向  
handleSwipe() {  
  const deltaX = this.touchEndX - this.touchStartX  
  const deltaY = this.touchEndY - this.touchStartY  
  const minSwipeDistance = 30  // 最小滑动距离  

  // 滑动距离太短,忽略  
  if (Math.abs(deltaX) < minSwipeDistance &&   
      Math.abs(deltaY) < minSwipeDistance) {  
    return  
  }  

  // 比较水平和垂直位移,判断主要方向  
  if (Math.abs(deltaX) > Math.abs(deltaY)) {  
    // 水平滑动  
    deltaX > 0 ? this.move('right') : this.move('left')  
  } else {  
    // 垂直滑动  
    deltaY > 0 ? this.move('down') : this.move('up')  
  }  
}

4. 移动和合并算法

这是游戏的核心逻辑。以向左移动为例:

moveLeft(): boolean {  
  let moved = false  

  for (let i = 0; i < 4; i++) {  
    // 记录每个位置是否已经合并过  
    let merged = [false, false, false, false]  

    // 从左到右遍历每一行  
    for (let j = 1; j < 4; j++) {  
      if (this.grid[i][j] !== 0) {  
        let col = j  

        // 尽可能向左移动  
        while (col > 0) {  
          // 左边是空格,移动过去  
          if (this.grid[i][col - 1] === 0) {  
            this.grid[i][col - 1] = this.grid[i][col]  
            this.grid[i][col] = 0  
            col--  
            moved = true  
          }   
          // 左边数字相同且未合并,合并  
          else if (this.grid[i][col - 1] === this.grid[i][col] &&   
                   !merged[col - 1]) {  
            this.grid[i][col - 1] *= 2  
            this.score += this.grid[i][col - 1]  
            this.grid[i][col] = 0  
            merged[col - 1] = true  
            moved = true  
            break  
          }   
          // 无法移动  
          else {  
            break  
          }  
        }  
      }  
    }  
  }  

  return moved  
}

算法关键点:

  • 使用 merged 数组防止一次移动中多次合并
  • 从移动方向的对侧开始遍历
  • 每个方块尽可能移动到最远位置

5. 游戏状态检测

胜利检测

hasWon(): boolean {  
  for (let i = 0; i < 4; i++) {  
    for (let j = 0; j < 4; j++) {  
      if (this.grid[i][j] === 2048) {  
        return true  
      }  
    }  
  }  
  return false  
}

游戏结束检测

isGameOver(): boolean {  
  // 1. 检查是否有空格  
  for (let i = 0; i < 4; i++) {  
    for (let j = 0; j < 4; j++) {  
      if (this.grid[i][j] === 0) {  
        return false  
      }  
    }  
  }  

  // 2. 检查是否可以合并  
  for (let i = 0; i < 4; i++) {  
    for (let j = 0; j < 4; j++) {  
      const current = this.grid[i][j]  
      // 检查右边  
      if (j < 3 && current === this.grid[i][j + 1]) {  
        return false  
      }  
      // 检查下边  
      if (i < 3 && current === this.grid[i + 1][j]) {  
        return false  
      }  
    }  
  }  

  return true  
}

🎨 界面设计与动画

1. 网格布局

使用 CSS 定位实现精确的网格布局:

.game-container {  
  position: relative;  
  width: 670rpx;  
  height: 670rpx;  
  background-color: #bbada0;  
  border-radius: 12rpx;  
  padding: 20rpx;  
}  

.grid-cell {  
  width: 142.5rpx;  
  height: 142.5rpx;  
  background-color: rgba(238, 228, 218, 0.35);  
  border-radius: 6rpx;  
}

计算公式:

单元格宽度 = (容器宽度 - 内边距*2 - 间距*3) / 4  
142.5rpx = (670 - 20*2 - 20*3) / 4

2. 方块定位

使用动态类名实现方块的精确定位:

<view   
  :class="[  
    'tile',   
    '' + tile.value,  
    'tile-' + tile.row + '-' + tile.col  
  ]"  
>  
  <text class="tile-inner">{{tile.value}}</text>  
</view>

CSS 定位规则:

.tile-position-0-0 { top: 0rpx; left: 0rpx; }  
.tile-position-0-1 { top: 0rpx; left: 162.5rpx; }  
/* ... 16个位置 ... */

3. 出现动画

新方块从小到大弹出:

.tile-new {  
  animation: appear 0.2s ease-in-out;  
}  

@keyframes appear {  
  0% {  
    opacity: 0;  
    transform: scale(0);  
  }  
  100% {  
    opacity: 1;  
    transform: scale(1);  
  }  
}

4. 合并动画

合并时放大再缩小:

.tile-merged {  
  animation: pop 0.2s ease-in-out;  
}  

@keyframes pop {  
  0% { transform: scale(1); }  
  50% { transform: scale(1.2); }  
  100% { transform: scale(1); }  
}

5. 渐变配色

不同数值的方块使用不同颜色:

.tile-2 { background-color: #eee4da; color: #776e65; }  
.tile-4 { background-color: #ede0c8; color: #776e65; }  
.tile-8 { background-color: #f2b179; color: #f9f6f2; }  
.tile-16 { background-color: #f59563; color: #f9f6f2; }  
/* ... */  
.tile-2048 { background-color: #edc22e; color: #f9f6f2; }

🌓 深色模式适配

1. 检测系统主题

detectTheme() {  
  const systemInfo = uni.getSystemInfoSync()  
  // @ts-ignore  
  this.isDarkMode = systemInfo.theme === 'dark' ||   
                    systemInfo.osTheme === 'dark'  
}  

onShow() {  
  // 每次显示时检测主题变化  
  this.detectTheme()  
}

2. 深色模式样式

使用级联选择器为深色模式定制样式:

/* 深色模式容器 */  
.dark-mode {  
  background-color: #1a1a1a;  
}  

/* 深色模式下的游戏网格 */  
.dark-mode .game-container {  
  background-color: #4a4340;  
}  

/* 深色模式下的方块 */  
.dark-mode .tile-2 {   
  background-color: #3a3a3a;  
  color: #c9c9c9;  
}  

.dark-mode .tile-4 {   
  background-color: #4a4a4a;  
  color: #d4d4d4;  
}

设计原则:

  • 降低对比度,减少眼睛疲劳
  • 保持色彩层次感
  • 确保文字清晰可读

💾 数据持久化

使用 uni-app 的本地存储 API 保存最高分:

// 保存最高分  
saveBestScore() {  
  uni.setStorageSync('bestScore', this.bestScore)  
}  

// 加载最高分  
loadBestScore() {  
  const saved = uni.getStorageSync('bestScore')  
  if (saved !== null && saved !== undefined && saved !== '') {  
    this.bestScore = parseInt(saved as string)  
  }  
}  

// 在分数更新时检查  
if (this.score > this.bestScore) {  
  this.bestScore = this.score  
  this.saveBestScore()  
}

📱 多平台适配

鸿蒙6特别优化

  1. 返回键处理(App.uvue):
onLastPageBackPress: function () {  
  if (firstBackTime == 0) {  
    uni.showToast({  
      title: '再按一次退出应用',  
      position: 'bottom',  
    })  
    firstBackTime = Date.now()  
    setTimeout(() => {  
      firstBackTime = 0  
    }, 2000)  
  } else if (Date.now() - firstBackTime < 2000) {  
    uni.exit()  
  }  
}
  1. rpx单位适配

    • rpx 是 uni-app 的响应式单位
    • 750rpx = 屏幕宽度
    • 自动适配不同屏幕尺寸
  2. 触摸事件优化

    • 使用原生触摸事件
    • 最小滑动距离30px
    • 防止误触

🐛 常见问题与解决方案

问题1:方块移动后位置不更新

原因:直接修改数组元素不会触发视图更新

解决方案:使用 updateTiles() 方法重建 tiles 数组

updateTiles() {  
  const newTiles = [] as Tile[]  
  for (let i = 0; i < 4; i++) {  
    for (let j = 0; j < 4; j++) {  
      if (this.grid[i][j] !== 0) {  
        let existingTile = this.tiles.find(t =>   
          t.row === i && t.col === j && !t.isNew  
        )  
        if (existingTile) {  
          existingTile.row = i  
          existingTile.col = j  
          existingTile.value = this.grid[i][j]  
          newTiles.push(existingTile)  
        }  
      }  
    }  
  }  
  this.tiles = newTiles  
}

问题2:一次移动合并多次

原因:没有标记已合并的位置

解决方案:使用 merged 数组记录

let merged = [false, false, false, false]  
// 合并时检查  
if (!merged[col - 1]) {  
  // 执行合并  
  merged[col - 1] = true  
}

问题3:动画不流畅

原因:方块 ID 重复或变化

解决方案:使用全局计数器生成唯一 ID

tileIdCounter: 0  

// 创建新方块时  
tile.id = this.tileIdCounter++

📊 性能优化

1. 减少不必要的渲染

// 只在有移动时才更新  
if (moved) {  
  this.addRandomTile()  
  this.updateTiles()  
}

2. 使用 CSS 动画而非 JS

.tile {  
  transition-property: transform;  
  transition-duration: 0.1s;  
  transition-timing-function: ease-in-out;  
}

3. 合理使用 v-if 和 v-show

<!-- 游戏结束遮罩使用 v-if -->  
<view class="game-message" v-if="gameOver || gameWon">  
  <!-- ... -->  
</view>

🚀 打包发布

鸿蒙应用打包

  1. 配置 manifest.json
  2. 在 HBuilderX 中选择"发行" -> "原生App-云打包"
  3. 选择鸿蒙平台
  4. 配置签名证书
  5. 打包上传

注意事项

  • 准备应用图标(512x512px)
  • 准备启动页图片
  • 填写应用描述和权限说明
  • 测试多种屏幕尺寸

📈 后续优化方向

功能扩展

  1. 撤销功能:保存每一步的状态
  2. 自定义尺寸:支持 3x3、5x5 网格
  3. 主题切换:多种配色方案
  4. 音效反馈:移动、合并音效
  5. 震动反馈:使用 uni.vibrateShort()

社交功能

  1. 排行榜:云端存储最高分
  2. 分享功能:分享到社交平台
  3. 成就系统:解锁各种成就
  4. 每日挑战:特殊模式挑战

体验优化

  1. 引导动画:首次进入时的教程
  2. 手势提示:显示滑动方向
  3. 历史记录:查看历史最高分
  4. 统计数据:游戏次数、时长等

💡 总结

通过这个项目,我们学到了:

  1. uni-app x 的基本用法:页面结构、数据绑定、事件处理
  2. UTS 语言特性:类型定义、类型安全
  3. 游戏算法实现:移动、合并、状态检测
  4. CSS 动画技巧:关键帧动画、过渡效果
  5. 响应式设计:rpx 单位、多屏适配
  6. 鸿蒙应用开发:平台特性、适配要点

uni-app x 是一个强大的跨平台开发框架,特别适合开发鸿蒙应用。通过本项目的实践,希望能帮助你快速上手 uni-app x 开发,创造出更多优秀的应用!

📚 参考资源


作者:坚果派小雨
项目地址: GitCode
开源协议: MIT License

如果觉得本文对你有帮助,欢迎点赞、收藏、分享!有任何问题也欢迎在评论区讨论。


💡 提示:本文涉及的完整代码已开源,你可以直接下载运行,也可以在此基础上进行二次开发。期待看到你的创意!

1 关注 分享
刘星

要回复文章请先登录注册