文本转语音是一种辅助技术,它使用人工语音将文本字符串转换为语音声音。除了改善视觉、行动或认知障碍玩家的游戏可访问性外,TTS 还允许您动态生成语音,因此您不必为所有可能的叙事场景预先录制音频。
使用 姜饼屋 - 开始 .rbxl 文件作为起点,并以 姜饼屋 - 文本转语音 作为参考,本教程向您展示如何将基本和上下文感知的 TTS 音频添加到您的游戏中,包括以下指导:
- 触发 TTS 以应对永远不会改变的常见游戏场景,例如 UI 交互和教程。
- 配置 TTS 以适应玩家的动作、环境状态或灵活目标。
如果在过程中遇到任何问题,您可以使用 姜饼屋 - 文本转语音 作为参考来比较您的进度。
音频对象
要创建 TTS 音频,了解您将在本教程中使用的音频对象非常重要。TTS 有五种主要类型的音频对象:
- AudioTextToSpeech 对象将文本字符串转换为语音声音。
- AudioEmitter 对象是一个 虚拟扬声器,将音频发射到 3D 环境中。
- AudioListener 对象是一个 虚拟麦克风,从 3D 环境中拾取音频。
- AudioDeviceOutput 对象是现实世界中的 物理硬件设备,例如扬声器或耳机。
- Wires 将音频流从一个对象传输到另一个对象。
所有这些音频对象共同工作,以响应玩家的动作发出 TTS 声音。让我们通过一个示例来看看这在 3D 音频中的实际工作原理,假设玩家在使用笔记本电脑玩游戏时佩戴耳机:
- AudioTextToSpeech 在玩家触摸靠近非可玩角色 (NPC) 的部件时加载并转换文本为音频。
- AudioEmitter 将 TTS 音频流从 NPC 发射到 3D 环境中。
- 角色的子 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 属性设置为 yellow。
- 使用此过程,创建两个更多属性,使用以下值。
名称 类型 值 HintOrder number 0 LocationDescription string 在瀑布旁 配置三个自定义属性以跟踪绿色糖果。
- 在 资源管理器 窗口中,选择绿色糖果。
- 在 属性 窗口中,使用以下值创建三个属性。
名称 类型 值 ColorDescription string green HintOrder number 1 LocationDescription string 在边缘上 配置三个自定义属性以跟踪红色糖果。
- 在 资源管理器 窗口中,选择红色糖果。
- 在 属性 窗口中,使用以下值创建三个属性。
名称 类型 值 ColorDescription string red 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 音频根据玩家角色在环境中已经找到的糖果的数量和颜色而变化。