HarmonyOS NEXT 开发手记:用 ArkTS 实现一个童年经典游戏
HarmonyOS NEXT 开发手记:用 ArkTS 实现一个童年经典游戏
还记得小时候和小伙伴玩石头剪刀布的快乐时光吗?今天我们就用 HarmonyOS NEXT 和 ArkTS 来实现这个经典游戏,体验一下声明式 UI 开发的魅力!
一、前言
石头剪刀布,这个简单却充满策略的小游戏,承载了我们太多童年回忆。选择它作为鸿蒙开发的学习项目,再合适不过了:
- 规则简单易懂,不需要复杂的业务逻辑
- 涉及用户交互、状态管理、动画等核心知识点
- 界面直观,便于理解布局系统
- 容易扩展,可以添加各种花里胡哨的功能
话不多说,咱们开始吧!
二、准备工作
开发环境一览
| 项目 | 版本/说明 |
|---|---|
| 操作系统 | Windows 10/11 |
| IDE | DevEco Studio 5.0+ |
| HarmonyOS SDK | API 23 (6.1.0) 最低版本 |
| 目标 SDK | API 24 (6.1.1) |
| 开发语言 | ArkTS (TypeScript 超集) |
创建项目
打开 DevEco Studio,选择 File → New → Create Project,选择 Empty Ability 模板,填写项目信息即可。项目创建后,IDE 会自动生成标准的项目结构。
三、项目结构一览
先来看看项目长什么样:
MyApplication/
├── AppScope/ # 全局资源
│ └── resources/base/
│ └── element/string.json
├── entry/ # 主模块
│ ├── src/main/
│ │ ├── ets/
│ │ │ ├── entryability/
│ │ │ │ └── EntryAbility.ets # 入口
│ │ │ └── pages/
│ │ │ └── Index.ets # 主页面
│ │ └── resources/
│ │ └── base/
│ │ ├── element/ # 字符串、颜色等
│ │ ├── media/ # 图片资源
│ │ └── profile/
│ │ └── main_pages.json # 页面路由
│ └── build-profile.json5
└── build-profile.json5
关键文件就两个:
EntryAbility.ets:应用入口,相当于 Android 的 ApplicationIndex.ets:主页面,所有 UI 和逻辑都在这里
四、动手实现
4.1 定义数据结构
首先定义一个接口来描述"出拳选项":
interface Choice {
emoji: string; // 显示的表情:✊✋✌️
label: string; // 文字标签:石头/布/剪刀
value: string; // 值:rock/paper/scissors
}
这样设计的好处是数据和显示分离,后续想改个图标什么的,只改一处就行。
4.2 组件状态定义
ArkTS 用 @State 来声明响应式状态,当状态变化时 UI 会自动更新:
@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' }
];
}
这里有个坑:@State 变量一定要显式声明类型!写成 @State playerScore = 0 可能会有类型推断问题。
4.3 游戏逻辑实现
电脑随机出拳
getComputerChoice(): string {
const randomIndex = Math.floor(Math.random() * 3);
return this.choices[randomIndex].value;
}
简单粗暴,随机从三个选项里选一个。
判定胜负
这个游戏的精髓就在这里:
play(playerValue: string): void {
const computerValue: string = this.getComputerChoice();
// 更新显示
this.playerChoice = this.choices.find(c => c.value === playerValue)?.emoji ?? '❔';
this.computerChoice = this.choices.find(c => c.value === computerValue)?.emoji ?? '❔';
this.roundCount++;
// 判定逻辑
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++;
}
}
胜负规则大家都懂:
- 石头克剪刀
- 剪刀克布
- 布克石头
4.4 界面布局
ArkTS 的布局很像 Flutter,用 Column 和 Row 来组织界面。
整体结构
build() {
Column() {
// 标题
// 对战区域
// 结果
// 出拳按钮
// 统计
// 重置按钮
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
.padding(20)
}
标题区
Text('✂️ 石头剪刀布')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor('#1C1C1E')
.margin({ top: 30, bottom: 8 })
Text('和电脑对战!')
.fontSize(14)
.fontColor('#8E8E93')
.margin({ bottom: 24 })
对战区域(核心布局)
这部分用 Row 水平排列三个区域:
Row() {
// 玩家
Column() {
Text('🧑 你').fontSize(14).fontColor('#8E8E93')
Text(this.playerChoice).fontSize(64).animation({ duration: 300 })
Text(`${this.playerScore} 胜`).fontSize(16).fontColor('#34C759')
}
.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')
Text(this.computerChoice).fontSize(64).animation({ duration: 300 })
Text(`${this.computerScore} 胜`).fontSize(16).fontColor('#FF3B30')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding(20)
.backgroundColor('#F2F2F7')
.borderRadius(16)
注意 layoutWeight 的用法:
- 玩家和电脑各占 1 份
- VS 占 0.5 份
- 总共 2.5 份,按比例分配宽度
出拳按钮
用 ForEach 循环渲染三个按钮,避免写重复代码:
Row() {
ForEach(this.choices, (choice: Choice) => {
Column() {
Text(choice.emoji).fontSize(44)
Text(choice.label).fontSize(14).fontColor('#1C1C1E')
}
.width(96)
.height(110)
.backgroundColor('#F2F2F7')
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.play(choice.value); // 注意用箭头函数!
})
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceEvenly)
又一个坑:.onClick(this.play(choice.value)) 这样写会直接执行函数,而不是绑定事件!正确写法是用箭头函数包装。
统计信息和重置按钮
// 统计(条件渲染)
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 })
}
// 重置按钮
Button('🔄 重新开始')
.type(ButtonType.Capsule)
.width(180)
.height(44)
.backgroundColor('#8E8E93')
.onClick(() => {
this.resetGame();
})
条件渲染的坑:ArkTS 不能像 React 那样用 condition && element,必须用原生 if 语句!
五、配置文件解读
main_pages.json
页面路由配置,所有页面都要在这里注册:
{
"src": [
"pages/Index"
]
}
module.json5
模块配置,相当于 Android 的 AndroidManifest.xml:
{
"module": {
"name": "entry",
"type": "entry",
"deviceTypes": ["phone"],
"abilities": [{
"name": "EntryAbility",
"label": "$string:EntryAbility_label",
"icon": "$media:layered_image"
}]
}
}
资源引用格式:$type:name,比如 $string:module_desc。
六、运行效果

