【鸿蒙实战】30分钟手把手教你开发石头剪刀布小游戏

保姆级教程!从零开始,带你一步步用 HarmonyOS NEXT 和 ArkTS 实现一个完整的石头剪刀布游戏。

前言

最近鸿蒙开发很火,很多小伙伴问我怎么入门。今天就用一个经典的石头剪刀布游戏,带大家快速上手 HarmonyOS NEXT 开发!

一、准备工作

1.1 开发环境

确保你已经安装好以下工具:

工具 版本要求
DevEco Studio 5.0 或更高
HarmonyOS SDK API 23+
Node.js 14.19.1+

1.2 创建项目

  1. 打开 DevEco Studio
  2. 点击 File → New → Create Project
  3. 选择 Empty Ability 模板
  4. 填写项目名称(比如:MyApplication)
  5. 点击 Finish,等待项目初始化完成

创建完成后,项目结构如下:

MyApplication/
├── AppScope/              # 应用全局资源
├── entry/                 # 主模块
│   ├── src/main/
│   │   ├── ets/           # ArkTS 代码
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets
│   │   │   └── pages/
│   │   │       └── Index.ets      # 主页面 ⭐
│   │   └── resources/     # 资源文件
│   └── build-profile.json5
└── build-profile.json5

我们主要编辑的文件就是 Index.ets

二、功能设计

2.1 游戏规则

石头剪刀布的规则大家都很熟悉:

出拳 克制
✊ 石头 ✌️ 剪刀
✌️ 剪刀 ✋ 布
✋ 布 ✊ 石头

2.2 功能清单

我们要实现的功能:

  • ✅ 玩家点击按钮选择出拳
  • ✅ 电脑随机出拳
  • ✅ 判定胜负并显示结果
  • ✅ 记录双方得分
  • ✅ 显示总局数和平局次数
  • ✅ 重置游戏功能

三、代码实现

3.1 定义数据结构

首先,我们需要定义一个接口来描述出拳选项:

interface Choice {
  emoji: string;   // 表情符号
  label: string;   // 中文名称
  value: string;   // 英文值
}

3.2 创建组件并定义状态

@Entry
@Component
struct Index {
  // ========== 状态变量 ==========
  @State playerChoice: string = '❔';
  @State computerChoice: string = '❔';
  @State result: string = '选择你的出拳';
  @State resultColor: string = '#8E8E93';
  
  @State playerScore: number = 0;
  @State computerScore: number = 0;
  @State draws: number = 0;
  @State roundCount: number = 0;
  
  // ========== 常量数据 ==========
  readonly choices: Choice[] = [
    { emoji: '✊', label: '石头', value: 'rock' },
    { emoji: '✋', label: '布', value: 'paper' },
    { emoji: '✌️', label: '剪刀', value: 'scissors' }
  ];
  
  // ========== 方法区域 ==========
  // (后续实现)
  
  // ========== UI 构建 ==========
  build() {
    // (后续实现)
  }
}

要点解析:

装饰器 作用
@Entry 标记为页面入口
@Component 声明为 UI 组件
@State 响应式状态变量

3.3 实现游戏逻辑

步骤 1:电脑随机出拳
getComputerChoice(): string {
  const randomIndex = Math.floor(Math.random() * 3);
  return this.choices[randomIndex].value;
}
步骤 2:判定胜负
play(playerValue: string): void {
  // 1. 获取电脑选择
  const computerValue: string = this.getComputerChoice();
  
  // 2. 更新显示
  this.playerChoice = this.choices.find(c => c.value === playerValue)?.emoji ?? '❔';
  this.computerChoice = this.choices.find(c => c.value === computerValue)?.emoji ?? '❔';
  this.roundCount++;
  
  // 3. 判定结果
  if (playerValue === computerValue) {
    // 平局
    this.result = '🤝 平局!';
    this.resultColor = '#8E8E93';  // 灰色
    this.draws++;
  } else if (
    (playerValue === 'rock' && computerValue === 'scissors') ||
    (playerValue === 'scissors' && computerValue === 'paper') ||
    (playerValue === 'paper' && computerValue === 'rock')
  ) {
    // 玩家获胜
    this.result = '🎉 你赢了!';
    this.resultColor = '#34C759';  // 绿色
    this.playerScore++;
  } else {
    // 电脑获胜
    this.result = '😅 电脑赢了!';
    this.resultColor = '#FF3B30';  // 红色
    this.computerScore++;
  }
}
步骤 3:重置游戏
resetGame(): void {
  this.playerChoice = '❔';
  this.computerChoice = '❔';
  this.result = '选择你的出拳';
  this.resultColor = '#8E8E93';
  this.playerScore = 0;
  this.computerScore = 0;
  this.draws = 0;
  this.roundCount = 0;
}

3.4 构建界面

