HarmonyOS ArkTS 实战:实现一个掷骰子模拟器
项目效果
本文实现的是一个基于 HarmonyOS 和 ArkTS 的掷骰子模拟器。项目中使用 ArkUI 组件完成页面布局,通过 @State 管理状态数据,实现1-6个骰子选择、投掷动画、骰子点数显示、总和计算和投掷历史等功能。
最终运行效果如下:

页面主要包含以下内容:
- 顶部应用标题;
- 骰子显示区域,用圆点显示点数;
- 投掷动画效果;
- 骰子数量选择;
- 大投掷按钮;
- 点数总和显示;
- 投掷历史记录;
- 页面整体采用 ArkUI 声明式布局。
本文重点是演示如何在 HarmonyOS 项目中使用 ArkTS 和 ArkUI 实现一个趣味小工具单页面应用。项目代码主要写在 entry/src/main/ets/pages/Index.ets 文件中,适合作为 HarmonyOS ArkTS 入门到进阶之间的练习案例。
前言
掷骰子是很多桌游和游戏中必不可少的环节,有时候手边没有骰子,手机上的骰子模拟器就很方便。一个好的骰子模拟器不仅能随机出点数,还要有投掷的动画效果,增加真实感和趣味性。
从应用开发角度来看,这个项目不依赖后端接口,也不需要数据库,但能练习 ArkTS 中的状态管理、动画效果、组件封装、随机数和列表渲染等内容。
本文基于 HarmonyOS 和 ArkTS 实现一个掷骰子模拟器。用户可以选择1-6个骰子,点击投掷按钮,骰子会有旋转动画,最后显示随机点数,计算总和,并记录投掷历史。
这个项目的核心不是简单的随机数,而是流畅的投掷动画和直观的骰子显示。每一次投掷,都有视觉反馈,让用户有真实掷骰子的感觉,这正是 ArkUI 动画的魅力所在。
一、项目目标
本次实践主要实现以下目标:
- 创建 HarmonyOS ArkTS 页面;
- 使用
@Entry和@Component定义页面组件; - 使用
@State管理页面状态; - 自定义骰子组件,用圆点显示点数;
- 支持1-6个骰子选择;
- 投掷时的旋转动画;
- 随机生成1-6点数;
- 自动计算点数总和;
- 记录投掷历史;
- 骰子点数用圆点图形表示;
- 使用
@Builder封装骰子面和历史项; - 完成一个可以运行的掷骰子模拟器页面。
这个项目虽然是单页面应用,但它实现了有趣的动画效果和完整的投掷功能,比普通静态页面更适合练习 ArkTS。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | HarmonyOS 应用开发 |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI |
| SDK 版本 | HarmonyOS API 23 及以上 |
| 工程模型 | Stage 模型 |
| 核心组件 | Text / Button / Column / Row / Flex / Circle |
| 状态管理 | @State |
| 数据处理 | 随机数 / 动画效果 / 历史记录 |
| 项目入口 | entry/src/main/ets/pages/Index.ets |
| 运行平台 | 模拟器或真机 |
本项目是 HarmonyOS 原生 ArkTS 项目。页面主体由 ArkUI 组件构建,核心逻辑写在 Index.ets 文件中,不依赖后端接口,也不需要额外配置数据库。
三、为什么选择掷骰子模拟器项目
掷骰子模拟器适合作为 ArkTS 练习项目,主要有以下几个原因。
第一,趣味性强。掷骰子有随机性和动画效果,玩起来很有趣。
第二,视觉效果好。骰子用圆点表示点数,动画旋转,界面美观。
第三,适合练习自定义组件。骰子可以封装成独立组件,练习组件化思想。
第四,适合练习动画效果。投掷时的旋转动画是基础动画练习。
第五,适合练习随机数。随机生成1-6的点数,模拟真实骰子。
第六,扩展空间比较大。基础功能完成后,可以继续增加不同面数的骰子(4面/8面/10面/20面)、骰子皮肤、掷骰子声音、多轮游戏和统计分析。
在本项目中,骰子数量和每个骰子的点数是核心数据。所有显示和计算都基于这些数据。
四、功能规则说明
骰子数量:1-6个,默认2个。
点数范围:每个骰子1-6点,均匀随机。
动画效果:投掷时骰子旋转360度,持续500毫秒。
总和计算:自动计算所有骰子点数之和。
历史记录:最多记录最近20次投掷结果,显示每次的点数和总和。
骰子显示:白色圆角正方形,黑色圆点表示点数,标准骰子点数排列。
五、项目结构
本项目主要修改首页文件:
entry
└── src
└── main
└── ets
└── pages
└── Index.ets
其中:
| 文件 | 作用 |
|---|---|
| Index.ets | 编写页面结构、状态数据和骰子逻辑 |
本文不涉及复杂路由,也不需要额外创建多个页面。对于练习项目来说,把主要逻辑集中在一个 Index.ets 文件中更方便理解。
六、核心实现思路
本项目的核心流程如下:
- 使用
@State保存骰子数量、每个骰子点数、是否正在动画; - 用户选择骰子数量;
- 点击投掷按钮;
- 触发旋转动画;
- 动画期间快速切换点数制造滚动效果;
- 动画结束后确定最终点数;
- 用圆点图形显示每个骰子的点数;
- 计算点数总和;
- 将结果添加到历史记录;
- 点击历史可以查看详情。
项目中最重要的状态变量如下:
@State diceCount: number = 2
@State diceValues: number[] = [1, 1]
@State isRolling: boolean = false
@State history: { values: number[], total: number }[] = []
其中:
| 状态变量 | 作用 |
|---|---|
| diceCount | 骰子数量 |
| diceValues | 每个骰子的点数 |
| isRolling | 是否正在投掷动画中 |
| history | 投掷历史记录 |
骰子点数数组是本项目中最重要的数据,所有显示都基于它。
七、Index.ets 完整代码
打开文件:
entry/src/main/ets/pages/Index.ets
将其中内容替换为下面代码:
typescript
@Entry
@Component
struct Index {
@State diceCount: number = 2
@State diceValues: number[] = [1, 1]
@State isRolling: boolean = false
@State history: { values: number[], total: number }[] = []
@State rotation: number = 0
private countOptions: number[] = [1, 2, 3, 4, 5, 6]
private rollDice(): void {
if (this.isRolling) return
this.isRolling = true
this.rotation = 0
let rollInterval = setInterval(() => {
this.diceValues = this.diceValues.map(() => {
return Math.floor(Math.random() * 6) + 1
})
}, 80)
animateTo({
duration: 500,
curve: Curve.EaseOut,
onFinish: () => {
clearInterval(rollInterval)
this.isRolling = false
let finalValues = this.diceValues.map(() => {
return Math.floor(Math.random() * 6) + 1
})
this.diceValues = finalValues
let total = finalValues.reduce((sum, v) => sum + v, 0)
this.history = [{ values: [...finalValues], total }, ...this.history].slice(0, 20)
}
}, () => {
this.rotation = 360
})
}
private setDiceCount(count: number): void {
if (this.isRolling) return
this.diceCount = count
this.diceValues = new Array(count).fill(1)
}
private getTotal(): number {
return this.diceValues.reduce((sum, v) => sum + v, 0)
}
@Builder
DiceFace(value: number) {
Stack() {
Column() {
if (value >= 2) {
Row() {
Circle().width(12).height(12).fill('#1F2937')
Blank()
if (value >= 3) {
Circle().width(12).height(12).fill('#1F2937')
}
}
.width('100%')
.layoutWeight(1)
}
if (value === 1 || value === 3 || value === 5) {
Row() {
Blank()
Circle().width(12).height(12).fill('#1F2937')
Blank()
}
.width('100%')
.layoutWeight(1)
}
if (value >= 2) {
Blank().layoutWeight(1)
Row() {
if (value >= 4) {
Circle().width(12).height(12).fill('#1F2937')
}
Blank()
Circle().width(12).height(12).fill('#1F2937')
}
.width('100%')
.layoutWeight(1)
}
}
.width('100%')
.height('100%')
.padding(16)
}
.width(80)
.height(80)
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 4
})
.rotate({ angle: this.rotation })
.animation({ duration: 500, curve: Curve.EaseOut })
}
@Builder
HistoryItem(item: { values: number[], total: number }, index: number) {
Row() {
Text(`#${this.history.length - index}`)
.fontSize(14)
.fontColor('#9CA3AF')
.width(40)
Text(item.values.join(' + '))
.fontSize(15)
.fontColor('#4B5563')
.layoutWeight(1)
Text(`= ${item.total}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.border({ width: { bottom: 1 }, color: '#F3F4F6' })
}
build() {
Column() {
Text('掷骰子')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.margin({ top: 22 })
Text('基于 HarmonyOS ArkTS 的骰子模拟器')
.fontSize(14)
.fontColor('#6B7280')
.margin({ top: 8, bottom: 20 })
Column() {
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
ForEach(this.diceValues, (value: number, index: number) => {
this.DiceFace(value)
.margin({ right: 12, bottom: 12, left: index % 3 === 0 ? 12 : 0 })
}, (v: number, i: number) => `${i}-${v}`)
}
.width('100%')
.padding({ top: 24, bottom: 20 })
Text(`点数总和:${this.getTotal()}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
.margin({ bottom: 20 })
Button(this.isRolling ? '投掷中...' : '🎲 投掷骰子')
.width('100%')
.height(56)
.fontSize(18)
.fontColor(Color.White)
.backgroundColor(this.isRolling ? '#9CA3AF' : '#EF4444')
.borderRadius(28)
.enabled(!this.isRolling)
.onClick(() => {
this.rollDice()
})
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ bottom: 16 })
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
Column() {
Text('骰子数量')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 12 })
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.countOptions, (count: number) => {
Button(`${count}个`)
.width(48)
.height(48)
.fontSize(15)
.fontColor(this.diceCount === count ? Color.White : '#4B5563')
.backgroundColor(this.diceCount === count ? '#0A59F7' : '#F3F4F6')
.borderRadius(24)
.onClick(() => {
this.setDiceCount(count)
})
}, (c: number) => c.toString())
}
.width('100%')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ bottom: 16 })
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
if (this.history.length > 0) {
Column() {
Text('投掷历史')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 8 })
ForEach(this.history, (item: { values: number[], total: number }, index: number) => {
this.HistoryItem(item, index)
}, (item: { values: number[], total: number }, i: number) => i.toString())
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
}
}
.width('100%')
.height('100%')
.padding({ left: 18, right: 18 })
.backgroundColor('#F5F7FA')
}
}
八、代码实现说明
1. 骰子点数绘制
用标准骰子圆点布局:
- 1点:中间一个点
- 2点:左上和右下
- 3点:左上、中间、右下
- 4点:四个角
- 5点:四个角加中间
- 6点:左右各三个点
2. 投掷动画
使用animateTo实现360度旋转动画,同时用setInterval快速切换点数制造滚动效果,动画结束后确定最终点数。
3. 随机数生成
每个骰子独立生成1-6的随机数,模拟真实骰子的均匀分布。
4. 数量选择
支持1-6个骰子,选择后重置所有骰子为1点。
5. 总和计算
使用reduce计算所有骰子点数之和,大字体蓝色显示。
6. 历史记录
每次投掷结果保存到历史,显示点数组合和总和,最多保存20条。
九、运行项目
代码编写完成后,在DevEco Studio中运行项目。选择不同骰子数量,点击投掷按钮,观察骰子旋转动画,检查点数是否正确,总和计算是否准确,历史记录是否正常。
十、开发中遇到的问题
- 动画流畅度:用快速切换点数+旋转动画模拟真实掷骰子效果
- 骰子布局:正确绘制1-6点的标准圆点位置
- 防重复点击:动画进行中禁止再次点击
- 响应式布局:多个骰子时自动换行,适配不同数量
十一、总结
本文基于HarmonyOS和ArkTS实现了一个掷骰子模拟器。项目通过自定义骰子组件和动画效果实现有趣的投掷体验,支持多骰子、点数统计和历史记录。这个项目展示了ArkTS中组件封装、动画效果和随机数处理的基本方法,适合作为入门练习项目。