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

【引言】

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

【实现目标】

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

  1. 开始游戏:通过点击"开始游戏"按钮启动游戏,触发杯子间的随机交换。

  2. 调整动画速度:允许用户通过界面上的控制器来调整游戏过程中杯子交换的速度。

  3. 调整混合次数:让用户可以设置每局游戏中杯子的混合次数。

  4. 显示杯子内容:当动画停止后,玩家可以通过点击任意一个杯子来查看其下面是否有小球。

  5. 自动重置:如果所有预定的交换次数完成,游戏会自动重置,等待下一轮开始。

【开发逻辑】

  1. 定义杯子类:创建 Cup 类,定义杯子的属性和构造函数。

  2. 实现游戏逻辑:

• 初始化游戏状态。

• 实现 startGame() 方法,用于开始游戏。

• 实现 moveCups() 方法,用于移动杯子。

• 实现 swapBalls() 方法,用于交换杯子内的球。

• 实现 resetCupPosition() 方法,用于重置杯子的位置。

  1. 动画效果:使用动画库(animateToImmediately)实现杯子的动画效果。

【完整代码】

javascript 复制代码
// 使用装饰器来追踪对象的变化
@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%');
  }
}
相关推荐
ly_Enhs几秒前
从无光照到 PBR:我的环境光照学习记录
学习·vulkan图形渲染c/c++
小李不想当小白1 分钟前
USART串口数据包(STM32标准库学习笔记)
经验分享·笔记·stm32·单片机·嵌入式硬件·学习
xier_ran10 分钟前
【infra之路】AI 编译器学习笔记(八):向量化——把小块变成 SIMD 指令
人工智能·笔记·学习
kmomo..11 分钟前
Linux 驱动开发学习笔记:I2C 子系统从入门到实践
linux·驱动开发·学习
zhangdaliangmvp42 分钟前
四元数的学习理解
学习
Georgewu8 小时前
【HarmonyOS 7】 HarmonyOS 应用 Skill创建和编排详解
harmonyos
Georgewu10 小时前
【HarmonyOS 7】 应用 Agent 接入云 A2A 与端 A2A 的实践详解
harmonyos
遇印记11 小时前
传输层,网络层
运维·服务器·网络·windows·学习
clorinda12 小时前
PyQt5学习:从界面搭建到与机器学习实战
学习
欣欣之王来了14 小时前
Vue3项目脚手架:Vite vs Vue CLI怎么选?
前端·学习·架构·项目·vue3教程