文本转语音是一种辅助技术,使用人工语音将文本字符串转换为语音声音。除了提高视觉、行动或认知障碍玩家的体验可及性之外,TTS 还允许您动态生成语音,这样就无需为所有可能的叙述场景录制音频。
使用 姜饼屋 - 开始 .rbxl 文件作为起点,并以 姜饼屋 - 文本转语音 作为参考,本教程将向您展示如何为您的体验添加基本和上下文感知的 TTS 音频,包括以下指导:
- 针对不会更改的常见游戏场景触发 TTS,例如 UI 交互和教程。
- 配置 TTS 以适应玩家的动作、环境状态或灵活目标。
如果您在过程中遇到任何问题,可以使用 姜饼屋 - 文本转语音 作为参考来比较您的进度。
音频对象
要创建 TTS 音频,了解您在本教程中将要使用的音频对象非常重要。 TTS 主要有五种类型的音频对象:
- AudioTextToSpeech 对象将文本字符串转换为语音声音。
- AudioEmitter 对象是一个 虚拟扬声器,将音频发射到 3D 环境中。
- AudioListener 对象是一个 虚拟麦克风,可以从 3D 环境中拾取音频。
- AudioDeviceOutput 对象是现实世界中的 物理硬件设备,例如扬声器或耳机。
- Wires 将音频流从一个对象传送到另一个对象。
所有这些音频对象一起工作,以根据玩家的动作发出 TTS 声音。让我们来看一个例子,假设播放体验的玩家正在佩戴耳机并使用笔记本电脑:
- AudioTextToSpeech 在玩家触摸接近非可玩角色 (NPC) 的一个部分时加载并将文本转换为音频。
- AudioEmitter 从 NPC 向 3D 环境发出 TTS 音频流。
- 角色的子项 AudioListener 对象在 3D 环境中收听此声音,并将其反馈到他们的耳机。
- AudioDeviceOutput 对象将声音从 AudioListener 传送到玩家的物理扬声器,或在这种情况下,传送到他们的耳机。
以下部分将深入探讨这些对象,并在您学习如何播放基本和上下文感知的音频时引用这些对象。当您回顾这些对象与即将采用的技术结合时,您可以更准确地预测如何从体验中捕获和反馈声音给玩家。
基本 TTS
基本 TTS 是最常见的文本转语音形式,人工语音无论玩家或环境上下文都会朗读文本字符串。这意味着每当玩家触发 TTS 音频时,单词和人工语音朗读的方式始终保持一致,无论玩家的状态、他们的动作或环境状态如何。
这种形式的 TTS 在大多数游戏场景中都很有用,例如玩家与 UI 菜单、教程或常规 NPC 交互(如商人提供商品或敌人的叫喊)。Roblox 提供以下类型的语音供您在任何这些交互中进行尝试:
| VoiceID | 语音描述 | 音频示例 |
|---|---|---|
| 1 | 英国男性 | |
| 2 | 英国女性 | |
| 3 | 美国男性 #1 | |
| 4 | 美国女性 #1 | |
| 5 | 美国男性 #2 | |
| 6 | 美国女性 #2 | |
| 7 | 澳大利亚男性 | |
| 8 | 澳大利亚女性 | |
| 9 | 复古声音 #1 | |
| 10 | 复古声音 #2 | |
| 11 | 主持人声音 | |
| 101 | 西班牙男性 | |
| 102 | 西班牙女性 | |
| 201 | 德国男性 | |
| 202 | 德国女性 | |
| 301 | 意大利男性 | |
| 302 | 意大利女性 | |
| 401 | 法国男性 | |
| 402 | 法国女性 |
要在示例 姜饼屋 - 文本转语音 场所文件中重现基本的 3D TTS 音频:
- 启用附加到玩家角色的默认监听器。
- 在 资源管理器 窗口中,选择 SoundService。
- 在 属性 窗口中,将 DefaultListenerLocation 设置为 Character。当您运行体验时,引擎会自动:
- 在每个玩家角色的 Humanoid.RootPart 下创建一个 AudioListener,以便您可以根据经验中声音源的位置和音量在真实世界的扬声器中听到声音变化。
- 在 SoundService 下创建一个 AudioDeviceOutput。
- 在 资源管理器 窗口中,导航到 Workspace ⟩ DialogueVolume,然后:
- 插入一个 AudioTextToSpeech 对象,以在雪人周围创建一个音频生成器。
- 插入一个 AudioEmitter 对象,从 DialogueVolume 发出位置流。
- 插入一个 Wire 对象,将音频流从音频生成器传送到发射器。
- 选择 AudioTextToSpeech 对象,然后在 属性 窗口中:
- 将 Text 设置为 "收集每一个糖果才能打开我的家!"
- 将 VoiceId 设置为 2,以使人工语音模仿一位英国女性。
- 将 Volume 设置为 3,以在体验中播放音频时的高音量,这样您可以听到 TTS 声音超过其他音源。
- 选择 Wire,然后在 属性 窗口中:
- 将 SourceInstance 设置为新的 AudioTextToSpeech,以指定您希望该电线从此特定的音频生成器传送音频。
- 将 TargetInstance 设置为新的 AudioEmitter,以指定您希望电线将音频传送到此特定的音频发射器。
- 返回 资源管理器 窗口,导航到 StarterPlayer ⟩ StarterCharacterScripts,然后插入一个 LocalScript,将其重命名为 PlayBasicTTSAudioWhenInVolume,并将以下代码粘贴到本地脚本中:
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local humanoid = script.Parent:WaitForChild("Humanoid")
local volumeDetector = Workspace.DialogueVolume
local trigger = humanoid:WaitForChild("Animator")
local debounce = false
local localPlayer = Players.LocalPlayer
volumeDetector.Touched:Connect(function(hit)
if debounce then
return
end
local hitCharacter = hit:FindFirstAncestorWhichIsA("Model")
local hitPlayer = Players:GetPlayerFromCharacter(hitCharacter)
if hitPlayer ~= localPlayer then
return
end
debounce = true
local audioTextToSpeech = Workspace.DialogueVolume.AudioTextToSpeech
audioTextToSpeech:Play()
audioTextToSpeech.Ended:Wait()
debounce = false
end)
此脚本首先获取 Workspace 和 Players 服务,以便可以引用其子元素和功能。对于每个加载或重新生成进入体验的玩家角色,该脚本会等待:
- 在工作区中名为 DialogueVolume 的体积对象。
当任何物体与体积发生碰撞时,Touched 事件处理函数获知第一个祖先是 Model,如果与该体积碰撞的 BasePart 是角色模型的子对象,则该物体应该是该角色。如果是,则该函数:
- 将 debounce 设置为 true。
- 播放并等待 TTS 音频结束。
- 将 debounce 重置为 false。
将 debounce 从 false 更改为 true,再返回为 false 来确保在基本 TTS 音频播放完成后是一种防抖模式,可以防止音频在玩家与体积不断碰撞时重复触发。有关此防抖模式的更多信息,请参见 防抖 - 检测碰撞。
进行游戏测试,聆听您的玩家角色在接触雪人周围的体积时的指导角色对话。
您可以通过修改 Text、VoiceID、Pitch 和 Speed 属性为新的值来进一步尝试此音频。生成的语音变得完全不同,而无需为每种场景录制并上传新的音频文件。
上下文感知 TTS
上下文感知 TTS 是一种更高级的文本转语音形式,其中人工语音与玩家、环境状态或游戏状态相关地朗读文本字符串。这意味着每当玩家触发 TTS 音频时,单词和人工语音朗读的方式会相应地进行调整。
这种形式的 TTS 对于不断变化的游戏场景非常有用,例如方向音频提示、目标状态或独特的 NPC 交互。因此,由于上下文感知的 TTS 需要进行变换以确保准确性,您必须配置游戏元素,以便在玩家在环境中导航并完成游戏目标时跟踪它们的状态。
虽然有很多方法可以完成这项任务,此示例使用自定义属性跟踪玩家必须收集的每个糖果的 颜色 和 位置 以进入姜饼屋。有关属性的更多信息,请查看 属性和属性。



