写点什么

鸿蒙开发案例:垃圾分类

作者:zhongcx
  • 2024-10-25
    北京
  • 本文字数:6633 字

    阅读完需:约 22 分钟

【引言】

随着人们对环保意识的提升,正确分类垃圾成为了一个重要的社会议题。本文将探讨一个基于 HarmonyOS NEXT 的垃圾分类小游戏,该游戏利用了 ArkUI 框架提供的动画功能以及一些简单的算法来实现交互式的学习体验。

【开发环境】

开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.814

工程版本:API 12

【算法分析】

1、动画算法:Spring Curve

为了增强用户体验,游戏中采用了弹簧曲线(springCurve)来模拟现实世界中的物理现象。springCurve 是一种常用的缓动曲线,可以用来模拟带有阻尼的弹簧效果。在游戏中,当用户将垃圾物品拖拽到正确的分类区域时,动画会产生一种自然的回弹效果,使得整个过程更加生动有趣。

animateToImmediately({    duration:800,    curve: curves.springCurve(0, 20, 90, 20),    // 其他配置});
复制代码

这里 curves.springCurve()接受四个参数,分别代表初始偏移量、振幅、周期和阻尼系数,通过这些参数可以控制动画的弹性效果。

2、随机算法:Fisher-Yates 洗牌算法

为了保持游戏的新鲜感,每次启动游戏时都需要随机打乱垃圾物品的顺序。游戏中采用的是 Fisher-Yates 洗牌算法,这是一种在线性时间内生成一个有限集合的随机排列的方法。

shuffleItems() {    for (let i = this.garbageItems.length - 1; i > 0; i--) {        const j = Math.floor(Math.random() * (i + 1));        let temp = this.garbageItems[i];        this.garbageItems[i] = this.garbageItems[j];        this.garbageItems[j] = temp;    }}
复制代码

Fisher-Yates 洗牌算法的核心思想是从最后一个元素开始,逐个向前交换元素的位置,直到第一个元素为止。每次选择一个从当前位置到数组末尾之间的随机元素与当前位置的元素交换位置。

3、粒子动画:Particle System

除了上述的动画,游戏中还引入了粒子系统来增强视觉效果。粒子系统是计算机图形学中的一种特效技术,它可以用来模拟火焰、水流、爆炸等效果。在这个游戏中,当用户成功分类垃圾时,会在屏幕中央产生粒子效果,以此来庆祝用户的正确分类。

if (this.showAnimation) {    Particle({        particles: [           ...
复制代码

【完整代码】

import { curves } from '@kit.ArkUI'; // 导入ArkUI工具包中的曲线模块// 定义垃圾项目类class GarbageItem {  name: string; // 垃圾名称  type: number; // 垃圾类型  description?: string; // 垃圾描述,可选  // 构造函数初始化垃圾项目  constructor(name: string, type: number, description?: string) {    this.name = name; // 设置垃圾名称    this.type = type; // 设置垃圾类型    this.description = description || ""; // 设置垃圾描述,默认为空字符串  }}// 定义垃圾类别类class GarbageCategory {  name: string; // 类别名称  type: number; // 类别类型  color: string; // 类别颜色  description: string; // 类别描述  // 构造函数初始化垃圾类别  constructor(name: string, type: number, color: string, description: string) {    this.name = name; // 设置类别名称    this.type = type; // 设置类别类型    this.color = color; // 设置类别颜色    this.description = description; // 设置类别描述  }}// 使用组件装饰器定义一个名为Index的应用入口@Entry@Componentstruct Index {  // 定义状态变量  @State currentQuestionIndex: number = 0; // 当前题目索引  @State quizResults: string[] = []; // 测验结果数组  @State totalScore: number = 0; // 总得分  @State showAnimation: boolean = false; // 是否显示动画  @State scaleOptions: ScaleOptions = { x: 1, y: 1 }; // 缩放选项  @State itemXPosition: number = 0; // 物品X轴位置  @State itemOpacity: number = 1.0; // 物品透明度  // 初始化垃圾类别数组  @State garbageCategories: GarbageCategory[] = [    new GarbageCategory("有害垃圾", 0, "#e2413f", "对人体健康或自然环境可能造成直接或潜在危害的生活垃圾"), // 创建有害垃圾类别    new GarbageCategory("可回收物", 1, "#1c6bb5", "适宜回收和资源利用的物品"), // 创建可回收物类别    new GarbageCategory("厨余垃圾", 2, "#4ca84e", "上海称湿垃圾,易腐烂的、含有机质的生活垃圾"), // 创建厨余垃圾类别    new GarbageCategory("其他垃圾", 3, "#5f5f5f", "上海称干垃圾,不能归类于以上三类的生活垃圾"), // 创建其他垃圾类别  ];  @State garbageItems: GarbageItem[] = [    new GarbageItem("菜帮菜叶", 2),    new GarbageItem("剩菜剩饭", 2),    new GarbageItem("过期食品", 2),    new GarbageItem("瓜果皮壳", 2),    new GarbageItem("鱼骨鱼刺", 2),    new GarbageItem("鸡蛋及蛋壳", 2),    new GarbageItem("残枝落叶", 2),    new GarbageItem("茶叶渣", 2),    new GarbageItem("酒瓶", 1,),    new GarbageItem("玻璃杯", 1),    new GarbageItem("调味瓶", 1),    new GarbageItem("图书", 1),    new GarbageItem("打印纸", 1),    new GarbageItem("信封", 1),    new GarbageItem("易拉罐", 1),    new GarbageItem("金属刀具", 1),    new GarbageItem("奶粉桶", 1),    new GarbageItem("衣服裤子", 1),    new GarbageItem("毛绒玩具", 1),    new GarbageItem("鞋", 1),    new GarbageItem("饮料瓶", 1),    new GarbageItem("塑料盆", 1),    new GarbageItem("食用油桶", 1),    new GarbageItem("洗衣机", 1),    new GarbageItem("电烤箱", 1),    new GarbageItem("电视机", 1),    new GarbageItem("充电电池", 0),    new GarbageItem("废含汞荧光灯管", 0),    new GarbageItem("过期药品及其包装物", 0),    new GarbageItem("油漆桶", 0),    new GarbageItem("血压计", 0),    new GarbageItem("废水银温度计", 0),    new GarbageItem("杀虫喷雾罐", 0),    new GarbageItem("废X光片等感光胶片", 0),    new GarbageItem("食品袋", 3),    new GarbageItem("大棒骨", 3),    new GarbageItem("创可贴", 3),    new GarbageItem("污损塑料袋", 3),    new GarbageItem("烟屁", 3),    new GarbageItem("陶瓷碎片", 3),    new GarbageItem("餐巾纸", 3, "厕纸、卫生纸遇水即溶,不算可回收的“纸张”,类似的还有烟盒等。"),    new GarbageItem("卫生纸", 3, "厕纸、卫生纸遇水即溶,不算可回收的“纸张”,类似的还有烟盒等。"),  ];
// 在组件即将出现时重置测验 aboutToAppear(): void { this.resetQuiz(); // 调用重置测验方法 } // 重置测验状态 resetQuiz() { this.quizResults = []; // 清空测验结果 this.totalScore = 0; // 清零总得分 this.currentQuestionIndex = 0; // 重置当前题目索引 this.shuffleItems(); // 打乱垃圾项目顺序 } // 打乱垃圾项目顺序 shuffleItems() { for (let i = this.garbageItems.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); // 随机索引 let temp = this.garbageItems[i]; // 临时存储 this.garbageItems[i] = this.garbageItems[j]; // 交换位置 this.garbageItems[j] = temp; // 交换位置 } } // 检查用户选择的答案 checkAnswer(categoryType: number) { const currentItem = this.garbageItems[this.currentQuestionIndex]; // 获取当前垃圾项目 this.quizResults.push(`${currentItem.name}(${this.garbageCategories[categoryType].name})【${currentItem.type === categoryType ? "✔" : this.garbageCategories[currentItem.type].name}】`); // 添加到测验结果中 if (currentItem.type === categoryType) { // 如果答案正确 this.totalScore += 10; // 加十分 } this.currentQuestionIndex++; // 进入下一个题目 if (this.currentQuestionIndex >= 10) { // 如果完成了十个题目 this.displayResults(); // 显示结果 this.resetQuiz(); // 重置测验 } } // 显示测验结果 displayResults() { let sheets: SheetInfo[] = []; // 初始化结果页面列表 for (let i = 0; i < this.quizResults.length; i++) { // 循环添加每一个结果 sheets.push({ title: this.quizResults[i], // 设置标题为测验结果 action: () => { // 不执行任何操作 } }); } this.getUIContext().showActionSheet({ // 显示结果页 title: '成绩单', // 标题 message: `总分数:${this.totalScore}`, // 分数信息 confirm: { // 确认按钮 defaultFocus: true, // 默认焦点 value: '我知道了', // 按钮文本 action: () => { // 点击后的动作 } }, onWillDismiss: () => { // 关闭前的动作 }, alignment: DialogAlignment.Center, // 对齐方式为中心 sheets: sheets // 结果页面列表 }); } // 构建用户界面 build() { Column() { // 创建列布局 Text(`垃圾分类测验:${this.currentQuestionIndex + 1}/10`) // 显示当前题目序号 .fontSize('30lpx') // 设置字体大小 .margin(20); // 设置外边距 Stack() { // 创建堆栈布局 Text(`${this.garbageItems[this.currentQuestionIndex].name}`) // 显示当前垃圾项目名称 .textAlign(TextAlign.Center) // 居中对齐 .width('130lpx') // 设置宽度 .height('130lpx') // 设置高度 .border({ width: 1 }) // 设置边框宽度 .borderRadius(5) // 设置圆角半径 .fontColor(Color.White) // 设置字体颜色 .backgroundColor(Color.Orange) // 设置背景颜色 .fontSize('20lpx') // 设置字体大小 .padding(2) // 设置内边距 .scale(this.scaleOptions); // 设置缩放比例 if (this.showAnimation) { // 如果显示动画 Particle({ // 创建粒子效果 particles: [ // 初始化粒子数组 { emitter: { // 粒子发射器配置 particle: { // 粒子类型配置 type: ParticleType.POINT, // 粒子类型为点 config: { // 配置 radius: 5 // 点的半径 }, count: 50, // 粒子数量 lifetime: -1, // 生命周期 lifetimeRange: -1 // 生命周期范围 }, emitRate: 100, // 发射速率 position: ['25%', 0], // 发射位置 size: ['100lpx', '100lpx'], // 发射器大小 shape: ParticleEmitterShape.RECTANGLE // 发射器形状为矩形 }, color: { // 粒子颜色配置 range: [Color.Orange, Color.Orange], // 颜色范围 updater: { // 更新器配置 type: ParticleUpdater.CURVE, // 变化方式为曲线变化 config: [ // 配置项 { from: Color.Orange, // 起始颜色 to: Color.Orange, // 终止颜色 startMillis: 0, // 开始时间 endMillis: -1, // 结束时间 curve: Curve.FastOutLinearIn // 曲线类型 } ] } }, scale: { // 粒子大小配置 range: [0.8, 1.2], // 大小范围 updater: { // 更新器配置 type: ParticleUpdater.CURVE, // 变化方式为曲线变化 config: [ // 配置项 { from: 1.0, // 起始大小 to: 1.0, // 终止大小 startMillis: 0, // 开始时间 endMillis: -1, // 结束时间 curve: Curve.EaseIn } ] } },
// 粒子加速度配置 acceleration: { speed: { range: [8000, 10000], // 向下减速,模拟重力 updater: { type: ParticleUpdater.RANDOM, // 加速度线性变化 config: [400, 500] } }, angle: { range: [90, 90] // 方向固定向下 } },
} ] }).width("100%").height("100%"); } } .width('150lpx') .height('300lpx') .align(Alignment.Top) .translate({x:`${this.itemXPosition}lpx`,y:0});
Row() { ForEach(this.garbageCategories, (category: GarbageCategory) => { Column() { Text(category.name) .fontColor(Color.White) .fontSize('30lpx') .padding(5); Divider(); Text(category.description) .fontColor(Color.White) .fontSize('28lpx') .padding(5); } .backgroundColor(category.color) .height('280lpx') .width("24%") .border({ width: 1 }) .borderRadius(5) .margin({ left: 1, right: 1 }) .clickEffect({ scale: 0.8, level: ClickEffectLevel.MIDDLE }) .onClick(() => { if(this.showAnimation){ return } this.showAnimation = true let itemX:number = 0 if(category.type==0){ itemX = -270 }else if(category.type==1){ itemX = -90 }else if(category.type==2){ itemX = 90 }else if(category.type==3){ itemX = 270 } animateToImmediately({ duration:200, onFinish:()=>{
animateToImmediately({ duration:800, curve: curves.springCurve(0, 20, 90, 20), onFinish:()=>{
animateToImmediately({ duration:200, onFinish:()=>{ this.itemXPosition = 0 this.checkAnswer(category.type); this.showAnimation = false } },()=>{ this.itemXPosition = 0 this.scaleOptions = { x: 1.0, y: 1.0 }; }) } },()=>{ this.scaleOptions = { x: 1.3, y: 1.3 }; }) } },()=>{ this.itemXPosition = itemX })
}); }); }
Button('重新开始').clickEffect({ level: ClickEffectLevel.LIGHT }).margin({ top: 50 }).onClick(() => { this.resetQuiz() }); }.width('100%'); }}
复制代码


用户头像

zhongcx

关注

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

还未添加个人简介

评论

发布
暂无评论
鸿蒙开发案例:垃圾分类_鸿蒙_zhongcx_InfoQ写作社区