This commit is contained in:
gcw_4spBpAfv
2026-05-15 16:33:22 +08:00
parent 4e33063a98
commit b29dfd8b47
6 changed files with 95 additions and 18 deletions

View File

@@ -303,4 +303,65 @@ https://tianchi.aliyun.com/dataset/93864
- 将 tuanjieLibrary/build.gradle 替换为完整的 Android library 模块构建文件apply plugin: 'com.android.library' - 将 tuanjieLibrary/build.gradle 替换为完整的 Android library 模块构建文件apply plugin: 'com.android.library'
参考 launcher/build.gradle 的配置compileSdkVersion、ndkVersion、ndkPath、aaptOptions、buildTypes 等), 参考 launcher/build.gradle 的配置compileSdkVersion、ndkVersion、ndkPath、aaptOptions、buildTypes 等),
并在 dependencies 中添加 implementation files('libs/unity-classes.jar')。 并在 dependencies 中添加 implementation files('libs/unity-classes.jar')。
- 注意:每次从 Tuanjie 重新导出后都需要重新替换该文件。 - 注意:每次从 Tuanjie 重新导出后都需要重新替换该文件。
14. 从 Unity 导出 Android 项目IL2CPP推荐步骤
下面按 Unity 2021/2022+ 通用流程写(菜单名可能略有差异,但逻辑一致)。
1. 切到 Android 平台
File → Build Settings…
左侧选中 Android
若未切换过,点 Switch Platform等切换完成
2. Player SettingsAndroid
Build Settings 窗口里点 Player Settings…或 Edit → Project Settings → Player
选中 Android 图标(或 Android 标签页)
在 Other Settings 里检查并设置:
Scripting Backend选 IL2CPP
Target Architectures勾选 ARM64你现在 app 的 abiFilters 也是 arm64-v8a要一致
Minimum API Level与你们 Android 工程要求一致(你工程 minSdk 22Unity 不要设得更高除非你有意为之)
可选但常见Publishing Settings / Keystore导出后若要直接装真机后面 Build 时可用 debug发版再配正式签名。
3. 选择导出方式
在 Build Settings
Export Project ✅ 勾选(你们要集成到现有 Android Studio 工程,必须勾)
Development Build调试可开发版关
4. Export 输出目录
点 Export不要只点“生成文件夹”就关掉选一个空目录或新版本目录例如
D:\unity_export\MyProject_Android_IL2CPP_20260424\
等 Unity 完整跑完(控制台无失败)。
5. 在导出结果里确认 libil2cpp.so
在导出根目录里找 unityLibrary或你们模块名
unityLibrary\src\main\jniLibs\arm64-v8a\libil2cpp.so
同目录通常还有 libmain.so、libunity.so 等
若这里仍然没有,再回到 Player Settings → Android → Scripting Backend 逐项核对(尤其是是否被 Override 成 Mono
6. 集成到你们 digital_person 工程
原则:同一轮 Export 的 jniLibs/arm64-v8a 整包拷贝到你们 tuanjieLibrary/src/main/jniLibs/arm64-v8a/(或你们实际使用的 Unity module不要“只替换较新文件”也不要混两次导出的 so。
15. Unity导出安卓工程目录之后找不到 libil2cpp.so可以通过先不export先编译出apk得到这个so然后复制到对应的目录下
power shell 命令
# 源文件路径
$source = "C:\Users\42087\My project\Library\Bee\Android\Prj\IL2CPP\Gradle\tuanjieLibrary\build\intermediates\stripped_native_libs\release\out\lib\arm64-v8a\libil2cpp.so"
# 目标路径(你的导出工程)
$targetDir = "D:\code\digital_person\tuanjieLibrary\src\main\jniLibs\arm64-v8a"
# 创建目标目录(如果不存在)
New-Item -ItemType Directory -Force -Path $targetDir
# 复制文件
Copy-Item -Path $source -Destination $targetDir -Force
# 验证复制成功
Get-ChildItem -Path $targetDir\libil2cpp.so | Select-Object Name, Length
16. 有些Unity导出的问题可以参考https://blog.csdn.net/weixin_45774916/article/details/144171875

View File

@@ -503,6 +503,10 @@ class UnityDigitalPersonActivity : UnityPlayerActivity(), LifecycleOwner {
val filteredText = ttsController.speakLlmResponse(finalText) val filteredText = ttsController.speakLlmResponse(finalText)
Log.d("UnityDigitalPerson", "LOCAL filtered: ${filteredText?.take(60)}") Log.d("UnityDigitalPerson", "LOCAL filtered: ${filteredText?.take(60)}")
if (filteredText != null) appendChat("助手: $filteredText") if (filteredText != null) appendChat("助手: $filteredText")
// 本地 LLM 回复也要进入互动状态机:否则不会进入 WAITING_REPLY / PROACTIVE 的计时流程
val spoken = filteredText ?: finalText.trim()
maybeShowMatchedRefImage(spoken)
interactionCoordinator.onCloudFinalResponse(spoken)
} }
localThoughtSilentMode = false localThoughtSilentMode = false
} }

View File