要在示例 姜饼屋 - 文本转语音 场所文件中重现上下文感知的 3D TTS 音频:
在 资源管理器 窗口中,导航到 Workspace ⟩ GumDrops。
配置三个自定义属性以跟踪黄色糖果。
- 选择黄色糖果,然后在 属性 窗口中,导航到 属性 部分,然后点击加号图标。弹出对话框将显示。
- 在 名称 字段中输入 ColorDescription。
- 在 类型 下拉菜单中,选择 string。
- 单击 保存 按钮。
- 将新的 ColorDescription 属性设置为 黄色。
- 使用此过程,创建两个更多属性,使用以下值。
名称 类型 值 HintOrder number 0 LocationDescription string 在瀑布旁边 配置三个自定义属性以跟踪绿色糖果。
- 在 资源管理器 窗口中,选择绿色糖果。
- 在 属性 窗口中,创建三个属性,使用以下值。
名称 类型 值 ColorDescription string 绿色 HintOrder number 1 LocationDescription string 在边缘上 配置三个自定义属性来跟踪红色糖果。
- 在 资源管理器 窗口中,选择红色糖果。
- 在 属性 窗口中,创建三个属性使用以下值。
名称 类型 值 ColorDescription string 红色 HintOrder number 2 LocationDescription string 在栅栏后面 在 资源管理器 窗口中,导航到 Workspace ⟩ HintVolume,然后:
- 插入一个 AudioTextToSpeech 对象,以为围绕驯鹿的体积创建一个音频生成器。
- 插入一个 AudioEmitter 对象,从 HintVolume 发出位置流。
- 插入一个 Wire 对象,将音频流从音频生成器传送到发射器。
选择 Wire,然后在 属性 窗口中:
- 将 SourceInstance 设置为新的 AudioTextToSpeech,以指定您希望此电线从此特定音频生成器传送音频。
- 将 TargetInstance 设置为新的 AudioEmitter,以指定您希望电线将音频传送至此特定的音频发射器。
返回到 资源管理器 窗口,导航到 StarterPlayer ⟩ StarterCharacterScripts,然后插入一个 LocalScript,将其重命名为 PlayContextTTSAudioWhenInVolume,并将以下代码粘贴到本地脚本中:
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")
local humanoid = script.Parent:WaitForChild("Humanoid")
local volumeDetector = Workspace.HintVolume
local trigger = humanoid:WaitForChild("Animator")
local debounce = false
local localPlayer = Players.LocalPlayer
function getRemainingGumdrops(): {Part}
local gumdropsFolder = Workspace.Gumdrops
local gumdrops = gumdropsFolder:GetChildren()
local remainingGumdrops = {};
for _, gumdrop in gumdrops do
if gumdrop:GetAttribute("Active") then
remainingGumdrops[#remainingGumdrops + 1] = gumdrop
end
end
table.sort(remainingGumdrops, function(a, b)
return a:GetAttribute("HintOrder") < b:GetAttribute("HintOrder")
end)
return remainingGumdrops
end
function getReindeerHint(remainingGumdrops: {Part}): string
local remainingGumdrops = getRemainingGumdrops()
if (#remainingGumdrops == 0) then
return "没有剩余的糖果。检查屋子里。"
end
local nextGumdrop = remainingGumdrops[1]
local colorDescription = nextGumdrop:GetAttribute("ColorDescription")
local locationDescription = nextGumdrop:GetAttribute("LocationDescription")
local message = #remainingGumdrops > 1
and "剩余 " .. #remainingGumdrops .. " 颗糖果。寻找 " .. colorDescription .. " 颗在 " .. locationDescription .. "。"
or "剩下的糖果只剩一颗。它是 " .. colorDescription .. ",在 " .. locationDescription .. "。"
return message
end
volumeDetector.Touched:Connect(function(hit)
if debounce then
return
end
local hitCharacter = hit:FindFirstAncestorWhichIsA("Model")
local hitPlayer = Players:GetPlayerFromCharacter(hitCharacter)
if hitPlayer ~= localPlayer then
return
end
print("玩家触碰了体积。")
debounce = true
local remainingGumdrops = getRemainingGumdrops()
local message = getReindeerHint(remainingGumdrops)
local audioTextToSpeech = Workspace.HintVolume.AudioTextToSpeech
audioTextToSpeech.Text = message
audioTextToSpeech:Play()
audioTextToSpeech.Ended:Wait()
debounce = false
end)
此脚本首先获取 Workspace 和 Players 服务,以便可以引用其子元素和功能。每当玩家角色加载或重新生成回到体验中,该脚本都会等待:
- 在工作区中名为 HintVolume 的体积对象。
脚本的 getRemainingGumdrops() 函数返回 Gumdrops 文件夹中糖果 Part 对象的列表,并过滤掉玩家已收集的任何部分。剩余的糖果将根据每个糖果的 HintOrder 属性以特定顺序进行排序。
脚本的 getReindeerHint(remainingGumdrops) 函数从 getRemainingGumdrops() 返回该列表,并返回一个字符串消息,描述玩家可以找到的任何剩余糖果的位置,包括下一个玩家需要收集的糖果的颜色和位置描述。然而,如果玩家已经收集了环境中的所有糖果,此字符串消息便描述收集完所有糖果后的去处。
一旦任何物体与体积发生碰撞,Touched 事件处理函数会获知第一个祖先是 Model,如果与该体积碰撞的 BasePart 是角色模型的子对象,则该物体应该是该角色。如果是,则该函数:
- 将 debounce 设置为 true。
- 获取剩余糖果的列表和描述玩家需要找到的下一个糖果的字符串消息。
- 使用 AudioTextToSpeech 对象将字符串转换为音频,播放音频,并等待音频结束。
- 将 debounce 重置为 false。
将 debounce 从 false 更改为 true,再返回为 false 来确保在上下文感知的 TTS 音频播放完成后是一种防抖模式,可以防止音频在玩家与体积不断碰撞时重复触发。有关此防抖模式的更多信息,请参见 防抖 - 检测碰撞。
进行游戏测试,以在玩家角色接触驯鹿周围的体积时听到上下文提示。 TTS 音频会根据玩家角色在环境中已找到的糖果的数量和颜色进行变化。