【引言】
打地鼠游戏是一款经典的休闲游戏,玩家需要在地鼠出现时快速点击它们以获得分数。使用鸿蒙框架创建组件、管理状态、实现基本的动画效果以及处理用户交互。本文将详细介绍游戏的结构、核心算法以及代码实现。注意完整代码在最后面。
【项目概述】
游戏的主要功能包括:
1. 地鼠组件的定义:通过Hamster结构体定义了地鼠的外观,包括身体、眼睛等各个部分的样式,并支持根据单元格的宽度动态调整地鼠的尺寸。
2. 单元格类Cell:定义了游戏中的单个单元格,它具有表示地鼠是否显示的状态,并可以设置显示地鼠时的缩放选项。此外,Cell类中还包含了一些方法,比如setSelectedTrueTime()用于设置地鼠显示的时间戳,checkTime()则用来检测地鼠是否应该因为超过了预定的停留时间而被隐藏。
3. 游戏主组件Index:这是游戏的主要入口组件,它维护了游戏的核心状态,如动画间隔、出现的地鼠数量、地鼠的停留时间等。此外,它还包括了开始游戏(startGame)和结束游戏(endGame)的方法,这些方法负责初始化游戏状态和重置游戏数据。
4. 游戏界面构建:在Index组件的build方法中,定义了游戏的界面布局,包括显示计时器、得分板以及游戏区域内的各个单元格。
5. 时间控制与地鼠显示逻辑:通过TextTimer组件来控制游戏的时间,每经过一定的时间间隔,就会随机选择一些单元格显示地鼠。同时,游戏逻辑还包括了在地鼠被点击时增加玩家的得分,并执行相应的动画效果。
6. 用户交互:用户可以通过点击显示地鼠的单元格来获得分数,点击事件触发后,地鼠会被隐藏,并且游戏得分会被更新。
综上所述,该代码提供了一个完整的打地鼠游戏框架,包括地鼠的外观设计、游戏逻辑处理、时间控制以及用户交互等多个方面的功能。
【环境准备】
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
【算法分析】
1. 随机抽取算法
在游戏中,需要随机选择多个地鼠出现的位置。通过洗牌算法随机抽取可用位置的索引。
let availableIndexList: number[] = []; // 存储可用的索引
for (let i = 0; i < this.cells.length; i++) {
if (!this.cells[i].isSelected) {
availableIndexList.push(i); // 添加到可用索引列表
}
}
// 洗牌算法
for (let i = 0; i < availableIndexList.length; i++) {
let index = Math.floor(Math.random() * (availableIndexList.length - i));
let temp = availableIndexList[availableIndexList.length - i - 1];
availableIndexList[availableIndexList.length - i - 1] = availableIndexList[index];
availableIndexList[index] = temp;
}
2. 停留时间检查算法
在每个时间间隔内检查地鼠的停留时间,如果超过设定的停留时间,则将地鼠隐藏。
if (elapsedTime % 10 == 0) { // 每间隔100毫秒检查一次
for (let i = 0; i < this.cells.length; i++) {
this.cells[i].checkTime(this.hamsterStayDuration); // 检查每个单元格的停留时间
}
}
3. 游戏结束处理算法
当游戏时间结束时,显示得分并重置游戏状态。
if (elapsedTime * 10 == this.gameDuration) { // 如果计时结束
let currentScore = this.currentScore; // 获取当前得分
this.getUIContext().showAlertDialog({ // 显示结果对话框
title: '游戏结束',
message: `得分:${currentScore}`,
confirm: {
defaultFocus: true,
value: '我知道了',
action: () => {}
},
alignment: DialogAlignment.Center,
});
this.endGame(); // 结束游戏
}
【完整代码】
import { curves, window } from '@kit.ArkUI' // 导入所需的库和模块
// 定义地鼠组件
@Component
struct Hamster {
@Prop cellWidth: number // 定义一个属性,表示单元格的宽度
build() {
Stack() { // 创建一个堆叠布局
// 身体
Text()
.width(`${this.cellWidth / 2}lpx`) // 设置宽度为单元格宽度的一半
.height(`${this.cellWidth / 3 * 2}lpx`) // 设置高度为单元格高度的2/3
.backgroundColor("#b49579") // 设置背景颜色
.borderRadius({ topLeft: '50%', topRight: '50%' }) // 设置圆角
.borderColor("#2a272d") // 设置边框颜色
.borderWidth(1) // 设置边框宽度
// 嘴巴
Ellipse()
.width(`${this.cellWidth / 4}lpx`) // 设置嘴巴的宽度
.height(`${this.cellWidth / 5}lpx`) // 设置嘴巴的高度
.fillOpacity(1) // 设置填充不透明度
.fill("#e7bad7") // 设置填充颜色
.stroke("#563e3f") // 设置边框颜色
.strokeWidth(1) // 设置边框宽度
.margin({ top: `${this.cellWidth / 6}lpx` }) // 设置上边距
// 左眼睛
Ellipse()
.width(`${this.cellWidth / 9}lpx`) // 设置左眼睛的宽度
.height(`${this.cellWidth / 6}lpx`) // 设置左眼睛的高度
.fillOpacity(1) // 设置填充不透明度
.fill("#313028") // 设置填充颜色
.stroke("#2e2018") // 设置边框颜色
.strokeWidth(1) // 设置边框宽度
.margin({ bottom: `${this.cellWidth / 3}lpx`, right: `${this.cellWidth / 6}lpx` }) // 设置下边距和右边距
// 右眼睛
Ellipse()
.width(`${this.cellWidth / 9}lpx`) // 设置右眼睛的宽度
.height(`${this.cellWidth / 6}lpx`) // 设置右眼睛的高度
.fillOpacity(1) // 设置填充不透明度
.fill("#313028") // 设置填充颜色
.stroke("#2e2018") // 设置边框颜色
.strokeWidth(1) // 设置边框宽度
.margin({ bottom: `${this.cellWidth / 3}lpx`, left: `${this.cellWidth / 6}lpx` }) // 设置下边距和左边距
// 左眼瞳
Ellipse()
.width(`${this.cellWidth / 20}lpx`) // 设置左眼瞳的宽度
.height(`${this.cellWidth / 15}lpx`) // 设置左眼瞳的高度
.fillOpacity(1) // 设置填充不透明度
.fill("#fefbfa") // 设置填充颜色
.margin({ bottom: `${this.cellWidth / 2.5}lpx`, right: `${this.cellWidth / 6}lpx` }) // 设置下边距和右边距
// 右眼瞳
Ellipse()
.width(`${this.cellWidth / 20}lpx`) // 设置右眼瞳的宽度
.height(`${this.cellWidth / 15}lpx`) // 设置右眼瞳的高度
.fillOpacity(1) // 设置填充不透明度
.fill("#fefbfa") // 设置填充颜色
.margin({ bottom: `${this.cellWidth / 2.5}lpx`, left: `${this.cellWidth / 6}lpx` }) // 设置下边距和左边距
}.width(`${this.cellWidth}lpx`).height(`${this.cellWidth}lpx`) // 设置组件的宽度和高度
}
}
// 定义单元格类
@ObservedV2
class Cell {
@Trace scaleOptions: ScaleOptions = { x: 1, y: 1 }; // 定义缩放选项
@Trace isSelected: boolean = false // true表示显示地鼠,false表示隐藏地鼠
cellWidth: number // 单元格宽度
selectTime: number = 0 // 选择时间
constructor(cellWidth: number) { // 构造函数
this.cellWidth = cellWidth // 初始化单元格宽度
}
setSelectedTrueTime() { // 设置选择时间
this.selectTime = Date.now() // 记录当前时间
this.isSelected = true // 设置为选中状态
}
checkTime(stayDuration: number) { // 检查停留时间
if (this.isSelected) { // 如果当前是选中状态
if (Date.now() - this.selectTime >= stayDuration) { // 如果停留时间超过设定值
this.selectTime = 0 // 重置选择时间
this.isSelected = false // 设置为未选中状态
}
}
}
}
// 定义文本计时器修饰符类
class MyTextTimerModifier implements ContentModifier<TextTimerConfiguration> {
constructor() {}
applyContent(): WrappedBuilder<[TextTimerConfiguration]> { // 应用内容
return wrapBuilder(buildTextTimer) // 返回构建文本计时器的函数
}
}
// 构建文本计时器的函数
@Builder
function buildTextTimer(config: TextTimerConfiguration) {
Column() {
Stack({ alignContent: Alignment.Center }) { // 创建一个堆叠布局,内容居中对齐
Circle({ width: 150, height: 150 }) // 创建一个圆形
.fill(config.started ? (config.isCountDown ? 0xFF232323 : 0xFF717171) : 0xFF929292) // 根据状态设置填充颜色
Column() {
Text(config.isCountDown ? "倒计时" : "正计时").fontColor(Color.White) // 显示计时状态
Text(
(config.isCountDown ? "剩余" : "已经过去了") + (config.isCountDown ?
(Math.max(config.count / 1000 - config.elapsedTime / 100, 0)).toFixed(0) // 计算剩余时间
: ((config.elapsedTime / 100).toFixed(0)) // 计算已过去时间
) + "秒"
).fontColor(Color.White) // 显示时间
}
}
}
}
// 定义游戏主组件
@Entry
@Component
struct Index {
@State animationIntervalCount: number = 0 // 动画间隔计数
@State appearanceCount: number = 4 // 每次出现的地鼠数量
@State animationInterval: number = 1000 // 地鼠出现的间隔时间
@State hamsterStayDuration: number = 1500 // 地鼠停留时间
@State gameDuration: number = 30000 // 游戏总时长
@State randomPositionIndex: number = 0 // 随机位置
@State cells: Cell[] = [] // 存储地鼠单元格
@State cellWidth: number = 100 // 单元格宽度
@State currentScore: number = 0 // 当前游戏得分
@State timerModifier: MyTextTimerModifier = new MyTextTimerModifier() // 计时器修饰符
countdownTimerController: TextTimerController = new TextTimerController() // 倒计时控制器
timerController: TextTimerController = new TextTimerController() // 正计时控制器
aboutToAppear(): void {
// 设置当前app以横屏方式显示
window.getLastWindow(getContext()).then((windowClass) => {
windowClass.setPreferredOrientation(window.Orientation.LANDSCAPE) // 设置为横屏
})
// 显示10个地鼠坑位
for (let i = 0; i < 10; i++) {
this.cells.push(new Cell(this.cellWidth)) // 初始化10个单元格
}
}
endGame() { // 结束游戏
this.animationIntervalCount = 0 // 重置动画间隔计数
this.currentScore = 0 // 重置得分
for (let i = 0; i < this.cells.length; i++) {
this.cells[i].isSelected = false // 隐藏所有地鼠
}
this.countdownTimerController.reset() // 重置倒计时
this.timerController.reset() // 重置正计时
}
startGame() { // 开始游戏
this.endGame() // 结束当前游戏,重置所有状态
this.countdownTimerController.start() // 启动倒计时控制器
this.timerController.start() // 启动正计时控制器
}
build() { // 构建游戏界面
Row() { // 创建一个水平布局
// 显示时间与得分
Column({ space: 30 }) { // 创建一个垂直布局,设置间距
// 总时长
Column({ space: 5 }) { // 创建一个垂直布局,设置间距
Text(`倒计时长(秒)`).fontColor(Color.Black) // 显示倒计时长度的文本
Counter() { // 创建一个计数器组件
Text(`${this.gameDuration / 1000}`) // 显示游戏总时长(秒)
.fontColor(Color.Black) // 设置字体颜色
}
.width(300) // 设置计数器宽度
.onInc(() => { // 增加按钮的点击事件
this.gameDuration += 1000; // 每次增加1秒
}).onDec(() => { // 减少按钮的点击事件
this.gameDuration -= 1000; // 每次减少1秒
this.gameDuration = this.gameDuration < 1000 ? 1000 : this.gameDuration; // 确保最小值为1秒
});
}
// 每次出现个数
Column({ space: 5 }) { // 创建一个垂直布局,设置间距
Text(`每次出现(个)`).fontColor(Color.Black) // 显示每次出现的地鼠数量的文本
Counter() { // 创建一个计数器组件
Text(`${this.appearanceCount}`) // 显示每次出现的地鼠数量
.fontColor(Color.Black) // 设置字体颜色
}
.width(300) // 设置计数器宽度
.onInc(() => { // 增加按钮的点击事件
this.appearanceCount += 1; // 每次增加1个
}).onDec(() => { // 减少按钮的点击事件
this.appearanceCount -= 1; // 每次减少1个
this.appearanceCount = this.appearanceCount < 1 ? 1 : this.appearanceCount; // 确保最小值为1
});
}
// 地鼠每隔多长时间显示
Column({ space: 5 }) { // 创建一个垂直布局,设置间距
Text(`出现间隔(毫秒)`).fontColor(Color.Black) // 显示地鼠出现间隔的文本
Counter() { // 创建一个计数器组件
Text(`${this.animationInterval}`) // 显示地鼠出现的间隔时间
.fontColor(Color.Black) // 设置字体颜色
}
.width(300) // 设置计数器宽度
.onInc(() => { // 增加按钮的点击事件
this.animationInterval += 100; // 每次增加100毫秒
}).onDec(() => { // 减少按钮的点击事件
this.animationInterval -= 100; // 每次减少100毫秒
this.animationInterval = this.animationInterval < 100 ? 100 : this.animationInterval; // 确保最小值为100毫秒
});
}
// 地鼠停留时间
Column({ space: 5 }) { // 创建一个垂直布局,设置间距
Text(`停留间隔(毫秒)`).fontColor(Color.Black) // 显示地鼠停留时间的文本
Counter() { // 创建一个计数器组件
Text(`${this.hamsterStayDuration}`) // 显示地鼠的停留时间
.fontColor(Color.Black) // 设置字体颜色
}
.width(300) // 设置计数器宽度
.onInc(() => { // 增加按钮的点击事件
this.hamsterStayDuration += 100; // 每次增加100毫秒
}).onDec(() => { // 减少按钮的点击事件
this.hamsterStayDuration -= 100; // 每次减少100毫秒
this.hamsterStayDuration = this.hamsterStayDuration < 100 ? 100 : this.hamsterStayDuration; // 确保最小值为100毫秒
});
}
}.layoutWeight(1).padding({ left: 50 }) // 设置布局权重和左边距
// 游戏区
Flex({ wrap: FlexWrap.Wrap }) { // 创建一个可换行的弹性布局
ForEach(this.cells, (cell: Cell, index: number) => { // 遍历所有单元格
Stack() { // 创建一个堆叠布局
// 洞
Ellipse()
.width(`${this.cellWidth / 1.2}lpx`) // 设置洞的宽度
.height(`${this.cellWidth / 2.2}lpx`) // 设置洞的高度
.fillOpacity(1) // 设置填充不透明度
.fill("#020101") // 设置填充颜色
.stroke("#020101") // 设置边框颜色
.strokeWidth(1) // 设置边框宽度
.margin({ top: `${this.cellWidth / 2}lpx` }) // 设置上边距
// 地鼠
Hamster({ cellWidth: this.cellWidth }) // 创建地鼠组件
.visibility(cell.isSelected ? Visibility.Visible : Visibility.None) // 根据状态设置可见性
.scale(cell.scaleOptions) // 设置缩放选项
}.width(`${this.cellWidth}lpx`).height(`${this.cellWidth}lpx`) // 设置堆叠布局的宽度和高度
.margin({ left: `${index == 0 || index == 7 ? this.cellWidth / 2 : 0}lpx` }) // 设置左边距
.onClick(() => { // 点击事件
if (cell.isSelected) { // 如果当前单元格是选中状态
animateToImmediately({ // 执行动画
duration: 200, // 动画持续时间
curve: curves.springCurve(10, 1, 228, 30), // 动画曲线
onFinish: () => { // 动画结束后的回调
cell.isSelected = false // 隐藏地鼠
cell.scaleOptions = { x: 1.0, y: 1.0 }; // 重置缩放
this.currentScore += 1 // 增加得分
}
}, () => {
cell.scaleOptions = { x: 0, y: 0 }; // 动画开始时缩放到0
})
}
})
})
}.width(`${this.cellWidth * 4}lpx`) // 设置游戏区的宽度
// 操作按钮
Column({ space: 20 }) { // 创建一个垂直布局,设置间距
// 倒计时
TextTimer({ isCountDown: true, count: this.gameDuration, controller: this.countdownTimerController }) // 创建倒计时组件
.contentModifier(this.timerModifier) // 应用计时器修饰符
.onTimer((utc: number, elapsedTime: number) => { // 定义计时器的回调
// 每隔指定时间随机显示地鼠
if (elapsedTime * 10 >= this.animationInterval * this.animationIntervalCount) { // 判断是否达到显示地鼠的时间
this.animationIntervalCount++ // 增加动画间隔计数
// 获取可以出现的位置集合
let availableIndexList: number[] = [] // 存储可用的索引
for (let i = 0; i < this.cells.length; i++) { // 遍历所有单元格
if (!this.cells[i].isSelected) { // 如果当前单元格未被选中
availableIndexList.push(i) // 添加到可用索引列表
}
}
// 根据每次出现次数 appearanceCount 利用洗牌算法随机抽取
for (let i = 0; i < availableIndexList.length; i++) { // 遍历可用索引列表
let index = Math.floor(Math.random() * (availableIndexList.length - i)) // 随机选择一个索引
let temp = availableIndexList[availableIndexList.length - i - 1] // 交换位置
availableIndexList[availableIndexList.length - i - 1] = availableIndexList[index]
availableIndexList[index] = temp
}
// 随机抽取 appearanceCount,取前几个已经打乱好的顺序
for (let i = 0; i < availableIndexList.length; i++) { // 遍历可用索引列表
if (i < this.appearanceCount) { // 如果索引小于每次出现的数量
this.cells[availableIndexList[i]].setSelectedTrueTime() // 设置选中的单元格为显示状态
}
}
}
if (elapsedTime % 10 == 0) { // 每隔100毫秒检查一次
console.info('检查停留时间是否已过,如果过了就隐藏地鼠') // 输出调试信息
for (let i = 0; i < this.cells.length; i++) { // 遍历所有单元格
this.cells[i].checkTime(this.hamsterStayDuration) // 检查每个单元格的停留时间
}
}
if (elapsedTime * 10 >= this.gameDuration) { // 如果计时结束
let currentScore = this.currentScore // 获取当前得分
this.getUIContext().showAlertDialog({ // 显示结果对话框
// 显示结果页
title: '游戏结束', // 对话框标题
message: `得分:${currentScore}`, // 显示得分信息
confirm: { // 确认按钮配置
defaultFocus: true, // 默认焦点
value: '我知道了', // 按钮文本
action: () => { // 点击后的动作
// 这里可以添加点击确认后的逻辑
}
},
onWillDismiss: () => { // 关闭前的动作
// 这里可以添加关闭前的逻辑
},
alignment: DialogAlignment.Center, // 对齐方式为中心
});
this.endGame() // 结束游戏
}
})
Text(`当前得分:${this.currentScore}`) // 显示当前得分
Button('开始游戏').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建开始游戏按钮
this.startGame() // 点击后开始游戏
})
Button('结束游戏').clickEffect({ level: ClickEffectLevel.LIGHT }).onClick(() => { // 创建结束游戏按钮
this.endGame() // 点击后结束游戏
})
}.layoutWeight(1) // 设置布局权重
}
.height('100%') // 设置整体高度为100%
.width('100%') // 设置整体宽度为100%
.backgroundColor("#61ac57") // 设置背景颜色
.justifyContent(FlexAlign.SpaceBetween) // 设置内容对齐方式
}
}