鸿蒙NEXT小游戏开发:猜小球
1. 引言
“猜小球”是一个经典的益智游戏,通常由一名表演者和多名参与者共同完成。表演者会将一个小球放在一个杯子下面,然后将三个杯子快速地交换位置,参与者则需要猜出最终哪个杯子下面有小球。本文将介绍如何使用HarmonyOS NEXT技术,如装饰器、状态管理和动画,来实现一个“猜小球”游戏。
2. 环境准备
电脑系统:windows 10
开发工具:DevEco Studio NEXT Beta1 Build Version: 5.0.3.806
工程版本:API 12
真机:Mate 60 Pro
语言:ArkTS、ArkUI
3. 实现目标
创建一个交互式的游戏,让玩家能够:
- 开始游戏:通过点击“开始游戏”按钮启动游戏,触发杯子间的随机交换。
- 调整动画速度:允许用户通过界面上的控制器来调整游戏过程中杯子交换的速度。
- 调整混合次数:让用户可以设置每局游戏中杯子的混合次数。
- 显示杯子内容:当动画停止后,玩家可以通过点击任意一个杯子来查看其下面是否有小球。
- 自动重置:如果所有预定的交换次数完成,游戏会自动重置,等待下一轮开始。
【开发逻辑】 - 定义杯子类:创建 Cup 类,定义杯子的属性和构造函数。
- 实现游戏逻辑:
• 初始化游戏状态。
• 实现 startGame() 方法,用于开始游戏。
• 实现 moveCups() 方法,用于移动杯子。
• 实现 swapBalls() 方法,用于交换杯子内的球。
• 实现 resetCupPosition() 方法,用于重置杯子的位置。 - 动画效果:使用动画库(animateToImmediately)实现杯子的动画效果。
4. 完整代码
// 使用装饰器来追踪对象的变化
@ObservedV2
class Cup { // 定义杯子类// 使用装饰器来追踪属性的变化@Trace positionX: number; // 杯子的X轴位置@Trace positionY: number; // 杯子的Y轴位置@Trace containsBall: boolean; // 杯子内是否有球@Trace isRevealed: boolean; // 杯子是否打开,即是否显示内部情况// 构造函数初始化杯子的状态constructor(hasBall: boolean) { // 初始化杯子,参数hasBall表示杯子是否包含小球this.positionX = 0; // 初始X轴位置设为0this.positionY = 0; // 初始Y轴位置设为0this.containsBall = hasBall; // 设置杯子是否含有小球this.isRevealed = true; // 初始状态为打开,即可以看到里面是否有球}
}// 游戏入口组件
@Entry
@Component
struct ThreeCupGame { // 定义游戏主结构体// 游戏状态变量@State gameCups: Cup[] = [// 初始化三个杯子数组,其中只有一个杯子含有小球new Cup(true), // 第一个杯子含有小球new Cup(false), // 第二个杯子不含有小球new Cup(false)// 第三个杯子也不含有小球];@State cupWidth: number = 200; // 设置杯子宽度@State cupSpacing: number = 10; // 设置杯子之间的间距@State animationDurationMs: number = 140; // 设置动画持续时间(毫秒)@State isGameAnimating: boolean = false; // 表示游戏是否正在进行动画@State mixingCount: number = 5; // 设置每局游戏混合次数@State currentMixingCount: number = 0; // 当前正在进行的混合次数计数// 开始游戏的方法startGame() { // 开始游戏的函数this.currentMixingCount--; // 每次调用此函数时减少一次混合次数const cupPairs = [[0, 1], [0, 2], [1, 2]]; // 定义可能的杯子对组合const selectedPair = cupPairs[Math.floor(Math.random() * cupPairs.length)]; // 随机选择一对杯子this.moveCups(selectedPair[0], selectedPair[1]); // 开始移动选定的两个杯子}// 移动指定的两个杯子moveCups(cupIndex1: number, cupIndex2: number) {const direction: number = Math.random() < 0.5 ? -1 : 1; // 随机方向const distanceFactor: number = Math.abs(cupIndex1 - cupIndex2); // 距离因子const adjustedDistanceFactor: number = distanceFactor === 1 ? 2 : 1; // 根据距离调整因子animateToImmediately({delay: 0,duration: this.animationDurationMs}, () => {this.gameCups[cupIndex1].positionY = -direction * (this.cupWidth + this.cupSpacing * 2) / adjustedDistanceFactor})animateToImmediately({delay: this.animationDurationMs,duration: this.animationDurationMs}, () => {this.gameCups[cupIndex1].positionX = (this.cupWidth + this.cupSpacing * 2) * distanceFactorthis.gameCups[cupIndex1].positionY = -direction * (this.cupWidth + this.cupSpacing * 2) / adjustedDistanceFactor})animateToImmediately({delay: this.animationDurationMs * 2,duration: this.animationDurationMs}, () => {this.gameCups[cupIndex1].positionX = (this.cupWidth + this.cupSpacing * 2) * distanceFactorthis.gameCups[cupIndex1].positionY = 0})animateToImmediately({delay: 0,duration: this.animationDurationMs}, () => {this.gameCups[cupIndex2].positionY = direction * (this.cupWidth + this.cupSpacing * 2) / adjustedDistanceFactor})animateToImmediately({delay: this.animationDurationMs,duration: this.animationDurationMs}, () => {this.gameCups[cupIndex2].positionX = -(this.cupWidth + this.cupSpacing * 2) * distanceFactorthis.gameCups[cupIndex2].positionY = direction * (this.cupWidth + this.cupSpacing * 2) / adjustedDistanceFactor})animateToImmediately({delay: this.animationDurationMs * 2,duration: this.animationDurationMs,onFinish: () => {this.swapBalls(cupIndex1, cupIndex2)}}, () => {this.gameCups[cupIndex2].positionX = -(this.cupWidth + this.cupSpacing * 2) * distanceFactorthis.gameCups[cupIndex2].positionY = 0})}// 重置杯子的位置resetCupPosition(cupIndex: number) { // 重置指定杯子位置的函数this.gameCups[cupIndex].positionX = 0; // 重置X轴位置this.gameCups[cupIndex].positionY = 0; // 重置Y轴位置}// 交换两个杯子内的球swapBalls(cupIndex1: number, cupIndex2: number) { // 交换两个杯子中球的函数this.resetCupPosition(cupIndex1); // 重置第一个杯子的位置this.resetCupPosition(cupIndex2); // 重置第二个杯子的位置let temporaryBallStatus = this.gameCups[cupIndex1].containsBall; // 临时保存第一个杯子的球状态this.gameCups[cupIndex1].containsBall = this.gameCups[cupIndex2].containsBall; // 将第二个杯子的球状态赋给第一个杯子this.gameCups[cupIndex2].containsBall = temporaryBallStatus; // 将临时保存的球状态赋给第二个杯子if (this.currentMixingCount <= 0) { // 如果当前混合次数已经用完this.isGameAnimating = false; // 结束动画} else {setTimeout(() => { // 否则,延时调用startGame继续下一轮混合this.startGame();}, 10);}}// 构建游戏界面build() { // 构建游戏界面的函数Column({ space: 20 }) { // 创建一个垂直布局容器// 游戏标题Text('猜小球游戏')// 显示游戏标题.fontSize(24)// 设置字体大小.margin({ top: 20 }); // 设置顶部边距// 动画速度控制器Counter() { // 创建一个计数器组件Text(`当前速度${this.animationDurationMs}毫秒`)// 显示当前动画速度.fontColor(Color.Black)// 设置字体颜色.fontSize('26lpx'); // 设置字体大小}.width('400lpx') // 设置计数器宽度.onInc(() => { // 当计数器增加时this.animationDurationMs += 10; // 增加动画速度}).onDec(() => { // 当计数器减少时this.animationDurationMs -= 10; // 减少动画速度this.animationDurationMs = this.animationDurationMs < 10 ? 10 : this.animationDurationMs; // 确保动画速度不低于10毫秒});// 混合次数控制器Counter() { // 创建另一个计数器组件Text(`每局混合${this.mixingCount}次`)// 显示当前混合次数.fontColor(Color.Black).fontSize('26lpx');}.width('400lpx').onInc(() => { // 当计数器增加时this.mixingCount += 1; // 增加混合次数}).onDec(() => { // 当计数器减少时this.mixingCount -= 1; // 减少混合次数this.mixingCount = this.mixingCount < 1 ? 1 : this.mixingCount; // 确保混合次数不低于1});// 杯子布局Row() { // 创建一个水平布局容器ForEach(this.gameCups, (cup: Cup) => { // 循环遍历每个杯子Text(cup.isRevealed ? (cup.containsBall ? '小球' : '空') : '')// 根据杯子的状态显示文字.width(`${this.cupWidth}lpx`)// 设置宽度.height(`${this.cupWidth}lpx`)// 设置高度.margin(`${this.cupSpacing}lpx`)// 设置外边距.backgroundColor(Color.Orange)// 设置背景色.fontSize(`${this.cupWidth / 4}lpx`)// 设置字体大小.textAlign(TextAlign.Center)// 设置文本对齐方式.fontColor(Color.White)// 设置字体颜色.borderRadius(5)// 设置圆角半径.translate({ x: `${cup.positionX}lpx`, y: `${cup.positionY}lpx` })// 设置位置偏移.onClick(() => { // 当点击杯子时if (!this.isGameAnimating) { // 如果游戏没有在动画中cup.isRevealed = true; // 打开杯子查看内部}});});}.justifyContent(FlexAlign.Center).width('100%').height('720lpx').backgroundColor(Color.Gray); // 设置布局属性// 开始游戏按钮Button('开始游戏').onClick(() => { // 创建一个按钮,点击时触发事件if (!this.isGameAnimating) { // 如果游戏没有在动画中this.currentMixingCount = this.mixingCount; // 重置当前混合次数this.isGameAnimating = true; // 设置游戏为动画中this.gameCups.forEach(cup => cup.isRevealed = false); // 关闭所有杯子this.startGame(); // 开始游戏}});}.width('100%').height('100%'); // 设置布局容器的尺寸}
}
相关文章:
鸿蒙NEXT小游戏开发:猜小球
1. 引言 “猜小球”是一个经典的益智游戏,通常由一名表演者和多名参与者共同完成。表演者会将一个小球放在一个杯子下面,然后将三个杯子快速地交换位置,参与者则需要猜出最终哪个杯子下面有小球。本文将介绍如何使用HarmonyOS NEXT技术&…...
[NCTF2019]Fake XML cookbook [XXE注入]
题目源代码 function doLogin(){var username $("#username").val();var password $("#password").val();if(username "" || password ""){alert("Please enter the username and password!");return;}var data "…...
Android 防抖和节流
文章目录 Android 防抖和节流概述工具类使用源码下载 Android 防抖和节流 概述 防抖(Debounce): 防抖是指在事件被触发后,等待一段时间,如果在这段时间内没有再触发事件,才执行处理函数。如果在这段时间内…...
安徽京准:NTP时间同步服务器操作使用说明
安徽京准:NTP时间同步服务器操作使用说明 3.1 连接天线 天线连接到“ANT”口。 3.2 连接电源 将220V电源线连到AC220V座上或将电源适配器(7.5V~12V)接到DC口上。也可以同时接上,提高供电可靠性。 3.3 LAN网口 网线连接到NTP…...
【学习记录】pytorch载入模型的部分参数
需要从PointNet网络框架中提取encoder部分的参数,然后赋予自己的模型。因此,需要从一个已有的.pth文件读取部分参数,加载到自定义模型上面。做了一些尝试,记录如下。 关于模型保存与载入 torch.save(): 使用Python的pickle实用程…...
Ubuntu Wayland启动腾讯会议并实现原生屏幕共享
Intro 众所周知,长期以来,由于腾讯会议项目组的尸位素餐、极度不作为,在Wayland成为Ubuntu 24.04 LTS的默认窗口环境下,仍然选择摆烂,甚至还“贴心”地在启动脚本下增加检测Wayland退出的代码;并且即使使用…...
写Prompt的技巧和基本原则
一.基本原则 1.一定要描述清晰你需要大模型做的事情,不要模棱两可 2.告诉大模型需要它做什么,不需要做什么 改写前: 请帮我推荐一些电影 改写后: 请帮我推荐2025年新出的10部评分比较高的喜剧电影,不要问我个人喜好等其他问题ÿ…...
前端Material-UI面试题及参考答案
目录 Material-UI 的设计理念与 Material Design 规范的关系是什么? 如何通过 npm/yarn/pnpm 安装 Material-UI 的核心依赖? Material-UI 的默认主题系统如何实现全局样式管理? 如何在项目中配置自定义字体和颜色方案? 什么是 emotion 和 styled-components,它们在 Ma…...
29、web前端开发之CSS3(六)
13. 多列布局(Multi-column Layout) 多列布局(Multi-column Layout)是一种通过CSS实现的布局方式,允许将内容组织成多列,类似于报纸或杂志的排版方式。这种布局方法能够有效地利用页面空间,提升…...
Go 语言语法精讲:从 Java 开发者的视角全面掌握
《Go 语言语法精讲:从 Java 开发者的视角全面掌握》 一、引言1.1 为什么选择 Go?1.2 适合 Java 开发者的原因1.3 本文目标 二、Go 语言环境搭建2.1 安装 Go2.2 推荐 IDE2.3 第一个 Go 程序 三、Go 语言基础语法3.1 变量与常量3.1.1 声明变量3.1.2 常量定…...
MySQL 复制与主从架构(Master-Slave)
MySQL 复制与主从架构(Master-Slave) MySQL 复制与主从架构是数据库高可用和负载均衡的重要手段。通过复制数据到多个从服务器,既可以实现数据冗余备份,又能分担查询压力,提升系统整体性能与容错能力。本文将详细介绍…...
水下成像机理分析
一般情况下, 水下环境泛指浸入到人工水体 (如水库、人工湖等)或自然水体(如海洋、河流、湖 泊、含水层等)中的区域。在水下环境中所拍摄 的图像由于普遍受到光照、波长、水中悬浮颗粒物 等因素的影响,导致生成的水下图像出现模糊、退 化、偏色等现象,图像…...
腾讯云智测试开发面经
1、投递时间线 2.20投递简历,3.11第一轮面试,3.30第二轮面试,4.4第三轮面试,4.10第四轮面试,4.11offer意向书 2、第一轮面试 第一轮面试技术面,面试官是导师,面试时长40多分钟 1)自我介绍 2)数组和列表的区别 3)了解哪些数据库 4)进程和线程的区别 5)了解哪…...
JVM类加载器详解
文章目录 1.类与类加载器2.类加载器加载规则3.JVM 中内置的三个重要类加载器为什么 获取到 ClassLoader 为null就是 BootstrapClassLoader 加载的呢? 4.自定义类加载器什么时候需要自定义类加载器代码示例 5.双亲委派模式类与类加载器双亲委派模型双亲委派模型的执行…...
@ComponentScan注解详解:Spring组件扫描的核心机制
ComponentScan注解详解:Spring组件扫描的核心机制 一、ComponentScan注解概述 ComponentScan是Spring框架中的一个核心注解,用于自动扫描和注册指定包及其子包下的Spring组件。它是Spring实现依赖注入和自动装配的基础机制之一。 Retention(Retention…...
rust Send Sync 以及对象安全和对象不安全
开头:菜鸟小明的疑惑 小明: “李哥,我最近学 Rust,感觉它超级严谨,啥 Send、Sync、对象安全、静态分发、动态分发的,我都搞晕了!为啥 Rust 要设计得这么复杂啊?” 小李࿰…...
从一到无穷大 #44:AWS Glue: Data integration + Catalog
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。 文章目录 引言Glue的历史,设计原则与挑战Serverless ETL 功能设计Glue StudioGlue …...
【Redis】如何处理缓存穿透、击穿、雪崩
Redis 缓存穿透、击穿和雪崩是高并发场景下的典型问题,以下是详细解决方案和最佳实践: 一、缓存穿透(Cache Penetration) 问题:恶意请求不存在的数据(如不存在的ID),绕过缓存直接访…...
区块链技术如何重塑金融衍生品市场?
区块链技术如何重塑金融衍生品市场? 金融衍生品市场一直是全球金融体系的重要组成部分,其复杂性和风险性让许多投资者望而却步。然而,随着区块链技术的兴起,这一领域正在经历一场深刻的变革。区块链以其去中心化、透明和不可篡改…...
实战打靶集锦-35-GitRoot
文章目录 1. 主机发现2. 端口扫描3. 服务枚举4. 服务探查5. 系统提权6. 写在最后 靶机地址:https://download.vulnhub.com/gitroot/GitRoot.ova 1. 主机发现 目前只知道目标靶机在192.168.56.xx网段,通过如下的命令,看看这个网段上在线的主机…...
Vue3 + Element Plus + AntV X6 实现拖拽树组件
Vue3 Element Plus AntV X6 实现拖拽树组件 介绍 在本篇文章中,我们将介绍如何使用 Vue 3 和 Element Plus 结合 antv/x6 实现树形结构的拖拽功能。用户可以将树节点拖拽到图形区域,自动创建相应的节点。我们将会通过简单的示例来一步步讲解实现过程…...
从零开始跑通3DGS教程:介绍
写在前面 本文内容 本文所属《从零开始跑通3DGS教程》系列文章,将实现从原始图像(有序、无序)数据开始,经过处理(视频抽帧成有序),SFM,3DGS训练、编辑、渲染等步骤,完整地呈现从原始图像到新视角合成的全部流程&#x…...
聊聊Spring AI的Chat Model
序 本文主要研究一下Spring AI的Chat Model Model spring-ai-core/src/main/java/org/springframework/ai/model/Model.java public interface Model<TReq extends ModelRequest<?>, TRes extends ModelResponse<?>> {/*** Executes a method call to …...
将mysql配置成服务的方法
第一步:配置环境变量 1)新建MYSQL_HOME变量,并配置:C:\Program Files\MySQL\MySQL Server 5.6 MYSQL_HOME:C:\Program Files\MySQL\MySQL Server 5.6 2)编辑path系统变量,将%MYSQL_HOME%\bin添加到path变量后。配置path环境变量…...
GaussDB(for PostgreSQL) 存储引擎:ASTORE 与 USTORE 详细对比
GaussDB(for PostgreSQL) 存储引擎:ASTORE 与 USTORE 详细对比 1. 背景说明 GaussDB(for PostgreSQL) 是华为基于 PostgreSQL 开发的企业级分布式数据库,其存储引擎分为 ASTORE 和 USTORE 两种类型,分别针对不同场景优化。 2. 核心对比 (1)…...
英语口语 -- 常用 1368 词汇
英语口语 -- 常用 1368 词汇 介绍常用单词List1 (96 个)时间类气候类自然类植物类动物类昆虫类其他生物地点类 List2 (95 个)机构类声音类食品类餐饮类蔬菜类水果类食材类饮料类营养类疾病类房屋类家具类服装类首饰类化妆品类 Lis…...
SpringBoot+Vue 中 WebSocket 的使用
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它使得客户端和服务器之间可以进行实时数据传输,打破了传统 HTTP 协议请求 - 响应模式的限制。 下面我会展示在 SpringBoot Vue 中,使用WebSocket进行前后端通信。 后端 1、引入 j…...
关于依赖注入框架VContainer DIIOC 的学习记录
文章目录 前言一、VContainer核心概念1.DI(Dependency Injection(依赖注入))2.scope(域,作用域) 二、练习例子1.Hello,World!步骤一,编写一个底类。HelloWorldService步骤二,编写使用低类的类。GamePresenter步骤三&am…...
LRU缓存是什么
LRU缓存是什么 LRU(Least Recently Used)即最近最少使用,是一种缓存淘汰策略。在缓存空间有限的情况下,当新的数据需要存入缓存,而缓存已满时,LRU 策略会优先淘汰最近最少使用的数据,以此保证缓存中存储的是最近最常使用的数据。 LRU缓存的工作原理 LRU 缓存的核心思…...
Qt常用控件第一部分
1.控件概述 Widget 是 Qt 中的核⼼概念. 英⽂原义是 "⼩部件", 我们此处也把它翻译为 "控件" . 控件是构成⼀个图形化界⾯的基本要素. 像上述⽰例中的, 按钮, 列表视图, 树形视图, 单⾏输⼊框, 多⾏输⼊框, 滚动条, 下拉框等, 都可以称为 "控件"…...
