HarmonyOS NEXT 入门实战:ArkTS 从零实现石头剪刀布小游戏(完整源码 + 超详细解析)
本文介绍了一个基于HarmonyOS NEXT(API12)开发的石头剪刀布人机对战小游戏项目。项目采用ArkTS+ArkUI声明式语法实现,包含完整的游戏功能:手势选择、电脑随机出拳、胜负判定、数据统计和重置功能。文章详细讲解了开发环境配置、设计思路、核心代码实现(包括状态管理、随机数生成、动画效果等),并提供了完整的可运行源码。该项目适合鸿蒙应用开发入门学习,涵盖了@State响应式状态管理、
本文基于当下主流的 HarmonyOS NEXT 系统 + API 12,采用 ArkTS 语言与 ArkUI 声明式开发范式,手把手带你从零开发经典人机对战小游戏 —— 石头剪刀布。
项目覆盖鸿蒙应用开发核心基础知识点:@State 响应式状态管理原理、随机数算法、业务逻辑分层、ArkUI 主流布局组件、组件动画、点击事件交互等。全文代码注释详尽、架构规范,不仅适合零基础鸿蒙开发者入门练手,也可作为高校实训、课程设计、技术面试的参考案例。文末附带问题排查、功能进阶拓展、代码优化思路,由浅入深帮你吃透基础语法与开发思想。
关键字:HarmonyOS NEXT、ArkTS、鸿蒙开发、ArkUI、石头剪刀布、移动应用、前端开发、声明式 UI
目录
一、项目整体概述 二、开发环境配置要求 三、整体设计思路与架构分层 四、完整可运行源码 五、核心代码逐段深度解析 六、项目运行实操演示 七、代码优化与功能进阶拓展 八、知识点总结与学习延伸 九、开发常见问题 & 完整解决方案 十、写在最后
一、项目整体概述
1.1 项目背景与定位
石头剪刀布是移动端经典入门小游戏,逻辑简单、交互直观,非常适合用来学习鸿蒙声明式 UI 开发。本项目不追求复杂功能,重点聚焦语法规范、编码习惯、状态管理思想,帮大家建立鸿蒙开发的基础认知。
1.2 核心功能清单
- 玩家点击按钮选择石头、剪刀、布三种手势;
- 程序通过随机算法模拟电脑出拳,完成人机对战;
- 内置标准对战规则,自动判定胜利、失败、平局三种结果;
- 实时统计全局对局数据:胜利、失败、平局次数;
- 支持一键重置游戏状态,清空所有统计数据;
- 为手势组件添加过渡动画,提升 APP 交互质感;
- 全局采用弹性布局,自动适配鸿蒙手机不同屏幕尺寸。
1.3 本次学习技术栈 & 知识点
通过本项目,你将系统掌握以下内容:
- ArkUI 基础布局组件:
Column纵向布局、Row横向布局、Text文本、Button按钮 - ArkTS 核心装饰器:
@State响应式状态(数据驱动视图核心) - 组件事件:按钮
onClick点击事件绑定与回调处理 - 基础语法:常量定义、数组使用、分支条件判断
- 工具方法:
Math.random()随机数生成规则与实践 - 动效开发:ArkUI 内置
animation动画属性配置 - 编码规范:逻辑与 UI 分离、方法封装、代码注释规范
二、开发环境配置要求
在开始编码前,请严格匹配以下环境,从根源避免编译、运行报错。
表格
| 工具 / 软件 | 推荐版本 | 用途说明 |
|---|---|---|
| DevEco Studio | 4.0 及以上 | 鸿蒙官方集成开发 IDE,代码编写、编译、运行一体化工具 |
| HarmonyOS SDK | API 12 | 适配 HarmonyOS NEXT 最新系统,兼容 ArkTS 新语法 |
| 运行设备 | 模拟器 / 鸿蒙真机 | 优先使用手机模拟器调试,真机可做最终效果验证 |
环境检查实操步骤:
- 打开 DevEco Studio,点击顶部菜单栏
File -> Project Structure;- 在
Modules选项卡中确认 Compile SDK 为 API 12;- 新建项目时,模板选择
Empty Ability,语言选择ArkTS。
三、整体设计思路与架构分层
遵循高内聚、低耦合的开发思想,将项目整体划分为四大模块,结构清晰、便于后期维护和拓展:
-
UI 界面层 采用
Column + Row组合布局,页面划分为:标题区、对战展示区、结果提示区、数据统计区、功能按钮区,模块化拆分页面结构。 -
状态管理层 使用
@State管理所有动态数据:玩家手势、电脑手势、对局结果、统计数据。利用鸿蒙数据驱动视图特性,数据变更自动刷新页面,无需手动操作 DOM。 -
业务逻辑层 单独封装对局方法、胜负判定规则、游戏重置方法,将业务逻辑与 UI 代码解耦,代码可读性更强。
-
交互动效层 为手势组件添加过渡动画,优化点击反馈,符合鸿蒙系统流畅交互的设计理念。
四、完整可运行源码
文件路径:entry/src/main/ets/pages/Index.ets 使用方式:新建空白 ArkTS 项目后,直接替换该文件全部代码,即可编译运行。
typescript
运行
@Entry
@Component
struct RockPaperScissors {
// 手势常量定义:使用常量替代硬编码,提升代码可维护性
private readonly ROCK: number = 0;
private readonly SCISSORS: number = 1;
private readonly PAPER: number = 2;
// 手势表情与文字映射数组,统一管理UI展示内容
private readonly handEmojis: string[] = ['✊', '✌️', '🖐️'];
private readonly handNames: string[] = ['石头', '剪刀', '布'];
// ========== 全局响应式状态变量 ==========
// 玩家选择手势,初始值-1代表未选择
@State userChoice: number = -1;
// 电脑随机手势,初始值-1代表未生成
@State computerChoice: number = -1;
// 对局结果提示文本
@State resultText: string = '请选择你的手势开始游戏!';
// 对局统计数据
@State winCount: number = 0;
@State loseCount: number = 0;
@State drawCount: number = 0;
build() {
// 页面根布局:纵向全局布局
Column() {
// 页面标题模块
Column() {
Text('✂️ 石头剪刀布 人机对战')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')
}
.width('100%')
.alignItems(HorizontalAlign.Center)
.margin({ top: 30, bottom: 40 })
// 对战核心区域:玩家 VS 电脑
Row() {
// 玩家展示区域
Column() {
Text('玩家')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 15 })
Text(this.userChoice === -1 ? '❓' : this.handEmojis[this.userChoice])
.fontSize(100)
// 手势切换过渡动画
.animation({ duration: 300, curve: Curve.EaseOut })
}
.alignItems(HorizontalAlign.Center)
.flexGrow(1)
// 对战标识
Text('VS')
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor('#ff3b30')
.margin({ left: 20, right: 20 })
// 电脑展示区域
Column() {
Text('电脑')
.fontSize(20)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 15 })
Text(this.computerChoice === -1 ? '❓' : this.handEmojis[this.computerChoice])
.fontSize(100)
.animation({ duration: 300, curve: Curve.EaseOut })
}
.alignItems(HorizontalAlign.Center)
.flexGrow(1)
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.margin({ bottom: 40 })
// 对局结果提示文本
Text(this.resultText)
.fontSize(22)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 30 })
// 数据统计区域:胜/负/平展示
Row() {
Text(`胜利:${this.winCount}`)
.fontSize(18)
.fontColor('#34c759')
.margin({ right: 25 })
Text(`失败:${this.loseCount}`)
.fontSize(18)
.fontColor('#ff3b30')
.margin({ right: 25 })
Text(`平局:${this.drawCount}`)
.fontSize(18)
.fontColor('#666666')
}
.margin({ bottom: 50 })
// 手势选择按钮组
Row() {
Button(`${this.handEmojis[this.ROCK]} 石头`)
.width(120)
.height(50)
.fontSize(16)
.backgroundColor('#007aff')
.onClick(() => {
this.play(this.ROCK);
})
Button(`${this.handEmojis[this.SCISSORS]} 剪刀`)
.width(120)
.height(50)
.fontSize(16)
.backgroundColor('#007aff')
.margin({ left: 15, right: 15 })
.onClick(() => {
this.play(this.SCISSORS);
})
Button(`${this.handEmojis[this.PAPER]} 布`)
.width(120)
.height(50)
.fontSize(16)
.backgroundColor('#007aff')
.onClick(() => {
this.play(this.PAPER);
})
}
.margin({ bottom: 30 })
// 游戏重置按钮
Button('🔄 重新开始游戏')
.width(250)
.height(50)
.fontSize(16)
.backgroundColor('#8e8e93')
.onClick(() => {
this.resetGame();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
.alignItems(HorizontalAlign.Center)
.padding({ left: 20, right: 20, top: 20 })
.backgroundColor('#ffffff')
}
/**
* 游戏核心对局方法
* @param userChoice 玩家选中的手势下标
*/
play(userChoice: number): void {
// 赋值玩家选择的手势
this.userChoice = userChoice;
// 电脑随机生成 0 / 1 / 2 对应三种手势
this.computerChoice = Math.floor(Math.random() * 3);
// 胜负逻辑判断
if (userChoice === this.computerChoice) {
// 双方手势一致:平局
this.resultText = '🤝 本局平局,再来一局吧!';
this.drawCount++;
} else if (
(userChoice === this.ROCK && this.computerChoice === this.SCISSORS) ||
(userChoice === this.SCISSORS && this.computerChoice === this.PAPER) ||
(userChoice === this.PAPER && this.computerChoice === this.ROCK)
) {
// 玩家满足获胜条件
this.resultText = '🎉 恭喜你,本局获胜!';
this.winCount++;
} else {
// 其余情况判定为玩家失败
this.resultText = '😥 本局失利,再接再厉!';
this.loseCount++;
}
}
/**
* 重置游戏方法:恢复所有状态至初始值
*/
resetGame(): void {
this.userChoice = -1;
this.computerChoice = -1;
this.resultText = '请选择你的手势开始游戏!';
this.winCount = 0;
this.loseCount = 0;
this.drawCount = 0;
}
}
五、核心代码逐段深度解析
5.1 常量与数组定义(编码规范讲解)
typescript
运行
private readonly ROCK: number = 0;
private readonly SCISSORS: number = 1;
private readonly PAPER: number = 2;
private readonly handEmojis: string[] = ['✊', '✌️', '🖐️'];
- 使用
readonly定义只读常量,运行期间不可修改,规避数据误改问题; - 用常量代替数字硬编码,语义清晰,后期修改、拓展无需全局查找替换;
- 数组统一管理展示文本与表情,实现数据与视图分离。
5.2 @State 响应式状态(鸿蒙核心原理)
typescript
运行
@State userChoice: number = -1;
@State computerChoice: number = -1;
@State resultText: string = '请选择你的手势开始游戏!';
@State 是 ArkUI 实现数据驱动视图的核心装饰器:
- 被
@State修饰的变量为响应式变量; - 当变量值发生变更时,框架会自动检测并刷新页面中依赖该变量的组件;
- 区别于传统安卓 /iOS 命令式开发,无需手动调用刷新视图方法,开发效率更高。
5.3 布局组件详解
Column:纵向弹性布局,页面整体、独立模块均使用该组件,是鸿蒙最常用布局;Row:横向弹性布局,用于实现并排展示的元素(对战区、按钮组、统计行);flexGrow:弹性占比属性,实现左右区域等分屏幕宽度,适配不同机型;margin / padding:控制内边距、外边距,规范页面留白,提升视觉体验。
5.4 组件动画配置
typescript
运行
.animation({ duration: 300, curve: Curve.EaseOut })
duration:动画时长,单位毫秒,本案例设置 300ms,符合移动端通用动效标准;curve:动画运动曲线,Curve.EaseOut表示先快后慢,过渡效果更自然;- 动画跟随状态变化自动触发,无需额外手动调用。
5.5 随机数算法解析
typescript
运行
this.computerChoice = Math.floor(Math.random() * 3);
Math.random():生成范围在[0, 1)的随机小数;- 乘以 3 后范围变为
[0, 3); Math.floor()向下取整,最终得到0、1、2三个整数,完美对应三种手势。
5.6 胜负判定逻辑
遵循经典游戏规则:
- 石头(0)击败 剪刀(1)
- 剪刀(1)击败 布(2)
- 布(2)击败 石头(0)
- 下标相等直接判定为平局 逻辑采用多分支判断,写法直观,新手极易理解。
5.7 方法封装思想
将对局、重置功能单独封装为独立函数:
- 代码模块化,逻辑集中管理;
- 复用性强,一处定义多处调用;
- 降低主体
build函数复杂度,代码层级分明。
六、项目运行实操演示
6.1 完整运行步骤
- 打开 DevEco Studio,新建项目,模板选择
Empty Ability,语言选择ArkTS,SDK 选择 API 12; - 进入路径
entry/src/main/ets/pages/,打开Index.ets,清空原有代码,粘贴上方完整源码; - 点击工具栏 Run 按钮,选择模拟器或连接好的鸿蒙真机;
- 等待编译完成,自动启动应用即可体验。
6.2 功能效果说明
- 初始状态:玩家与电脑区域均显示问号,页面提示选择手势;
- 对局过程:点击任意手势按钮,页面触发动画,电脑随机出拳并展示结果;
- 数据统计:每完成一局,胜利 / 失败 / 平局对应数据自动累加;
- 重置功能:点击「重新开始游戏」,所有状态与统计数据恢复为初始状态。
建议:发布文章时在此处插入 4 张运行截图(初始页、对局胜利、对局失败、重置后页面),图文结合大幅提升阅读评分。
七、代码优化与功能进阶拓展
7.1 现有代码优化点(进阶编码习惯)
-
防重复点击 对局过程中禁用按钮,避免短时间连续点击造成逻辑错乱,核心优化代码:
typescript
运行
@State isPlaying: boolean = false; // 按钮增加禁用状态 Button('石头') .enabled(!this.isPlaying) .onClick(()=>{ this.isPlaying = true; this.play(this.ROCK); }) // play 方法末尾恢复状态 this.isPlaying = false; -
抽离提示文案 将所有提示文字统一抽离为常量,便于后期多语言适配。
7.2 进阶功能拓展(课后练手方向)
- 音频拓展:调用鸿蒙音频 API,为出拳、胜利、失败添加音效;
- 图片资源替换:使用本地图片替代 Emoji 手势,自定义游戏皮肤;
- 连胜统计:新增变量记录连续胜利次数,增加游戏趣味性;
- 历史对局记录:使用数组存储每一局结果,实现对战日志功能;
- 难度模式:区分简单 / 困难模式,调整电脑随机出拳概率。
八、知识点总结与学习延伸
8.1 本项目核心总结
- 鸿蒙 ArkUI 采用声明式 UI + 数据驱动视图,是区别于传统开发的核心思想;
@State状态管理是所有鸿蒙应用的基础,后续组件通信、全局状态均基于此延伸;Column + Row组合布局可以实现移动端绝大多数页面结构,务必熟练掌握;- 方法封装、常量定义、代码注释是专业开发的必备编码规范。
8.2 后续学习路线
掌握本案例后,可以依次练习:计数器、简易计算器、待办清单、图片轮播等入门项目,循序渐进学习 @Link、@Prop、@Provide 等高级状态装饰器、自定义组件、页面路由等知识点。
九、开发常见问题 & 完整解决方案
整理开发过程中高频报错,附带原因 + 解决方案,一站式排错:
-
问题:编译报错,提示 API 版本过低 原因:项目 SDK 未切换为 API 12 解决:进入
Project Structure,将 Compile SDK 修改为 API 12,重新编译。 -
问题:点击按钮无任何响应 原因:
onClick事件未绑定、方法名书写错误、语法括号缺失 解决:逐行检查回调函数与方法名称,核对大小写与符号。 -
问题:页面布局错乱、元素超出屏幕 原因:固定宽高、布局属性搭配不当 解决:移动端优先使用百分比宽高,配合
flexGrow实现弹性适配。 -
问题:模拟器黑屏、应用无法启动 原因:模拟器进程卡死、代码存在语法死循环 解决:关闭并重启模拟器,检查代码语法,重新运行项目。
-
问题:动画效果不生效 原因:动画属性未绑定响应式变量 解决:确保动画依赖的状态变量被
@State修饰
更多推荐


所有评论(0)