【鸿蒙版消消乐 适合6.0.0版本】
本项目为鸿蒙开发课程的大作业,目标是使用实现一个类似《开心消消乐》的小游戏。主要功能包括:棋盘初始化(8×8,可扩展)点击选中 + 邻格交换三消判定(横向/纵向 ≥3)消除动画(淡出)方块下落 gravity()顶部补充 fillTop()得分机制(基础分 + 关卡加成)关卡升级(每 100 分 +1)重置功能特殊方块:4 连消 → 条纹方块 tile7(消行/消列)5 连消 → 彩色炸弹 til
基于鸿蒙 HarmonyOS 6.0 实现一个类似开心消消乐游戏(含完整代码)
一、项目介绍
本项目为鸿蒙开发课程的大作业,目标是使用 ArkTS + Declarative UI 实现一个类似《开心消消乐》的小游戏。
主要功能包括:
-
棋盘初始化(8×8,可扩展)
-
点击选中 + 邻格交换
-
三消判定(横向/纵向 ≥3)
-
消除动画(淡出)
-
方块下落 gravity()
-
顶部补充 fillTop()
-
得分机制(基础分 + 关卡加成)
-
关卡升级(每 100 分 +1)
-
重置功能
-
特殊方块:
-
4 连消 → 条纹方块 tile7(消行/消列)
-
5 连消 → 彩色炸弹 tile8(全屏/同色消除)
-
-
UI 完全贴合鸿蒙 6.0 写法(无旧语法错误)
二、游戏整体架构
Index.ets
│
├── 状态管理(@State)
│ ├── grid[][] 游戏棋盘
│ ├── selected 当前选中
│ ├── score 得分
│ ├── level 关卡
│ ├── animating 动画锁
│ ├── comboMessage 提示文本
│
├── initGrid() 初始化棋盘
├── onTap() 处理点击 + 交换逻辑
├── performSwap... 判断普通交换/特殊方块触发
├── findMatches() 搜索三消结构
├── resolveSwap() 处理连续消除
├── gravity() 下落补充
├── fillTop() 顶部新的随机方块
├── tileScore() 得分公式
├── refreshLevel() 关卡更新
│
└── build() 游戏 UI
三、棋盘算法设计
1. 棋盘表示
棋盘使用:
grid: number[][] // 8×8 的数字矩阵
每个值代表一个方块:
1~6 普通 tile1~tile6
7 条纹方块(消行/消列)
8 彩色炸弹(消色 or 全屏
2. 初始化棋盘(避免出生三消)
do {
t = random 1~6
} while (createsMatchAt(g, r, c, t)
保证游戏不会一开始就有三消。
3. 三消查找算法
横向扫描:
for r in rows:
while c < GRID:
if 连续 >=3 → 记录 group
纵向扫描:
for c in cols:
while r < GRID:
if 连续 >=3 → 记录 group
最终输出:
-
所有被消除的位置
-
每个连消的 group(用于生成 tile7/tile8)
4. 下落算法 gravity()
对每一列,从下往上填:
let write = this.GRID - 1
for (let r = this.GRID - 1; r >= 0; r--) {
if (g[r][c] !== 0) {
g[write][c] = g[r][c]
if (write !== r) {
g[r][c] = 0
}
write--
5. 顶部补充 fillTop()
所有 0 都变成随机普通方块。
四、特殊方块设计(课程加分点)
1)4 连消 → tile7 条纹方块
-
横向交换 → 消整行
-
纵向交换 → 消整列
2)5 连消 → tile8 彩色炸弹
-
与普通色交换 → 清空所有同色
-
两个 tile8 相撞 → 清空全盘(爽爆)
这是课程设计里最亮眼的部分。
五、得分与关卡系统
每消除一个方块:
基础分:10
关卡加成: (level - 1) * 5
总分 = 数量 × (10 + level*5
每达到 100 分:
level += 1
六、UI 架构
采用 HarmonyOS 6.0 Declarative UI:
-
背景图 img
-
tile 图片:tile1~tile8
-
边框显示选中状态
-
弹出提示:Amazing、Good
七、完整 可运行 Index.ets 代码
下面即为你提供的最新版稳定代码,已通过鸿蒙 6.0 编译。
可直接替换 entry/src/main/ets/pages/Index.ets 使用。:
interface GridPosition {
r: number;
c: number;
}
interface SwapResult {
producedEffect: boolean;
}
interface MatchesResult {
cells: GridPosition[];
groups: GridPosition[][];
}
interface SpecialSpawn {
pos: GridPosition;
type: number;
}
@Entry
@Component
struct Index {
private readonly GRID: number = 8
private readonly TYPES: number = 6 // 普通颜色数量(tile1~tile6)
@State grid: number[][] = []
@State selected: GridPosition | null = null
@State score: number = 0
@State level: number = 1
@State animating: boolean = false
@State comboMessage: string = ""
onPageShow() {
this.initGrid()
}
//初始化
private initGrid() {
const g: number[][] = []
for (let r = 0; r < this.GRID; r++) {
g.push([])
for (let c = 0; c < this.GRID; c++) {
let t: number
do {
t = Math.floor(Math.random() * this.TYPES) + 1
} while (this.createsMatchAt(g, r, c, t))
g[r].push(t)
}
}
this.grid = g
this.score = 0
this.level = 1
this.selected = null
this.comboMessage = ""
}
private createsMatchAt(g: number[][], r: number, c: number, t: number): boolean {
if (c >= 2 && g[r][c - 1] === t && g[r][c - 2] === t) {
return true
}
if (r >= 2 && g[r - 1][c] === t && g[r - 2][c] === t) {
return true
}
return false
}
//工具
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms))
}
private async showComboMessage(msg: string) {
this.comboMessage = msg
await this.delay(1000)
this.comboMessage = ""
}
// 分数与关卡
private tileScore(): number {
return 10 + (this.level - 1) * 5
}
private refreshLevel() {
this.level = Math.floor(this.score / 100) + 1
}
//点击 / 交换(含特殊方块逻辑)
private async onTap(r: number, c: number) {
if (this.animating) {
return
}
if (!this.selected) {
this.selected = { r, c }
return
}
const s = this.selected
const dr = Math.abs(s.r - r)
const dc = Math.abs(s.c - c)
// 非邻近直接取消选择
if (!((dr === 1 && dc === 0) || (dr === 0 && dc === 1))) {
this.selected = null
return
}
// 锁定交互
this.animating = true
// 执行交换(swap 内会处理 tile7/tile8 的特效)
// 记录原位置以便回退
const a1: GridPosition = { r: s.r, c: s.c }
const a2: GridPosition = { r: r, c: c }
// 交换并处理返回的标志,swap 会直接修改 this.grid
const swapResult = this.performSwapWithSpecial(a1.r, a1.c, a2.r, a2.c)
if (!swapResult.producedEffect) {
// 普通交换:判断是否有消除
const matchesResult = this.findMatches()
const matched = matchesResult.cells
if (matched.length === 0) {
// 无消除:1s 回退
await this.delay(1000)
this.swap(a1.r, a1.c, a2.r, a2.c)
this.animating = false
this.selected = null
return
}
// 有消除:进入消除流程
await this.resolveSwap()
} else {
// swapResult.producedEffect 表示 tile7/tile8 被触发并已更新棋盘
// 在特殊效果触发后,需要继续消除链(可能产生连锁)
await this.resolveSwap()
}
this.selected = null
}
// 将普通交换与特殊方块的触发分开:如果交换中涉及 tile7/8,直接触发对应效果并返回 producedEffect=true
private performSwapWithSpecial(r1: number, c1: number, r2: number, c2: number): SwapResult {
const g = this.grid.map(row => [...row])
const t1 = g[r1][c1]
const t2 = g[r2][c2]
// tile8(彩色炸弹)
if (t1 === 8 || t2 === 8) {
const otherType = (t1 === 8 ? t2 : t1)
// 两个彩弹互爆:清空全盘
if (otherType === 8) {
for (let rr = 0; rr < this.GRID; rr++) {
for (let cc = 0; cc < this.GRID; cc++) {
g[rr][cc] = 0
}
}
this.grid = g
//立刻重力 + 填充
this.gravity()
this.fillTop()
this.showComboMessage("BOOM!")
return { producedEffect: true }
}
// 单彩弹:消除所有同色
for (let rr = 0; rr < this.GRID; rr++) {
for (let cc = 0; cc < this.GRID; cc++) {
if (g[rr][cc] === otherType) {
g[rr][cc] = 0
}
}
}
this.grid = g
//立刻重力 + 填充
this.gravity()
this.fillTop()
this.showComboMessage("彩弹发挥!")
return { producedEffect: true }
}
// tile7(条纹)
if (t1 === 7 || t2 === 7) {
const stripePos: GridPosition = (t1 === 7 ? { r: r1, c: c1 } : { r: r2, c: c2 })
const isHorizontalSwap = (r1 === r2)
if (isHorizontalSwap) {
// 左右交换 → 消行
for (let cc = 0; cc < this.GRID; cc++) {
g[stripePos.r][cc] = 0
}
this.grid = g
//立刻重力 + 填充
this.gravity()
this.fillTop()
this.showComboMessage("good!消行")
return { producedEffect: true }
} else {
// 上下交换 → 消列
for (let rr = 0; rr < this.GRID; rr++) {
g[rr][stripePos.c] = 0
}
this.grid = g
//立刻重力 + 填充
this.gravity()
this.fillTop()
this.showComboMessage("good!消列")
return { producedEffect: true }
}
}
// 普通交换(无特殊方块)
g[r1][c1] = t2
g[r2][c2] = t1
this.grid = g
return { producedEffect: false }
}
// 简单 swap(只用于回退普通交换)
private swap(r1: number, c1: number, r2: number, c2: number) {
const newGrid = this.grid.map(row => [...row])
const temp = newGrid[r1][c1]
newGrid[r1][c1] = newGrid[r2][c2]
newGrid[r2][c2] = temp
this.grid = newGrid
}
//查找匹配(返回 cells 与 groups)
private findMatches(): MatchesResult {
const g = this.grid
const cellMap = new Map<string, GridPosition>()
const groups: GridPosition[][] = []
// 横向
for (let r = 0; r < this.GRID; r++) {
let c = 0
while (c < this.GRID) {
const start = c
const val = g[r][c]
c++
while (c < this.GRID && g[r][c] === val) {
c++
}
if (val && c - start >= 3) {
const group: GridPosition[] = []
for (let i = start; i < c; i++) {
const pos: GridPosition = { r: r, c: i }
group.push(pos)
cellMap.set(`${r}_${i}`, pos)
}
groups.push(group)
}
}
}
// 纵向
for (let c = 0; c < this.GRID; c++) {
let r = 0
while (r < this.GRID) {
const start = r
const val = g[r][c]
r++
while (r < this.GRID && g[r][c] === val) {
r++
}
if (val && r - start >= 3) {
const group: GridPosition[] = []
for (let i = start; i < r; i++) {
const pos: GridPosition = { r: i, c: c }
group.push(pos)
cellMap.set(`${i}_${c}`, pos)
}
groups.push(group)
}
}
}
return { cells: Array.from(cellMap.values()), groups: groups }
}
//处理消除链(包含生成 tile7 / tile8)
private async resolveSwap() {
while (true) {
const matchesResult = this.findMatches()
const cells = matchesResult.cells
const groups = matchesResult.groups
if (cells.length === 0) {
break
}
// 评分:普通按消除块数计分(特殊方块在被触发时也会被计入被清除的数量)
this.score += cells.length * this.tileScore()
// 制作新格子副本
let newGrid = this.grid.map(row => [...row])
// 我们先标记哪些位置需要被清除(非特殊的会被清零),同时记录需要生成特殊方块的位置
const toClear = new Map<string, GridPosition>()
const spawnSpecial: SpecialSpawn[] = []
for (const group of groups) {
// 如果 group 长度 >=4 且包含至少一个普通色(1..6),我们在 group 中选一个位置生成特殊方块
// 先检查 group 的长度
const len = group.length
if (len >= 4) {
// 选择生成点:group 中靠中间的那个
const center = group[Math.floor(group.length / 2)]
// 仅当 center 不是其它特殊方块时生成(若已经为 7/8,则不覆盖)
const curVal = newGrid[center.r][center.c]
if (curVal >= 1 && curVal <= this.TYPES) {
if (len === 4) {
spawnSpecial.push({ pos: center, type: 7 })
// 弹窗提示
this.showComboMessage("太棒了!")
} else if (len >= 5) {
spawnSpecial.push({ pos: center, type: 8 })
this.showComboMessage("Amazing!")
}
}
}
// 将组内所有位置标记为清除(但不要立刻覆盖 spawnSpecial 的位置)
for (const p of group) {
toClear.set(`${p.r}_${p.c}`, p)
}
}
// 执行清除:先把所有 toClear 的位置设为 0
for (const entry of toClear) {
const p = entry[1]
newGrid[p.r][p.c] = 0
}
// 在清除后,根据 spawnSpecial 将特殊方块放回(覆盖被清除位置)
for (const s of spawnSpecial) {
newGrid[s.pos.r][s.pos.c] = s.type
}
this.grid = newGrid
// 下落与补充
this.gravity()
this.fillTop()
// 等待一小段时间让 UI 显示变化
await this.delay(180)
}
this.refreshLevel()
this.animating = false
}
//下落
private gravity() {
const g = this.grid.map(row => [...row])
for (let c = 0; c < this.GRID; c++) {
let write = this.GRID - 1
for (let r = this.GRID - 1; r >= 0; r--) {
if (g[r][c] !== 0) {
g[write][c] = g[r][c]
if (write !== r) {
g[r][c] = 0
}
write--
}
}
}
this.grid = g
}
//填充顶部
private fillTop() {
const g = this.grid.map(row => [...row])
for (let r = 0; r < this.GRID; r++) {
for (let c = 0; c < this.GRID; c++) {
if (g[r][c] === 0) {
// 随机生成普通方块(不直接生成特殊方块)
g[r][c] = Math.floor(Math.random() * this.TYPES) + 1
}
}
}
this.grid = g
}
//重置
private onReset() {
this.initGrid()
}
//资源路径
private tileSrc(type: number) {
// tile1~tile8
return $r(`app.media.tile${type}`)
}
//UI
build() {
Column() {
// 背景图层
Image($r("app.media.img"))
.width("100%")
.height("100%")
.objectFit(ImageFit.Fill)
.position({ x: "0%", y: "0%" })
.zIndex(-1)
Text("开心消消乐")
.fontSize(22)
.margin({ bottom: 10 })
Row() {
Text(`分数:${this.score}`).fontSize(18).margin({ right: 20 })
Text(`关卡:${this.level}`).fontSize(18)
}.margin({ bottom: 10 })
Button("重置")
.onClick(() => this.onReset())
.margin({ bottom: 10 })
// 棋盘
Column() {
ForEach(this.grid, (row: number[], r: number) => {
Row() {
ForEach(row, (cell: number, c: number) => {
Image(this.tileSrc(cell))
.width(40)
.height(40)
.margin(2)
.border({
width: this.selected?.r === r && this.selected?.c === c ? 3 : 1,
color: this.selected?.r === r && this.selected?.c === c ? 0xFF9800 : 0xCCCCCC
})
.onClick(() => this.onTap(r, c))
})
}
})
}
// 弹窗提示(居中浮层)
if (this.comboMessage !== "") {
Column() {
Text(this.comboMessage)
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
.backgroundColor('#CC000000')
.padding(16)
.borderRadius(10)
}
.alignItems(HorizontalAlign.Center)
.position({ x: '50%', y: '18%' })
.zIndex(10)
}
}.padding(12)
}
}
八、总结
本次鸿蒙 6.0《开心消消乐》大作业完整实现了:
-
游戏核心机制
-
UI 架构
-
连续三消
-
特殊方块
-
关卡系统
-
完整可运行代码
本项目非常适合作为课程设计,也能展示对鸿蒙 UI、ArkTS 数据驱动、响应式渲染的理解。
更多推荐


所有评论(0)