鴻蒙NEXT開發案例:指尖輪盤

zhongcx發表於2024-11-11

【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) // 更新玩家位置
      }
    })
  }
}

  

相關文章