@@ -9,7 +9,7 @@ object AppConfig {
const val SAMPLE_RATE = 16000 const val SAMPLE_RATE = 16000
const val WINDOW_SIZE = 512 const val WINDOW_SIZE = 512
const val SHOW_DEBUG_TEXT = true const val SHOW_DEBUG_TEXT = false
const val USE_HOLD_TO_SPEAK = true // true: 按住说话, false: 传统按钮 const val USE_HOLD_TO_SPEAK = true // true: 按住说话, false: 传统按钮
object Tts { object Tts {

View File

@@ -148,12 +148,13 @@ abstract class BaseDigitalPersonCoordinator(
* (i.e. after a cloud LLM response), NOT after greeting / farewell / proactive TTS. * (i.e. after a cloud LLM response), NOT after greeting / farewell / proactive TTS.
*/ */
fun onTtsPlaybackCompleted() { fun onTtsPlaybackCompleted() {
// Let the controller advance its own timers (greeting/proactive/dlg all count as assistant speaking).
controller.onAssistantTtsPlaybackCompleted()
if (pendingDialogueFinish) { if (pendingDialogueFinish) {
pendingDialogueFinish = false pendingDialogueFinish = false
controller.onDialogueResponseFinished() controller.onDialogueResponseFinished()
} }
// Let the controller advance its own timers after state transitions above.
// (greeting/proactive/dlg all count as assistant speaking)
controller.onAssistantTtsPlaybackCompleted()
} }
fun isLlmInFlight(): Boolean = llmInFlight fun isLlmInFlight(): Boolean = llmInFlight

View File

@@ -7,6 +7,27 @@
android:background="@android:color/transparent" android:background="@android:color/transparent"
tools:context="com.digitalperson.UnityDigitalPersonActivity"> tools:context="com.digitalperson.UnityDigitalPersonActivity">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_mid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.5" />
<ImageView
android:id="@+id/ref_match_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="0dp"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintTop_toTopOf="@+id/guideline_mid"
app:layout_constraintBottom_toTopOf="@+id/button_row"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
<ScrollView <ScrollView
android:id="@+id/scroll_view" android:id="@+id/scroll_view"
android:layout_width="0dp" android:layout_width="0dp"
@@ -14,10 +35,12 @@
android:layout_margin="12dp" android:layout_margin="12dp"
android:background="#55000000" android:background="#55000000"
android:fillViewport="true" android:fillViewport="true"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/button_row" app:layout_constraintBottom_toTopOf="@+id/button_row"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<TextView <TextView
android:id="@+id/my_text" android:id="@+id/my_text"
@@ -31,18 +54,6 @@
android:textIsSelectable="true" /> android:textIsSelectable="true" />
</ScrollView> </ScrollView>
<ImageView
android:id="@+id/ref_match_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_margin="12dp"
android:background="#66000000"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/button_row"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<LinearLayout <LinearLayout
android:id="@+id/button_row" android:id="@+id/button_row"
android:layout_width="0dp" android:layout_width="0dp"

View File

@@ -3,7 +3,7 @@
<string name="start">开始</string> <string name="start">开始</string>
<string name="stop">结束</string> <string name="stop">结束</string>
<string name="hint">点击“开始”说话;识别后会请求大模型并用 TTS 播放回复。</string> <string name="hint">点击“开始”说话;识别后会请求大模型并用 TTS 播放回复。</string>
<string name="system_prompt">你是一个特殊学校一年级的数字人老师,你的名字叫,小鱼老师,你的任务是教这些特殊学校的学生一些基础的生活常识,如生活语文,生活数学,生活适应等课程。和这些小学生说话要有耐心,一定要讲明白,尽量用简短的语句、活泼的语气来回复。你可以和他们日常对话和《教材》相关的话题。在生成回复后,请你先检查一下内容是否符合我们约定的主题。请使用口语对话的形式跟学生聊天。在每次回复的最前面,用方括号标注你的心情,格式为[中性、悲伤、高兴、生气、恐惧、撒娇、震惊、厌恶],例如:[高兴]同学你好呀!请问有什么问题吗?</string> <string name="system_prompt">你是一个特殊学校一年级的数字人老师,你的名字叫,小鱼老师,你是女性,你的任务是教这些特殊学校的学生一些基础的生活常识,如生活语文,生活数学,生活适应等课程。和这些小学生说话要有耐心,一定要讲明白,尽量用简短的语句、活泼的语气来回复。你可以和他们日常对话和《教材》相关的话题。在生成回复后,请你先检查一下内容是否符合我们约定的主题。请使用口语对话的形式跟学生聊天。在每次回复的最前面,用方括号标注你的心情,格式为[中性、悲伤、高兴、生气、恐惧、撒娇、震惊、厌恶],例如:[高兴]同学你好呀!请问有什么问题吗?</string>
<!-- Unity 的字符串 <!-- Unity 的字符串
有人建议是: 将Launcher/src/main/res/values/strings.xml 文件拷贝进unityLibrary/src/main/res/values/里这样做的确就避免了那个变量值为0 的问题。 有人建议是: 将Launcher/src/main/res/values/strings.xml 文件拷贝进unityLibrary/src/main/res/values/里这样做的确就避免了那个变量值为0 的问题。