使用 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,是开发鸿蒙应用的最佳选择之一
🎯 项目目标
我们将实现以下功能:
- ✅ 完整的2048游戏逻辑
- ✅ 流畅的触摸手势控制
- ✅ 精美的动画效果
- ✅ 深色模式自动适配
- ✅ 最高分本地存储
- ✅ 多平台支持(重点支持鸿蒙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特别优化
- 返回键处理(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()
}
}
-
rpx单位适配:
- rpx 是 uni-app 的响应式单位
- 750rpx = 屏幕宽度
- 自动适配不同屏幕尺寸
-
触摸事件优化:
- 使用原生触摸事件
- 最小滑动距离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>
🚀 打包发布
鸿蒙应用打包
- 配置
manifest.json - 在 HBuilderX 中选择"发行" -> "原生App-云打包"
- 选择鸿蒙平台
- 配置签名证书
- 打包上传
注意事项
- 准备应用图标(512x512px)
- 准备启动页图片
- 填写应用描述和权限说明
- 测试多种屏幕尺寸
📈 后续优化方向
功能扩展
- 撤销功能:保存每一步的状态
- 自定义尺寸:支持 3x3、5x5 网格
- 主题切换:多种配色方案
- 音效反馈:移动、合并音效
- 震动反馈:使用 uni.vibrateShort()
社交功能
- 排行榜:云端存储最高分
- 分享功能:分享到社交平台
- 成就系统:解锁各种成就
- 每日挑战:特殊模式挑战
体验优化
- 引导动画:首次进入时的教程
- 手势提示:显示滑动方向
- 历史记录:查看历史最高分
- 统计数据:游戏次数、时长等
💡 总结
通过这个项目,我们学到了:
- ✅ uni-app x 的基本用法:页面结构、数据绑定、事件处理
- ✅ UTS 语言特性:类型定义、类型安全
- ✅ 游戏算法实现:移动、合并、状态检测
- ✅ CSS 动画技巧:关键帧动画、过渡效果
- ✅ 响应式设计:rpx 单位、多屏适配
- ✅ 鸿蒙应用开发:平台特性、适配要点
uni-app x 是一个强大的跨平台开发框架,特别适合开发鸿蒙应用。通过本项目的实践,希望能帮助你快速上手 uni-app x 开发,创造出更多优秀的应用!
📚 参考资源
作者:坚果派小雨
项目地址: GitCode
开源协议: MIT License
如果觉得本文对你有帮助,欢迎点赞、收藏、分享!有任何问题也欢迎在评论区讨论。
💡 提示:本文涉及的完整代码已开源,你可以直接下载运行,也可以在此基础上进行二次开发。期待看到你的创意!
0 个评论
要回复文章请先登录或注册