布局结构图
Column (主容器)
├── Text (标题:石头剪刀布)
├── Text (副标题:和电脑对战!)
├── Row (对战区域)
│   ├── Column (玩家)
│   │   ├── Text (🧑 你)
│   │   ├── Text (玩家选择 emoji)
│   │   └── Text (玩家得分)
│   ├── Column (VS)
│   └── Column (电脑)
│       ├── Text (🤖 电脑)
│       ├── Text (电脑选择 emoji)
│       └── Text (电脑得分)
├── Text (结果文字)
├── Row (出拳按钮组)
│   ├── Column (石头按钮)
│   ├── Column (布按钮)
│   └── Column (剪刀按钮)
├── Row (统计信息)
└── Button (重置按钮)
完整代码
build() {
  Column() {
    // ===== 标题区域 =====
    Text('✂️ 石头剪刀布')
      .fontSize(26)
      .fontWeight(FontWeight.Bold)
      .fontColor('#1C1C1E')
      .margin({ top: 30, bottom: 8 })
    
    Text('和电脑对战!')
      .fontSize(14)
      .fontColor('#8E8E93')
      .margin({ bottom: 24 })
    
    // ===== 对战区域 =====
    Row() {
      // 玩家
      Column() {
        Text('🧑 你')
          .fontSize(14)
          .fontColor('#8E8E93')
          .margin({ bottom: 8 })
        Text(this.playerChoice)
          .fontSize(64)
          .animation({ duration: 300 })
        Text(`${this.playerScore}`)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#34C759')
          .margin({ top: 6 })
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Center)
      
      // VS
      Column() {
        Text('VS')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF3B30')
      }
      .layoutWeight(0.5)
      .alignItems(HorizontalAlign.Center)
      
      // 电脑
      Column() {
        Text('🤖 电脑')
          .fontSize(14)
          .fontColor('#8E8E93')
          .margin({ bottom: 8 })
        Text(this.computerChoice)
          .fontSize(64)
          .animation({ duration: 300 })
        Text(`${this.computerScore}`)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF3B30')
          .margin({ top: 6 })
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%')
    .padding(20)
    .backgroundColor('#F2F2F7')
    .borderRadius(16)
    .margin({ bottom: 16 })
    
    // ===== 结果显示 =====
    Text(this.result)
      .fontSize(22)
      .fontWeight(FontWeight.Bold)
      .fontColor(this.resultColor)
      .margin({ top: 4, bottom: 24 })
    
    // ===== 出拳按钮 =====
    Row() {
      ForEach(this.choices, (choice: Choice) => {
        Column() {
          Text(choice.emoji)
            .fontSize(44)
          Text(choice.label)
            .fontSize(14)
            .fontColor('#1C1C1E')
            .margin({ top: 4 })
        }
        .width(96)
        .height(110)
        .backgroundColor('#F2F2F7')
        .borderRadius(16)
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .onClick(() => {
          this.play(choice.value);
        })
      })
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    .margin({ bottom: 24 })
    
    // ===== 统计信息 =====
    if (this.roundCount > 0) {
      Row() {
        Text(`${this.roundCount}`)
          .fontSize(14)
          .fontColor('#8E8E93')
        Blank()
        Text(`平局 ${this.draws}`)
          .fontSize(14)
          .fontColor('#8E8E93')
      }
      .width('100%')
      .padding({ left: 20, right: 20 })
      .margin({ bottom: 16 })
    }
    
    // ===== 重置按钮 =====
    Button('🔄 重新开始')
      .type(ButtonType.Capsule)
      .width(180)
      .height(44)
      .backgroundColor('#8E8E93')
      .fontSize(16)
      .fontWeight(FontWeight.Medium)
      .onClick(() => {
        this.resetGame();
      })
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#FFFFFF')
  .padding(20)
  .alignItems(HorizontalAlign.Center)
}

四、配置文件说明

4.1 页面路由配置

文件位置:entry/src/main/resources/base/profile/main_pages.json

{
  "src": [
    "pages/Index"
  ]
}

注意: 所有页面都必须在这里注册,否则无法跳转。

4.2 模块配置

文件位置:entry/src/main/module.json5

{
  "module": {
    "name": "entry",
    "type": "entry",
    "deviceTypes": ["phone"],
    "abilities": [{
      "name": "EntryAbility",
      "label": "$string:EntryAbility_label",
      "icon": "$media:layered_image"
    }]
  }
}

4.3 构建配置

文件位置:build-profile.json5

{
  "app": {
    "products": [{
      "name": "default",
      "targetSdkVersion": "6.1.1(24)",
      "compatibleSdkVersion": "6.1.0(23)"
    }]
  }
}

五、运行测试

在这里插入图片描述

六、常见问题

Q1: Previewer 显示空白?

A: 检查 main_pages.json 是否正确配置了页面路径。

Q2: 编译报错 “SDK not found”?

A: 打开 File → Settings → SDK,下载 HarmonyOS SDK(API 23+)。

Q3: 点击按钮没有反应?

A: 检查事件绑定,确保使用箭头函数:

// ❌ 错误
.onClick(this.play(choice.value))

// ✅ 正确
.onClick(() => { this.play(choice.value); })

Q4: 条件渲染不生效?

A: ArkTS 必须使用 if 语句,不能用 && 运算符:

// ❌ 错误
this.roundCount > 0 && Text('显示')

// ✅ 正确
if (this.roundCount > 0) {
  Text('显示')
}

Q5: 如何修改应用图标?

A: 替换 entry/src/main/resources/base/media/ 目录下的图片文件。

七、项目总结

7.1 核心知识点

知识点 说明
@State 状态变量,变化时自动更新 UI
Column/Row 垂直/水平布局容器
layoutWeight 按权重分配剩余空间
ForEach 循环渲染列表项
if 条件渲染
.onClick() 点击事件绑定

7.2 开发流程

设计功能 → 定义状态 → 实现逻辑 → 构建界面 → 配置文件 → 运行测试

7.3 扩展思路

  • 添加游戏历史记录(Preferences)
  • 添加动画效果(animateTo)
  • 添加音效(AVPlayer)
  • 支持多人对战
  • 添加排行榜

项目信息:

  • SDK:API 23 (最低) / API 24 (目标)
  • 开发工具:DevEco Studio 5.0+

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