Procházet zdrojové kódy

feat: 完成稳定性训练下的逻辑交互和UI

YuJing před 2 týdny
rodič
revize
fb5eda0670

+ 2 - 0
commons/basic/src/main/ets/models/YTDiaLogModel.ets

@@ -22,6 +22,8 @@ export enum DiaLogPageEnum{
   HistoryToday,
   // 气泡设置
   BubbleSetting,
+  // 游戏结束弹窗
+  GameOver,
 }
 
 export interface DiaLogParam<T = ESObject>{

+ 123 - 26
features/feature/src/main/ets/components/YtComp/YTDiaLogBuild.ets

@@ -2,7 +2,7 @@ import { BasicType, YTAvoid } from 'basic'
 import { DiaLogPageEnum, DiaLogParam } from 'basic/src/main/ets/models/YTDiaLogModel'
 import { HistoryApis } from '../../Apis/HistoryApis'
 import { HistoryEventDetail } from '../../model/Index'
-import { BubbleStorage } from '../../model/Storage'
+import { BubbleStorage, StabilityStorage } from '../../model/Storage'
 import { NumberKeyBoard, NumberKeyBoardStyle } from './NumberKeyboard'
 import { YtDatePicker } from './YtDatePicker'
 import { _YtHeader } from './_YtHeader'
@@ -18,6 +18,7 @@ export function getBuilder(param: DiaLogParam, onBack: (ans?: ESObject) => void)
   else if (param.pageEnum == DiaLogPageEnum.TextInput) InputComp({onBack: onBack, param: param.param})
   else if (param.pageEnum == DiaLogPageEnum.HistoryToday) HistoryToday({onBack: onBack, param: param.param})
   else if (param.pageEnum == DiaLogPageEnum.BubbleSetting) BubbleSetting({onBack: onBack, param: param.param})
+  else if (param.pageEnum == DiaLogPageEnum.GameOver) GameOver({onBack: onBack, param: param.param})
 }
 
 // 底部菜单