游戏流程演示
-
初始界面:显示默认图标,提示选择出拳
-
点击出拳:玩家和电脑同时显示选择,结果即时更新
-
多局对战:分数持续累计,统计信息实时显示
-
重新开始:一键清空所有数据
七、开发过程中遇到的坑
坑 1:类型推断不可靠
// ❌ 可能有问题
@State playerScore = 0;
// ✅ 显式声明类型
@State playerScore: number = 0;
坑 2:事件绑定写错
// ❌ 直接执行了函数
.onClick(this.play(choice.value))
// ✅ 用箭头函数包装
.onClick(() => { this.play(choice.value); })
坑 3:条件渲染语法
// ❌ ArkTS 不支持这种写法
this.roundCount > 0 && Text('显示')
// ✅ 必须用 if 语句
if (this.roundCount > 0) {
Text('显示')
}
坑 4:layoutWeight 理解偏差
layoutWeight 不是百分比,而是按比例分配剩余空间:
Row() {
Column().layoutWeight(1) // 占 1 份
Column().layoutWeight(0.5) // 占 0.5 份
Column().layoutWeight(1) // 占 1 份
}
// 总共 2.5 份
坑 5:资源引用格式
// ❌ 错误格式(有空格)
"description": "$string : module_desc"
// ✅ 正确格式
"description": "$string:module_desc"
八、总结与展望
这个项目学到了什么?
| 知识点 | 说明 |
|---|---|
| @State | 响应式状态管理 |
| Column/Row | 线性布局 |
| layoutWeight | 权重分配 |
| ForEach | 循环渲染 |
| 条件渲染 | if 语句 |
| 事件绑定 | 箭头函数 |
可以怎么扩展?
- 保存历史记录:用 Preferences 存储最高分和战绩
- 添加动画:出拳时的翻转效果、胜负时的庆祝动画
- 加音效:点击音效、胜负音效
- 多页面:添加设置页、历史记录页
- 多人模式:支持双人本地对战或在线对战
感想
ArkTS 的声明式 UI 真的很舒服,写起来比传统 Android 简洁多了。状态驱动 UI 的理念,让代码逻辑更清晰。DevEco Studio 的实时预览功能也大大提升了开发效率。
总之,HarmonyOS NEXT 的开发体验还是很不错的,值得深入学习和实践!
项目信息:
- SDK 版本:API 23 (最低) / API 24 (目标)
- 开发工具:DevEco Studio 5.0+
更多推荐


所有评论(0)