Kotlin 节奏点击训练小游戏:从 Kotlin 到 JavaScript 再到 ArkTS
本篇文章基于一个全新的 Kotlin 小案例——rhythmTapTrainer 节奏点击训练小游戏,完整演示:
- Kotlin 侧如何实现节奏分析算法并通过
@JsExport导出。 - JavaScript/TypeScript 侧如何通过
hellokjs.js/hellokjs.d.ts调用该函数。 - ArkTS(OpenHarmony UI 层) 中如何在
Index.ets调用这个 Kotlin/JS 导出的函数,并把结果显示在页面上。
在代码之外,本文也会结合一些实际开发经验,讨论 Kotlin/JS + ArkTS 这种跨栈组合在教学 Demo 和小工具中的优势和注意点。
一、Kotlin 端:节奏点击训练算法实现
下面是 Kotlin 侧核心实现代码,位于 src/jsMain/kotlin/App.kt 中,通过 @JsExport 导出给 JavaScript 使用:
@OptIn(ExperimentalJsExport::class)
@JsExport
fun rhythmTapTrainer(inputText: String = "380 420 410 395 405 390"): String {
// 输入格式: 一串用空格分隔的毫秒间隔,表示用户连续点击的时间间隔
// 例如: "380 420 410 395 405 390" 表示每次点击间隔约 400ms
val parts = inputText.trim().split(" ").filter { it.isNotEmpty() }
if (parts.isEmpty()) {
return "❌ 错误: 请输入至少一次点击间隔,例如: 380 420 410 395 405 390"
}
val intervals = parts.mapNotNull { it.toIntOrNull() }.filter { it > 0 }
if (intervals.isEmpty()) {
return "❌ 错误: 输入中没有有效的间隔毫秒数\n示例: 380 420 410 395 405 390"
}
val count = intervals.size
val target = 400 // 目标节奏间隔 400ms
val avg = intervals.sum().toDouble() / count
val best = intervals.minOrNull() ?: 0
val worst = intervals.maxOrNull() ?: 0
// 计算与目标节奏的偏差与稳定性
val deviations = intervals.map { it - target }
val avgDeviation = deviations.sum().toDouble() / count
val variance = if (count > 1) {
intervals.map { (it - avg) * (it - avg) }.sum() / (count - 1)
} else 0.0
val stdDev = kotlin.math.sqrt(variance)
val avgRounded = (avg * 10).toInt() / 10.0
val avgDevRounded = (avgDeviation * 10).toInt() / 10.0
val stdRounded = (stdDev * 10).toInt() / 10.0
// 评分:越接近 400ms 且越稳定,得分越高
val speedScore = when {
kotlin.math.abs(avg - target) <= 15 -> 50
kotlin.math.abs(avg - target) <= 30 -> 38
kotlin.math.abs(avg - target) <= 50 -> 25
else -> 15
}
val stabilityScore = when {
stdDev <= 10 -> 30
stdDev <= 25 -> 22
stdDev <= 40 -> 15
else -> 8
}
val rhythmScore = when {
kotlin.math.abs(avgDeviation) <= 10 -> 20
kotlin.math.abs(avgDeviation) <= 25 -> 14
kotlin.math.abs(avgDeviation) <= 40 -> 10
else -> 6
}
val totalScore = (speedScore + stabilityScore + rhythmScore).coerceIn(0, 100)
val level = when {
totalScore >= 85 -> "🎵 节奏大师"
totalScore >= 70 -> "🥁 节奏达人"
totalScore >= 55 -> "🎼 初级节奏手"
else -> "🎹 需要多多练习"
}
val suggestion = buildString {
append("• 可以跟着节拍器或喜欢的歌曲,用手指轻敲屏幕或桌面,训练对节奏的感知。\n")
append("• 保持身体放松很重要,越紧张手越容易提前或拖拍。\n")
append("• 练习时可以先放慢节奏,再逐渐提升速度,注意始终稳定在同一律动上。")
}
val readableList = intervals.joinToString(" ") { it.toString() + "ms" }
return "🎶 节奏点击训练小游戏\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"原始记录: $inputText\n\n" +
"1️⃣ 基础统计:\n" +
" 点击次数: ${count} 次\n" +
" 每次间隔: $readableList\n" +
" 最短间隔: ${best} ms\n" +
" 最长间隔: ${worst} ms\n" +
" 平均间隔: ${avgRounded} ms\n\n" +
"2️⃣ 节奏分析:\n" +
" 目标节奏: ${target} ms/拍\n" +
" 平均偏差: ${avgDevRounded} ms\n" +
" 稳定性(标准差): ${stdRounded} ms\n\n" +
"3️⃣ 评分结果:\n" +
" 速度评分: ${speedScore}/50\n" +
" 稳定性评分: ${stabilityScore}/30\n" +
" 律动感评分: ${rhythmScore}/20\n" +
" 综合得分: ${totalScore}/100 ($level)\n\n" +
"4️⃣ 练习建议:\n" +
" $suggestion\n\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"✅ 分析完成!"
}
这是节奏点击训练小游戏的核心实现函数,展示了完整的节奏分析算法。函数使用 @OptIn(ExperimentalJsExport::class) 和 @JsExport 注解,使其可以被编译成 JavaScript 并在 ArkTS 中调用。函数首先进行输入解析,使用 split(" ") 分割输入字符串,使用 filter { it.isNotEmpty() } 过滤空字符串。然后使用 mapNotNull { it.toIntOrNull() } 安全转换为整数,过滤掉无效数据。计算基础统计数据:点击次数、平均间隔、最短间隔、最长间隔。计算与目标节奏 400ms 的偏差,以及稳定性指标(标准差)。使用 when 表达式根据不同的指标范围计算三部分得分:速度评分(50分)、稳定性评分(30分)、律动感评分(20分)。计算总分并使用 coerceIn(0, 100) 确保在 0-100 范围内。根据总分判断等级,从"节奏大师"到"需要多多练习"。使用 buildString 构建练习建议。最后使用字符串模板和 joinToString() 格式化输出完整的分析报告。这个函数展示了如何将复杂的统计分析转换为用户友好的游戏反馈。
在 Kotlin 端有几个关键点值得注意:
- 数据解析:从
String到List<Int>的转换过程中,既要过滤空字符串,又要对非法数字做容错处理,这样在 JS/ArkTS 层输入脏数据时不会直接崩溃,而是返回清晰的错误信息。 - 节奏分析指标设计:这里使用平均值、标准差以及相对目标节奏的平均偏差作为三个维度来打分,这种设计既易于理解,又能在教学中自然引入方差、标准差这样的基础统计概念。
- 评分体系:通过
when表达式把连续数值区间映射到区间分数,再组合出一个 0–100 的总分,同时配合“节奏大师 / 节奏达人”等文案,让这个算法更像一个真正的小游戏结果汇总。 - 跨语言边界友好性:返回值直接是一个格式化好的多行字符串,而不是复杂的数据结构,这样在 JS/ArkTS 侧几乎可以零成本显示结果,非常适合入门级 Demo 和教学文章使用。
二、JavaScript 端:通过 hellokjs.js 调用 Kotlin 导出函数
Kotlin Multiplatform JS 使用 IR 后端,并启用了 useEsModules() 和 generateTypeScriptDefinitions()。编译完成后,我们可以在输出目录中拿到 hellokjs.mjs 和 hellokjs.d.ts,再通过脚本复制并重命名为 hellokjs.js,供 ArkTS 侧直接 import 使用。
在普通 JavaScript/TypeScript 环境下,可以像下面这样调用这个函数:
// 假设 hellokjs.js 与当前文件在同一目录
import { rhythmTapTrainer } from './hellokjs.js';
const input = '380 420 410 395 405 390';
const result = rhythmTapTrainer(input);
console.log('节奏分析结果:');
console.log(result);
这段代码展示了如何在 JavaScript 环境中调用编译后的 Kotlin 函数。首先使用 import 语句从编译后的 JavaScript 模块 hellokjs.js 中导入 rhythmTapTrainer 函数。定义输入字符串 '380 420 410 395 405 390',表示六次点击的时间间隔(单位毫秒)。调用 rhythmTapTrainer(input) 函数,传入输入字符串,获取节奏分析结果。使用 console.log() 输出结果。这展示了 KMP 的跨端能力,同一份 Kotlin 代码可以在 JavaScript 环境中无缝调用。编译器自动处理了 Kotlin 的类型系统到 JavaScript 的转换,使得调用方式和普通 JavaScript 函数完全相同。
这里要注意两点:
- 导出名称与 Kotlin 中标记为
@JsExport的函数名完全一致,因此 Kotlin 函数签名的命名风格会直接暴露在 JS 层。这一点在设计 API 时需要提前规划,尽量保持命名简洁直观。 - 类型声明文件
hellokjs.d.ts能为 TypeScript 或具备类型推断能力的 IDE 提供补全与参数提示,对日常开发和教学演示都很友好,读者可以直接在编辑器中看到rhythmTapTrainer(inputText: string): string这样的签名。
三、ArkTS 端:在 Index.ets 中调用 Kotlin/JS 导出的函数
在 OpenHarmony 的 ArkTS 页面中,我们已经把 hellokjs.js 复制到了 entry/src/main/ets/pages 目录下,因此可以在 Index.ets 顶部直接导入并调用:
import { rhythmTapTrainer } from './hellokjs';
@Entry
@Component
struct Index {
@State message: string = '请输入每次点击的时间间隔,例如: 380 420 410 395 405 390';
@State inputText: string = '380 420 410 395 405 390';
@State resultText: string = '';
aboutToAppear(): void {
this.analyzeReaction();
}
analyzeReaction(): void {
try {
const input: string = this.inputText;
const result: string = rhythmTapTrainer(input);
this.resultText = result;
this.message = '✓ 分析完成';
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.message = `✗ 错误: ${errorMessage}`;
}
}
}
这段 ArkTS 代码是鸿蒙应用的用户界面实现,展示了如何在 ArkTS 中导入和调用编译后的 Kotlin 函数。首先使用 import 语句从编译后的 JavaScript 模块中导入 rhythmTapTrainer 函数。使用 @Entry 装饰器标记这是应用的入口页面,使用 @Component 装饰器标记这是一个 ArkUI 组件。使用 @State 装饰器定义三个响应式状态变量:message 用于显示状态信息,inputText 存储用户输入的时间间隔数据(默认为示例数据),resultText 存储分析结果。aboutToAppear() 生命周期函数在组件加载时自动调用 analyzeReaction() 进行初始分析。analyzeReaction() 方法调用 Kotlin 编译的 JavaScript 函数 rhythmTapTrainer(),将用户输入的文本传入,获取节奏分析结果,然后更新 resultText 和 message。使用 try-catch 块捕获异常,如果发生错误则显示错误信息。这个结构实现了清晰的前后端职责划分:ArkTS 只负责收集输入和展示结果,所有节奏分析逻辑都集中在 Kotlin 中。
在实际界面构建中,我们还给页面加了一张卡片式布局、提示说明、输入框和按钮,让这个小游戏更贴近真实的应用场景。ArkTS 层的代码非常接近前端框架(例如 React)的思维模式,通过 @State 管理状态并自动驱动 UI 更新,这一点和 Kotlin/JVM 端的思路截然不同,却能够通过 Kotlin/JS 做一个自然的桥接。
四、从教学和实践角度看这个小案例的价值
如果把“节奏点击训练小游戏”放到整个 Kotlin Multiplatform + OpenHarmony 的学习路径中,它有几个非常实际的价值:
- 算法足够简单,但不无聊。很多初学者不喜欢只做加减乘除或斐波那契数列,这个案例利用日常生活中很容易理解的“打节奏”场景,将平均数、标准差、偏差等概念自然引入,让人一眼就能读懂结果的含义。
- 跨语言调用链路完整。从 Kotlin 源码到 JS 模块,再到 ArkTS 页面,读者能够真实看到一次跨栈调用的全链路:编译、导出、拷贝、导入、调用和展示,每一步都和实际 OpenHarmony 项目的工程实践高度贴合。
- 对错误输入有明确反馈。在文章开头的 Kotlin 代码中,我们专门处理了空输入、非法数字等情况,并返回详细的中文错误提示信息。结合 ArkTS 层的
try/catch,读者可以学习如何构建一个既不容易崩溃又能清晰提示用户的稳健 API。 - 利于扩展成更多小游戏或工具。当前案例以 400ms 为目标节奏,如果将来想扩展成节拍器训练、鼓点练习或者音乐教学辅助工具,只需要在 Kotlin 侧替换少量逻辑即可。同时,由于
@JsExport的存在,所有新功能在 JS/ArkTS 中都能立即复用。
从作者自己的实践体验来看,在写这类 Demo 时,最有成就感的瞬间往往不是算法本身跑通,而是看到同一段 Kotlin 代码可以同时在多端环境中“发光”:编译成 JS 后既能在 Node.js 控制台中输出结果,又能通过 ArkTS 页面给终端用户展示一块精致的 UI,这种“一码多端”的体验会让人对 Kotlin Multiplatform 的价值有更直观的感受。
五、构建与集成小结
在工程层面,将 Kotlin/JS 输出集成进 OpenHarmony 项目时,可以遵循下面的套路:
- Kotlin 工程配置 中启用 JS(IR)、
useEsModules()和generateTypeScriptDefinitions(),确保能够生成hellokjs.mjs与hellokjs.d.ts。 - 使用脚本(如
build-and-copy.ps1)统一完成编译与文件复制,把生成的.mjs重命名为.js并放到 ArkTS 可以import的pages目录中。 - 在 ArkTS 页面中通过
import { rhythmTapTrainer } from './hellokjs';直接调用,让 UI 只关注输入/输出,不关心背后的实现细节。 - 把这一整套流程整理成文档或教学案例,方便以后快速复制出更多类似的小工具或小游戏,而不用每次都从零开始搭建环境。
通过这篇文章构建的“节奏点击训练小游戏”,你不仅得到一个可以在 OpenHarmony 设备上直接试玩的 Demo,更重要的是掌握了一条打通 Kotlin 算法实现与 ArkTS 前端展示的通路。之后无论是继续扩展更多统计类、游戏类还是数据分析类的小功能,都可以沿着这条通路快速迭代,让 Kotlin/JS 和 ArkTS 在同一个项目里真正发挥各自的优势。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)