写点什么

鸿蒙开发案例:打地鼠

作者:zhongcx
  • 2024-10-31
    北京
  • 本文字数:8995 字

    阅读完需:约 30 分钟

【引言】

打地鼠游戏是一款经典的休闲游戏,玩家需要在地鼠出现时快速点击它们以获得分数。使用鸿蒙框架创建组件、管理状态、实现基本的动画效果以及处理用户交互。本文将详细介绍游戏的结构、核心算法以及代码实现。注意完整代码在最后面。

【项目概述】

游戏的主要功能包括:

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' // 导入所需的库和模块
// 定义地鼠组件@Componentstruct 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`) // 设置组件的宽度和高度 }}
// 定义单元格类@ObservedV2class 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) // 返回构建文本计时器的函数 }}
// 构建文本计时器的函数@Builderfunction 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@Componentstruct 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) // 设置内容对齐方式 }}
复制代码


用户头像

zhongcx

关注

还未添加个人签名 2024-09-27 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙开发案例:打地鼠_zhongcx_InfoQ写作社区