@@ -368,6 +369,19 @@ struct BubbleSetting{
   @Param @Require param: BasicType
 
   @Local bubbleConnect: BubbleStorage = AppStorageV2.connect(BubbleStorage, () => new BubbleStorage())!
+  @Local stabilityStorage: StabilityStorage = AppStorageV2.connect(StabilityStorage, () => new StabilityStorage())!
+  @Local forEach: Array<string> = []
+  @Local isBubble: boolean = true
+
+  aboutToAppear(): void {
+    this.isBubble = this.param.isShowClose ?? true
+    if(this.isBubble) {
+      this.forEach = ['气泡出现频率', '气泡消失时间']
+    } else {
+      this.forEach = ['水平最大偏移', '竖直最大偏移', '训练时间']
+    }
+  }
+
 
   build() {
     Column({space: 9}){
@@ -382,53 +396,136 @@ struct BubbleSetting{
       .padding({right: 14, top: 10, bottom: 10})
 
       Column({space: 25}){
-        ForEach(['气泡出现频率', '气泡消失时间'], (item: string, index) => {
-          Row({space: 15}){
+        ForEach(this.forEach, (item: string, index) => {
+          Row(){
             Text(`${item}:`)
               .fontSize(16)
               .fontWeight(500)
               .fontColor('#FF333333')
 
             Row({space: 12}){
-              TextInput({text: `${index == 0 ? this.bubbleConnect.bubbleFrequency : this.bubbleConnect.bubbleDisappearTime}`})
-                .width(45)
-                .height(30)
-                .padding(0)
-                .maxLength(3)
-                .borderRadius(5)
-                .type(InputType.Number)
-                .textAlign(TextAlign.Center)
-                .border({width: 1, color: '#FFFFA001'})
-                .onChange((text: string) => {
-                  if(text){
-                    let num = parseInt(text) || 1
-                    if(index == 0){
-                      this.bubbleConnect.bubbleFrequency = num
-                    }else{
-                      this.bubbleConnect.bubbleDisappearTime = num
+              if(this.isBubble) {
+                TextInput({text: `${index == 0 ? this.bubbleConnect.bubbleFrequency : this.bubbleConnect.bubbleDisappearTime}`})
+                  .width(45)
+                  .height(30)
+                  .padding(0)
+                  .maxLength(2)
+                  .borderRadius(5)
+                  .type(InputType.Number)
+                  .textAlign(TextAlign.Center)
+                  .border({width: 1, color: '#FFFFA001'})
+                  .onChange((text: string) => {
+                    if(text){
+                      let num = parseInt(text) || 1
+                      if(index == 0){
+                        this.bubbleConnect.bubbleFrequency = num
+                      }else{
+                        this.bubbleConnect.bubbleDisappearTime = num
+                      }
+                    }
+                  })
+
+                Text('s')
+                  .fontSize(16)
+                  .fontWeight(500)
+                  .fontColor('#FF333333')
+              } else {
+                TextInput({text: `${index == 0 ? this.stabilityStorage.horizontalMaxOffset : index == 1 ? this.stabilityStorage.verticalMaxOffset : this.stabilityStorage.trainingTime}`})
+                  .width(45)
+                  .height(30)
+                  .padding(0)
+                  .maxLength(2)
+                  .borderRadius(5)
+                  .type(InputType.Number)
+                  .textAlign(TextAlign.Center)
+                  .border({width: 1, color: '#FFFFA001'})
+                  .onChange((text: string) => {
+                    if(text){
+                      let num = parseInt(text) || 1
+                      if(index == 0){
+                        this.stabilityStorage.horizontalMaxOffset = Math.min(num, 40)
+                      }else if(index == 1){
+                        this.stabilityStorage.verticalMaxOffset = Math.min(num, 100)
+                      } else {
+                        this.stabilityStorage.trainingTime = num
+                      }
                     }
-                  }
-                })
+                  })
 
-              Text('s')
-                .fontSize(16)
-                .fontWeight(500)
-                .fontColor('#FF333333')
+                Text(index == 2 ? 's' : '°')
+                  .fontSize(16)
+                  .fontWeight(500)
+                  .fontColor('#FF333333')
+              }
             }
           }
+          .width("100%")
+          .padding({left: 51, right: 51})
+          .justifyContent(FlexAlign.SpaceBetween)
         })
+
+        if(this.isBubble) {
+          Text('气泡最多存在 10 个')
+            .fontSize(12)
+        }
       }
       .width("100%")
       .alignItems(HorizontalAlign.Center)
     }
     .width(294)
-    .height(163)
     .borderRadius(20)
+    .padding({bottom: 26})
     .backgroundColor(Color.White)
     .justifyContent(FlexAlign.Start)
   }
 }
 
+@ComponentV2
+struct GameOver{
+  @Event onBack: (ans?:ESObject) => void
+  @Param @Require param: BasicType
+
+  @Local isWin: boolean = true
+
+  aboutToAppear(): void {
+    this.isWin = this.param.isShowClose ?? true
+  }
+
+  build() {
+    Column({space: 9}){
+      Row(){
+        Image(this.isWin ? $r("app.media.icon_win") : $r("app.media.icon_fail"))
+          .width(175)
+          .height(105)
+      }
+      .width("100%")
+      .position({y: -53})
+      .justifyContent(FlexAlign.Center)
+
+      Text(this.isWin ? '稳定性训练成功' : '稳定性训练失败')
+        .fontSize(16)
+        .fontWeight(500)
+        .fontColor(Color.Black)
+        .padding({top: 67, bottom: 46})
+
+      Row(){
+        Text(this.isWin ? '太棒了' : '继续努力')
+          .fontSize(16)
+          .fontWeight(500)
+      }
+      .borderRadius(8)
+      .padding({left: 41, top: 16, right: 41, bottom: 16})
+      .linearGradient({colors: [['#FFCC00', 0], ['#FF9F02', 1]], angle: 90})
+      .onClick(() => { this.onBack() })
+    }
+    .width(294)
+    .borderRadius(20)
+    .padding({bottom: 30})
+    .backgroundColor(Color.White)
+    .justifyContent(FlexAlign.Start)
+    .alignItems(HorizontalAlign.Center)
+  }
+}
 
 
 

+ 36 - 0
features/feature/src/main/ets/components/stabilityComp.ets

@@ -0,0 +1,36 @@
+@ComponentV2
+export struct stabilityComp {
+  // 最大偏移量x
+  @Param maxOffsetX: number = 0
+  // 最大偏移量y
+  @Param maxOffsetY: number = 0
+
+  @Local w: number = 0
+  @Local h: number = 0
+
+  build() {
+    Stack({alignContent: Alignment.Center}) {
+       Row()
+         .width(80)
+         .aspectRatio(1)
+         .border({width: 1, color: '#FF0000'})
+         .borderRadius(80)
+         // 减小偏移幅度,添加缩放因子控制灵敏度
+         .offset({
+           x: this.maxOffsetX / 90 * (this.h/2),
+           y: this.maxOffsetY / 90 * (this.w/2)
+         })
+
+      Image($r('app.media.icon_stability'))
+        .width(70)
+        .aspectRatio(1)
+    }
+    .width("100%")
+    .height("100%")
+    .backgroundColor('#333333')
+    .onAreaChange((o, n) => {
+      this.w = n.width as number
+      this.h = n.height as number
+    })
+  }
+}

+ 13 - 1
features/feature/src/main/ets/model/Storage.ets

@@ -1,7 +1,19 @@
+
+// 反应力训练设置内容
 @ObservedV2
 export class BubbleStorage{
   // 出现的频率
   @Trace bubbleFrequency: number = 1
   // 消失的时间
-  @Trace bubbleDisappearTime: number = 1
+  @Trace bubbleDisappearTime: number = 2
+}
+
+@ObservedV2
+export class StabilityStorage{
+  // 水平最大偏移量
+  @Trace horizontalMaxOffset: number = 10
+  // 垂直最大偏移量
+  @Trace verticalMaxOffset: number = 10
+  // 训练时间
+  @Trace trainingTime: number = 10
 }

+ 114 - 0
features/feature/src/main/ets/pages/stabilityPage.ets

@@ -0,0 +1,114 @@
+import { RouterPage } from 'basic';
+import { stabilityComp } from '../components/stabilityComp';
+import { _YtHeader } from '../components/YtComp/_YtHeader';
+import { stabilityViewModel } from '../viewModel/PageVm/stabilityViewModel';
+
+@ComponentV2
+@RouterPage
+struct stabilityPage {
+  @Local vm: stabilityViewModel = new stabilityViewModel();
+
+  aboutToDisappear(): void {
+    this.vm.dispose();
+  }
+
+  build() {
+    NavDestination() {
+      Column() {
+        _YtHeader({
+          title: '稳定性训练',
+          rightComp: () => { this.rightComp() }
+        })
+
+        Column(){
+          Row(){
+            ForEach(['稳定时间', '最大偏移x', '最大偏移y'], (item: string, index) => {
+              Column({space: 6}){
+                Text(item)
+                  .fontSize(12)
+                  .fontWeight(400)
+                  .fontColor('#FF333333')
+
+                Text(){
+                  if(index == 0) {
+                    Span(`${this.vm.trainTime}s`)
+                  } else if (index == 1) {
+                    Span(`水平:${this.vm.maxOffsetX}°`)
+                  } else {
+                    Span(`垂直:${this.vm.maxOffsetY}°`)
+                  }
+                }
+                .fontSize(18)
+                .fontWeight(500)
+                .fontColor('#FFFF7B00')
+              }
+              .layoutWeight(1)
+              .alignItems(HorizontalAlign.Center)
+            })
+          }
+          .width("100%")
+
+          Column(){
+            stabilityComp({
+              maxOffsetX: this.vm.maxOffsetX,
+              maxOffsetY: this.vm.maxOffsetY,
+            })
+          }
+          .padding({top: 23})
+          .width("100%")
+          .layoutWeight(1)
+
+          Row(){
+            Text()
+
+            Row(){
+              Text(this.vm.trainStarted == 1 ? '暂停训练' : '开始训练')
+            }
+            .borderRadius(8)
+            .padding({top: 16, left: 44, right: 44, bottom: 16})
+            .linearGradient({colors: [['#FFFFCC00', 0], ['#FFFF9F02', 0.7]], angle: 90})
+            .onClick(() => { this.vm.toggleTrain() })
+
+            Image($r('app.media.icon_reset'))
+              .width(33)
+              .aspectRatio(1)
+              .onClick(() => { this.vm.resetTrain() })
+          }
+          .width("100%")
+          .justifyContent(FlexAlign.SpaceBetween)
+          .padding({top: 18, bottom: 46, left: 37, right: 37})
+        }
+        .width("100%")
+        .layoutWeight(1)
+        .padding({top: 20})
+        .padding({left: 16, right: 16, top: 25})
+        .borderRadius({topLeft: 23, topRight: 23})
+        .backgroundColor('rgba(255, 255, 255, 0.3)')
+      }
+      .width('100%')
+      .height('100%')
+      .justifyContent(FlexAlign.Start)
+      .alignItems(HorizontalAlign.Start)
+    }
+    .hideTitleBar(true)
+    .padding({ top: this.vm.safeTop, bottom: this.vm.safeBottom })
+    .linearGradient({ colors: [['#FF7FF9C3', 0], ['#FFEEECED', 0.3]]})
+  }
+
+  @Builder
+  rightComp(){
+    Image($r('app.media.icon_setting'))
+      .width(24)
+      .aspectRatio(1)
+      .onClick(() => { this.vm.openSetting() })
+  }
+}
+
+@Builder
+function stabilityPageBuilder() {
+  stabilityPage()
+}
+@Builder
+function stabilityBuilder() {
+  stabilityPage()
+}

+ 14 - 2
features/feature/src/main/ets/utils/RouterUtils.ets

@@ -15,10 +15,22 @@ class RouterUtils {
     yTRouter.router2DiaLog(p)
   }
 
-  router2BubbleSetting(){
+  // 弹窗设置
+  router2BubbleSetting(isBubble: boolean = true){
     let p: DiaLogParam = {
       pageEnum: DiaLogPageEnum.BubbleSetting,
-      align: YTDiaLogModel.Center
+      align: YTDiaLogModel.Center,
+      param: { isShowClose: isBubble }
+    }
+    yTRouter.router2DiaLog(p)
+  }
+
+  // 游戏结束
+  router2GameOver(isWin: boolean = true){
+    let p: DiaLogParam = {
+      pageEnum: DiaLogPageEnum.GameOver,
+      align: YTDiaLogModel.Center,
+      param: { isShowClose: isWin }
     }
     yTRouter.router2DiaLog(p)
   }

+ 125 - 0
features/feature/src/main/ets/viewModel/PageVm/stabilityViewModel.ets

@@ -0,0 +1,125 @@
+import { YTAvoid, yTRouter } from "basic"
+import { uRouter } from "../../utils/RouterUtils"
+import { sensor } from "@kit.SensorServiceKit"
+import { StabilityStorage } from "../../model/Storage"
+import { AppStorageV2 } from "@kit.ArkUI"
+
+@ObservedV2
+export class stabilityViewModel{
+  @Trace safeTop: number = 0
+  @Trace safeBottom: number = 0
+
+  // 训练时长
+  @Trace trainTime: number = 0
+  // 训练的运行状态 -1 重置 0 未开始 1 开始
+  @Trace trainStarted: number = 0
+  // 最大偏移量x
+  @Trace maxOffsetX: number = 0
+  // 最大偏移量y
+  @Trace maxOffsetY: number = 0
+
+  startX?: number = undefined
+  startY?: number = undefined
+
+  lastTime: number = 0
+  timer: number = 0
+
+  stabilityStorage: StabilityStorage = AppStorageV2.connect(StabilityStorage, () => new StabilityStorage())!
+
+  // 打开设置页面
+  openSetting() {
+    // 节流
+    let now = new Date().getTime()
+    if(now - this.lastTime < 800) return
+    this.lastTime = now
+
+    if(this.trainStarted == 1) {
+      this.toggleTrain()
+    }
+
+    uRouter.router2BubbleSetting(false)
+  }
+
+  // 重置训练
+  resetTrain() {
+    if(this.trainStarted == 1) {
+      this.toggleTrain()
+    }
+    this.maxOffsetX = 0
+    this.maxOffsetY = 0
+    this.trainTime = 0
+    this.startX = undefined
+    this.startY = undefined
+  }
+
+  gameOver(isWin: boolean){
+    uRouter.router2GameOver(isWin)
+
+    this.toggleTrain()
+
+    this.trainStarted = -1
+  }
+
+  // 开始、暂停训练
+  toggleTrain() {
+    if(this.trainStarted == -1) {
+      this.resetTrain()
+    }
+
+    this.trainStarted = this.trainStarted === 1 ? 0 : 1
+
+    if(this.trainStarted == 1) {
+      this.timer = setInterval(() => {
+        this.trainTime += 1
+
+        if(this.trainTime == this.stabilityStorage.trainingTime) {
+          this.gameOver(true)
+        }
+
+      }, 1000)
+      this.start()
+    } else if(this.trainStarted == 0) {
+      clearInterval(this.timer)
+      this.dispose()
+    }
+  }
+
+  start(){
+    sensor.on(sensor.SensorId.ORIENTATION, (data) => {
+      let roll = Number(data.gamma.toFixed(0)); // 侧倾角 x
+      let pitch = Number(data.beta.toFixed(0)); // 俯仰角 y
+
+      if(this.startX == undefined || this.startY == undefined) {
+        this.startX = roll;
+        this.startY = pitch;
+      }
+
+      if(this.maxOffsetX == roll - this.startX && this.maxOffsetY == pitch - this.startY) return
+
+      this.maxOffsetX = roll - this.startX!;
+      this.maxOffsetY = pitch - this.startY!;
+
+      console.log(`maxX=${this.maxOffsetX} startX=${this.startX} maxY=${this.maxOffsetY} startY=${this.startY}`)
+
+      // 偏移量
+      let x = Math.abs(this.maxOffsetX) - Math.abs(this.startX);
+      let y = Math.abs(this.maxOffsetY) - Math.abs(this.startY);
+
+      if((Math.abs(this.maxOffsetX) >= this.stabilityStorage.horizontalMaxOffset || Math.abs(this.maxOffsetY) >= this.stabilityStorage.verticalMaxOffset) && this.trainStarted == 1){
+        this.dispose()
+        this.gameOver(false)
+      }
+    }, { interval: 1000000 });
+  }
+
+  dispose(){
+    sensor.off(sensor.SensorId.ORIENTATION);
+  }
+
+
+  constructor() {
+    this.safeTop = AppStorage.get(YTAvoid.SAFE_TOP_KEY) as number
+    this.safeBottom = AppStorage.get(YTAvoid.SAFE_BOTTOM_KEY) as number
+  }
+
+}

binární
features/feature/src/main/resources/base/media/icon_fail.png


binární
features/feature/src/main/resources/base/media/icon_stability.png


binární
features/feature/src/main/resources/base/media/icon_win.png


+ 5 - 0
features/feature/src/main/resources/base/profile/router_map.json

@@ -29,6 +29,11 @@
       "name": "VisionTestPage",
       "pageSourceFile": "src/main/ets/pages/VisionTestPage.ets",
       "buildFunction": "VisionTestBuilder"
+    },
+    {
+      "name": "stabilityPage",
+      "pageSourceFile": "src/main/ets/pages/stabilityPage.ets",
+      "buildFunction": "stabilityBuilder"
     }
   ]
 }