基于 Rokid AR 眼镜的聚会游戏助手开发实战
基于 Rokid AR 眼镜的聚会游戏助手开发实战。项目利用 Kotlin 语言结合 CXR-M SDK,通过蓝牙连接实现眼镜与手机通信。核心功能包括题目私密显示、倒计时同步及 TTS 语音播报。解决了蓝牙权限动态申请、SDK 连接回调处理及设备查找等技术难点。架构上采用 SDK 封装层解耦业务逻辑,支持真心话大冒险等多种游戏模式,提升聚会互动体验。

基于 Rokid AR 眼镜的聚会游戏助手开发实战。项目利用 Kotlin 语言结合 CXR-M SDK,通过蓝牙连接实现眼镜与手机通信。核心功能包括题目私密显示、倒计时同步及 TTS 语音播报。解决了蓝牙权限动态申请、SDK 连接回调处理及设备查找等技术难点。架构上采用 SDK 封装层解耦业务逻辑,支持真心话大冒险等多种游戏模式,提升聚会互动体验。

传统聚会游戏组织存在信息泄露与节奏失控问题。组织者需频繁查看手机题目,易被旁观者看到,且难以掌控游戏氛围。AR 眼镜方案可实现题目私密显示,让组织者回归参与者角色。
Rokid CXR-M SDK 提供了「提词器场景」能力,可将文字内容推送到眼镜屏幕显示,配合 TTS(语音合成)实现提示播报。
核心原则是保持简单,应用包含三个核心类:
com.rokid.game/
├── MainActivity.kt # 主界面,处理交互逻辑
├── data/
│ └── GameData.kt # 数据模型和预设题目
└── sdk/
└── RokidGlassesManager.kt # SDK 封装层
将 SDK 封装单独放一层,使业务代码与 SDK 实现解耦,便于后续升级或替换方案。
在 settings.gradle.kts 中配置仓库:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://s01.oss.sonatype.org/content/repositories/releases/") }
maven { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") }
}
}
在 app/build.gradle.kts 中添加依赖:
dependencies {
implementation("com.rokid.cxr:client-m:1.0.1-20250812.080117-2")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
}
注意:CXR-M SDK 需要 Android API 28+,记得在 defaultConfig 中设置 minSdk = 28。
眼镜通过蓝牙连接,需在 AndroidManifest.xml 中声明权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Android 12+ 需要动态申请 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT 权限:
// MainActivity.kt
private fun checkPermissions() {
val perms = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
perms.add(Manifest.permission.BLUETOOTH_SCAN)
perms.add(Manifest.permission.BLUETOOTH_CONNECT)
}
val notGranted = perms.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }
if (notGranted.isNotEmpty()) {
ActivityCompat.requestPermissions(this, notGranted.toTypedArray(), 100)
}
}
支持三种经典聚会游戏:真心话大冒险、你比我猜、我是谁。
// GameData.kt
enum class GameType(val displayName: String) {
TRUTH_OR_DARE("真心话大冒险"),
CHARADES("你比我猜"),
WHO_AM_I("我是谁"),
COUNTDOWN("数数字")
}
data class GameQuestion(
val id: Int,
val gameType: GameType,
val content: String,
val answer: String? = null,
val isTruth: Boolean = true
)
预设数据硬编码在单例中,随机选题避免重复:
object GameData {
val questions: List<GameQuestion> = listOf(
GameQuestion(1, GameType.TRUTH_OR_DARE, "你最近一次哭是什么时候?", null, true),
GameQuestion(6, GameType.TRUTH_OR_DARE, "给通讯录第 5 个人打电话说新年快乐", null, false),
// ... 更多题目
)
fun getRandom(type: GameType, used: Set<Int>): GameQuestion? {
val available = getByType(type).filter { it.id !in used }
return if (available.isNotEmpty()) available.random()
else getByType(type).random()
}
}
创建 RokidGlassesManager 单例封装所有与眼镜的交互。
// RokidGlassesManager.kt
object RokidGlassesManager {
private val cxrApi: CxrApi by lazy { CxrApi.getInstance() }
private var connectionCallback: ConnectionCallback? = null
interface ConnectionCallback {
fun onConnecting()
fun onConnected()
fun onDisconnected()
fun onFailed(errorMsg: String)
}
val isConnected: Boolean get() = cxrApi.isBluetoothConnected
}
连接流程需初始化蓝牙、获取连接信息、再建立连接。注意 connectBluetooth 回调中 onConnectionInfo 方法必须实现。
查找眼镜设备遍历已配对蓝牙设备:
fun findRokidGlasses(adapter: BluetoothAdapter): BluetoothDevice? {
if (ActivityCompat.checkSelfPermission(adapter.javaClass, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED)
return null
return adapter.bondedDevices.find { it.name?.contains("Rokid", ignoreCase = true) == true }
}
发送内容到眼镜使用 sendStream 方法:
fun sendGameContent(text: String, callback: SendCallback? = null): Boolean {
if (!isConnected) {
callback?.onFailed("眼镜未连接")
return false
}
cxrApi.controlScene(ValueUtil.CxrSceneType.WORD_TIPS, true, null)
val status = cxrApi.sendStream(
ValueUtil.CxrStreamType.WORD_TIPS,
text.toByteArray(Charsets.UTF_8),
"game.txt",
object : SendStatusCallback {
override fun onSendSucceed() { callback?.onSuccess() }
override fun onSendFailed(e: ValueUtil.CxrSendErrorCode?) { callback?.onFailed(e?.name ?: "发送失败") }
}
)
return status == ValueUtil.CxrStatus.REQUEST_SUCCEED
}
TTS 语音播报功能:
fun sendTts(text: String): Boolean {
if (!isConnected) return false
return if (cxrApi.sendTtsContent(text) == ValueUtil.CxrStatus.REQUEST_SUCCEED) {
cxrApi.notifyTtsAudioFinished()
true
} else false
}
MainActivity.kt 负责用户交互。倒计时功能使用 CountDownTimer,最后 10 秒同步更新眼镜显示:
private fun startCountdown() {
countdownTimer?.cancel()
binding.tvCountdown.text = "60"
countdownTimer = object : CountDownTimer(60000, 1000) {
override fun onTick(millis: Long) {
binding.tvCountdown.text = "${millis / 1000}"
if (millis / 1000 <= 10) {
sendToGlasses("⏱ 倒计时:${millis / 1000}秒")
}
}
override fun onFinish() {
binding.tvCountdown.text = "0"
RokidGlassesManager.sendTts("时间到!")
}
}.start()
}
发送到眼镜的内容格式需保证清晰易读:
private fun buildDisplayText(): String = buildString {
val q = currentQuestion ?: return ""
appendLine("🎮 ${currentType.displayName}")
appendLine()
if (currentType == GameType.TRUTH_OR_DARE) {
appendLine("────── ${if (q.isTruth) "真心话" else "大冒险"} ──────")
} else {
appendLine("────── 题目 ──────")
}
appendLine()
appendLine(q.content)
appendLine()
appendLine("👆 手机点击下一题")
}
| 功能 | 说明 |
|---|---|
| 四种游戏 | 真心话大冒险/你比我猜/我是谁/数字 |
| 随机出题 | 自动避免重复 |
| 60 秒倒计时 | 你比我猜模式专用,最后 10 秒同步眼镜 |
| 眼镜同步 | 题目实时推送到眼镜显示 |
| TTS 语音 | 倒计时结束播报'时间到' |
| 蓝牙连接 | 自动查找已配对的 Rokid 设备 |
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)。当前版本不足:题目数量有限且硬编码;不支持自定义添加题目;无积分排行榜;仅支持单机。
后续改进方向:云端题库、自定义题目、多人局域网同步、增加狼人杀等游戏。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online