ETJava Beta | Java    注册   登录
  • 搜索:
  • 鸿蒙NEXT开发案例:指尖轮盘

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/zhongcx/p/18540339
    如有侵权 请联系我们删除  (页面底部联系我们)  

     

    【1】引言

    “指尖轮盘”是一个简单而有趣的互动游戏(类似抓阄),这个应用通过触摸屏幕的方式,让玩家参与一个激动人心的游戏,最终选出幸运的赢家。未来可以进一步扩展功能,如增加游戏模式、优化动画效果、增加音效等,提升用户体验。

    【2】环境准备

    电脑系统:windows 10

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

    工程版本:API 12

    真机:mate60 pro

    语言:ArkTS、ArkUI

    【功能概述】

    1. 显示标题和游戏说明,引导玩家参与游戏。

    2. 支持多位玩家参与,每位玩家以不同颜色的圆形表示。

    3. 根据触摸屏幕的手指数量,动态更新界面状态。

    4. 实现倒计时功能,倒计时结束后随机选择一位玩家作为赢家。

    【技术实现要点】

    1. 使用鸿蒙系统提供的组件和状态管理功能,构建界面和处理用户交互。

    2. 利用动画效果,为玩家圆形添加缩放动画,增强视觉效果。

    3. 通过定时器实现倒计时和随机选择玩家的功能。

    4. 处理触摸事件,根据手指数量更新玩家位置和界面状态。

    【开发流程】

    1. 创建玩家位置类(PlayerPosition),用于管理玩家属性和动画效果。

    2. 设计入口组件(WheelGamePage),包含玩家列表、倒计时、触摸事件处理等功能。

    3. 构建UI界面,显示标题、说明文本和玩家圆形,实现动态更新和交互效果。

    4. 实现倒计时和随机选择玩家的逻辑,提升游戏体验。

    【完整代码】

    @ObservedV2
      // 观察类,用于观察属性变化
    class PlayerPosition {
      @Trace isVisible: boolean = false // 玩家是否可见
      @Trace startX: number = 0 // 玩家起始X坐标
      @Trace startY: number = 0 // 玩家起始Y坐标
      @Trace scaleOptions: ScaleOptions = { x: 0.5, y: 0.5 } // 玩家缩放选项
      cellWidth: number = 100 // 玩家圆形的宽度
      color: string // 玩家颜色
    
      constructor(color: string) { // 构造函数,初始化颜色
        this.color = color
      }
    
      isRunningAnimation: boolean = false // 动画是否正在运行
    
      setShowAnimation() { // 设置显示动画
        if (!this.isRunningAnimation) { // 如果动画未运行
          this.isRunningAnimation = true // 标记为正在运行
          animateToImmediately({
            // 开始动画
            delay: 0,
            duration: 500,
            curve: Curve.Linear,
            iterations: 1,
            onFinish: () => { // 动画完成后的回调
              console.info(`onFinish 1`)
              animateToImmediately({
                // 开始第二个动画
                delay: 0,
                duration: 300,
                iterations: -1,
                curve: Curve.Linear,
                onFinish: () => { // 第二个动画完成后的回调
                  console.info(`onFinish 2`)
                }
              }, () => {
                this.scaleOptions = { x: 1.1, y: 1.1 } // 设置缩放
              })
            }
          }, () => {
            this.scaleOptions = { x: 1.0, y: 1.0 } // 动画结束时重置缩放
          })
        }
      }
    }
    
    @Entry
      // 入口组件
    @Component
    struct WheelGamePage {
      @State playerList: PlayerPosition[] = [// 玩家列表
        new PlayerPosition("#26c2ff"),
        new PlayerPosition("#978efe"),
        new PlayerPosition("#c389fe"),
        new PlayerPosition("#ff85bd"),
        new PlayerPosition("#ff7051"),
        new PlayerPosition("#fea800"),
        new PlayerPosition("#ffcf18"),
        new PlayerPosition("#a9c92a"),
      ]
      @State showTitle: boolean = true // 是否显示标题
      @State showInstructions: boolean = true // 是否显示说明
      @State showCountdown: boolean = false // 是否显示倒计时
      @State @Watch('countdownNumberChanged') countdownNumber: number = 3 // 倒计时数字
      @State selectIndex: number = -1 // 最终选中的玩家下标
      countdownTimerId: number = 0 // 倒计时定时器ID
      randomSelectionTimerId: number = 0 // 随机选择定时器ID
    
      countdownNumberChanged() { // 倒计时变化的处理函数
        if (this.countdownNumber <= 0) { // 如果倒计时结束
          this.startRandomSelection(); // 开始随机选择
        } else {
          this.selectIndex = -1; // 结束随机显示
        }
      }
    
      startRandomSelection() { // 开始随机选择玩家
        const len = this.playerList.length + Math.floor(Math.random() * this.playerList.length); // 随机长度
        const visiblePlayers = this.playerList.filter(player => player.isVisible); // 可见玩家列表
        console.info(`visiblePlayers:${JSON.stringify(visiblePlayers)}`);
    
        let count = 0; // 当前计数
        let iteration = 0; // 当前迭代次数
    
        clearInterval(this.randomSelectionTimerId); // 清除之前的定时器
        this.randomSelectionTimerId = setInterval(() => { // 设置新的定时器
          console.info(`count:${count}`);
          console.info(`iteration:${iteration}`);
          console.info(`len:${len}`);
    
          if (iteration >= len) { // 如果达到迭代次数
            clearInterval(this.randomSelectionTimerId); // 清除定时器
            return;
          }
    
          this.selectIndex = count++ % visiblePlayers.length; // 随机选择玩家
          iteration++; // 增加迭代次数
        }, 150); // 每150毫秒执行一次
      }
    
      updatePlayerPositions(touchPoints: TouchObject[]) { // 更新玩家位置
        this.playerList.forEach((player, index) => { // 遍历玩家列表
          if (index < touchPoints.length) { // 如果触摸点数量大于玩家索引
            player.isVisible = true // 设置玩家可见
            player.setShowAnimation() // 设置动画
            player.startX = touchPoints[index].x // 更新X坐标
            player.startY = touchPoints[index].y // 更新Y坐标
          } else {
            player.isVisible = false // 设置玩家不可见
          }
        })
      }
    
      updateTextState(touchCount: number) { // 更新文本状态
        this.countdownNumber = 3 // 重置倒计时
        if (touchCount === 0) { // 如果没有触摸
          this.showTitle = true // 显示标题
          this.showInstructions = true // 显示说明
          this.showCountdown = false // 隐藏倒计时
        } else if (touchCount === 1) { // 如果有一个触摸
          this.showTitle = false // 隐藏标题
          this.showInstructions = true // 显示说明
          this.showCountdown = false // 隐藏倒计时
        } else if (touchCount >= 2) { // 如果有两个或更多触摸
          this.showTitle = false // 隐藏标题
          this.showInstructions = false // 隐藏说明
          this.showCountdown = true // 显示倒计时
          clearInterval(this.countdownTimerId) // 清除之前的倒计时
          this.countdownTimerId = setInterval(() => { // 设置新的倒计时
            if (this.countdownNumber >= 0) { // 如果倒计时未结束
              this.countdownNumber-- // 倒计时减一
            } else {
              clearInterval(this.countdownTimerId) // 倒计时结束,清除定时器
            }
          }, 1000) // 每秒执行一次
        }
      }
    
      build() { // 构建UI
        Stack() { // 创建堆栈布局
          Text("指尖轮盘")// 显示标题文本
            .width('100%')// 宽度100%
            .height(80)// 高度80
            .textAlign(TextAlign.Center)// 文本居中
            .fontSize(20)// 字体大小20
            .fontColor("#0c0c0c")// 字体颜色
            .visibility(this.showTitle ? Visibility.Visible : Visibility.Hidden)// 根据状态设置可见性
            .draggable(false) // 不可拖动
          Stack() { // 创建另一个堆栈布局
            Text(`1. 邀请您的朋友一起加入这场激动人心的游戏吧!只需2到${this.playerList.length}位玩家即可开始。\n
    2. 准备好后,请每位参与者伸出一根手指轻轻按住手机屏幕。倒计时3秒后,游戏自动启动,或者您也可以手动点击“开始”按钮。\n
    3. 紧紧握住你的手指,直到动画结束。幸运之神将会眷顾其中一位玩家,成为本局的赢家!`)// 显示说明文本
              .textAlign(TextAlign.JUSTIFY)// 文本对齐
              .fontSize(20)// 字体大小20
              .fontColor("#0c0c0c")// 字体颜色
              .padding(20)// 内边距20
              .visibility(this.showInstructions ? Visibility.Visible : Visibility.None)// 根据状态设置可见性
              .draggable(false) // 不可拖动
    
            Text(this.countdownNumber > 0 ? `倒计时${this.countdownNumber}秒后开始` : ``)// 显示倒计时文本
              .textAlign(TextAlign.Center)// 文本居中
              .fontSize(20)// 字体大小20
              .fontColor("#0c0c0c")// 字体颜色
              .padding(20)// 内边距20
              .visibility(this.showCountdown ? Visibility.Visible : Visibility.None)// 根据状态设置可见性
              .draggable(false)
          }.width('100%').height('100%') // 设置堆栈宽高
          .draggable(false)
    
          ForEach(this.playerList, (player: PlayerPosition, index: number) => { // 遍历玩家列表
            Stack() { // 创建堆栈布局
              Circle()// 创建外圈圆形
                .width(player.cellWidth + 10)// 外圈宽度比玩家宽度大10
                .height(player.cellWidth + 10)// 外圈高度比玩家高度大10
                .fill(player.color)// 填充外圈颜色
                .fillOpacity(0.5)// 设置外圈透明度为0.5
                .visibility(player.isVisible ? Visibility.Visible : Visibility.None)// 根据玩家可见性设置外圈可见性
                .draggable(false)// 外圈不可拖动
                .scale(player.scaleOptions) // 设置外圈缩放选项
    
              Circle()// 创建内圈圆形
                .width(player.cellWidth)// 内圈宽度
                .height(player.cellWidth)// 内圈高度
                .fill(player.color)// 填充内圈颜色
                .fillOpacity(1)// 设置内圈透明度为1
                .visibility(player.isVisible ? Visibility.Visible : Visibility.None)// 根据玩家可见性设置内圈可见性
                .draggable(false)// 内圈不可拖动
                .scale(player.scaleOptions) // 设置内圈缩放选项
    
            }.draggable(false) // 堆栈布局不可拖动
            .scale(this.selectIndex == index ? { x: 1.5, y: 1.5 } : { x: 1.0, y: 1.0 }) // 根据选中状态设置缩放
            .margin({ left: player.startX - player.cellWidth / 2, top: player.startY - player.cellWidth / 2 }) // 设置玩家位置
          })
        }
        .align(Alignment.TopStart) // 设置堆栈对齐方式
        .height('100%') // 设置堆栈高度为100%
        .width('100%') // 设置堆栈宽度为100%
        .draggable(false) // 堆栈不可拖动
        .onTouch((event: TouchEvent) => { // 处理触摸事件
          if (event.type == TouchType.Down) { // 按下事件
            this.updatePlayerPositions(event.touches) // 更新玩家位置
            this.updateTextState(event.touches.length) // 更新文本状态
          } else if (event.type == TouchType.Move) { // 移动事件
            this.updatePlayerPositions(event.touches) // 更新玩家位置
          } else if (event.type == TouchType.Up) { // 抬起事件
            this.updateTextState(event.touches.length - 1) // 更新文本状态
            if (event.touches.length - 1 === 0) { // 如果没有触摸
              this.updatePlayerPositions([]) // 清空玩家位置
            }
          } else if (event.type == TouchType.Cancel) { // 取消事件
            this.updatePlayerPositions(event.touches) // 更新玩家位置
          }
        })
      }
    }