鸿蒙开发案例:通过三杯猜球学习显示动画

 

【引言】

“三杯猜球”是一个经典的益智游戏,通常由一名表演者和多名参与者共同完成。表演者会将一个小球放在一个杯子下面,然后将三个杯子快速地交换位置,参与者则需要猜出最终哪个杯子下面有小球。本文将介绍如何使用HarmonyOS NEXT技术,如装饰器、状态管理和动画,来实现一个“三杯猜球”游戏。

【实现目标】

创建一个交互式的游戏,让玩家能够:

1. 开始游戏:通过点击“开始游戏”按钮启动游戏,触发杯子间的随机交换。
2. 调整动画速度:允许用户通过界面上的控制器来调整游戏过程中杯子交换的速度。
3. 调整混合次数:让用户可以设置每局游戏中杯子的混合次数。
4. 显示杯子内容:当动画停止后,玩家可以通过点击任意一个杯子来查看其下面是否有小球。
5. 自动重置:如果所有预定的交换次数完成,游戏会自动重置,等待下一轮开始。

【开发逻辑】

1. 定义杯子类:创建 Cup 类,定义杯子的属性和构造函数。
2. 实现游戏逻辑:
• 初始化游戏状态。
• 实现 startGame() 方法,用于开始游戏。
• 实现 moveCups() 方法,用于移动杯子。
• 实现 swapBalls() 方法,用于交换杯子内的球。
• 实现 resetCupPosition() 方法,用于重置杯子的位置。
3. 动画效果:使用动画库(animateToImmediately)实现杯子的动画效果。

【完整代码】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// 使用装饰器来追踪对象的变化
@ObservedV2
class Cup {
  // 使用装饰器来追踪属性的变化
  @Trace positionX: number; // 杯子的X轴位置
  @Trace positionY: number; // 杯子的Y轴位置
  @Trace containsBall: boolean; // 杯子内是否有球
  @Trace isRevealed: boolean; // 杯子是否打开
 
  // 构造函数初始化杯子的状态
  constructor(hasBall: boolean) {
    this.positionX = 0;
    this.positionY = 0;
    this.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) * distanceFactor
      this.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) * distanceFactor
      this.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) * distanceFactor
      this.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) * distanceFactor
      this.gameCups[cupIndex2].positionY = 0
    })
  }
 
  // 重置杯子的位置
  resetCupPosition(cupIndex: number) {
    this.gameCups[cupIndex].positionX = 0;
    this.gameCups[cupIndex].positionY = 0;
  }
 
  // 交换两个杯子内的球
  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(() => {
        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;
      });
 
      // 混合次数控制器
      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
      });
 
      // 杯子布局
      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%');
  }
}

  

posted @   zhongcx  阅读(59)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示