添加 3D 音频

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

3D音频 是从3D空间特定位置发出的环向声音,根据音频发射者和接收者之间的距离和方向,音量会增加或减少。这意味着当收听者或发射者移动到环境周围时,玩家可以动态听到来自不同方向和音量级别的音频。

使用 姜饼屋 - 开始.rbxl 文件作为起点,并使用 姜饼屋 - 完成音频 作为参考,本教程向您展示如何将循环和一次性 3D 音频添加到您的体验中,包括指导:

  • 循环播放玩家连接到服务器时播放的环境声音。
  • 触发音频以向玩家通知关键情节事件,这些事件对他们的游戏有重要意义。
  • 激活音频,在玩家与 3D 对象互动时提供听觉反馈。
  • 播放角色声音片段,吸引并引导玩家关注环境中的特定点。

如果在任何时候你被困在过程中,你可以使用 姜饼屋 - 完成音频 作为参考来比较你的进度。

音频对象

要创建方向性音频,了解您在本教程期间将使用的音频对象非常重要。有六种主要类型的音频对象:

  • AudioPlayer 对象加载并播放 音频文件
  • AudioEmitter 对象是一个 虚拟扬声器 ,可以将音频发射到 3D 环境。
  • AudioListener 对象是一个 虚拟麦克风 ,可以从 3D 环境中拾取音频。
  • AudioDeviceOutput 对象是现实世界中的 物理硬件设备 ,例如扬声器或耳机。
  • AudioDeviceInput 对象是现实世界内的 物理麦克风
  • Wires 从一个对象传送音频流到另一个对象。

这些音频对象都会一起发出声音,就像它们的现实世界对应一样。让我们来看看这在实践中如何使用一个例子来显示玩家在玩体验时戴着耳机的情况:

  • The AudioPlayer 加载 1516791621 音频资产ID到体验以用于雨季轨道。
  • AudioEmitter 发出一串雨道音频到 3D 环境。
  • 一个 Wire 将流从 AudioPlayer 传递到 AudioEmitter 以便流从 3D 扬声麦克风中输出。
  • 角色的子对象 AudioListener 听到了 3D 环境内的那个声音,并将其传回到他们的头戴设备。
  • AudioDeviceOutput 对象将音从 AudioListener 传送到玩家的物理扬声麦克风或在这种情况下,他们的耳机。
  • AudioDeviceInput 对象从现实世界捕获声音,然后将其反馈到体验中进行语音聊天。

体验中的对象表示
>

在现实世界中的对象表示
>

以下部分更深入地潜入这些对象,并作为你学习如何播放循环和一次射击 3D 音频的参考。当您使用即将推出的技术审查这些对象时,您可以更准确地预测如何从体验中捕获和传送音效到玩家,并相反。

循环音频

循环 3D 音频 ,或像玩家连接到服务器一样无缝重复的方向性音频,是一种常见的音效设计技巧,可以通过让它感觉活跃而动态来增强 3D 空间的氛围。此外,循环 3D 音频可以保持环境音源的一致性,例如电视静音或瀑布的咆哮;如果这些声音突然停止,环境将感觉不现实。

为了展示这一概念,请查看以下 3D 音频如何在大型瀑布完成后立即停止,当未循环的音频轨道完成后。虽然水听起来最初将玩家浸入户外环境,但突然的听觉变化与真实世界中瀑布的行为格格不入。

同样,样本使用这种技术为流动的巧克力瀑布,并根据玩家与音频发射器的距离调整其音量。当玩家离发射器小于 20 个单位时,发射器以全音量发出声音。随着玩家移得越来越远,溅射音频在离音频发射器 20 英尺远的地方每减少 20 分贝。这会模拟现实世界的声音,使声音在你越远离源,音量越小。

要重新创建样本 姜饼屋 - 完整音频 中的循环 3D 音频文件:

  1. 启用附属于您的玩家角色的默认收听器。

    1. 探索器 窗口中,选择 音频服务
    2. 属性 窗口中,将 默认列听位置 设置为 角色 。当您运行体验时,引擎会自动:
  2. 资源管理器 窗口中,导航到 工作区 > WaterfallAudioObject ,然后:

    1. 插入一个 音频播放器 对象来创建瀑布的音频源。
    2. 插入一个 音频发射器 对象,从 WaterfallAudioObject 发出位置流。
    3. 插入一个 电线 对象,将流从音频播放器传送到音频发射器。
  3. 选择 音频播放器 ,然后在 属性 窗口中,

    1. 资产ID 设置为 rbxassetid://1516791621 播放雨天音频轨道。
    2. 启用 循环 以便音频无缝重复。
  4. 选择 音频发射器 ,然后在 属性 窗口中将 音量减少距离 设置为{0: 1}, {20: 0.8}, {40: 0.4}, {80: 0},以便音量逐渐减少,每20格远离音频发射器。

  5. 选择 电线 ,然后在 属性 窗口中,

    1. 源实例 设置为你的新 音频播放器 以指定你希望电线从特定音频播放玩家传输音频。
    2. 目标实例 设置为你的新 音频发射器 以指定你希望电线将音频传送到水流中这个特定的音频接收器。
  6. 回到 探索器 窗口,将 脚本 插入 WaterfallAudioObject 中,重命名为 循环水流音乐 ,将其 运行上下文 属性设置为 客户端 ,然后将以下代码粘贴到脚本中:


    local audioPlayer = script.Parent
    audioPlayer:Play()

    脚本以宣言变量来代表脚本的父级 AudioPlayer 开始。然后,脚本将音频源设置为从玩家加入体验到他离开体验的那一刻播放。

  7. 播放体验,在你的虚拟形象靠近瀑布时听到循环巧克力雨声。当你旋转角色的头以看到不同的方向时,声音会根据发射器在 3D 空间中的位置动态切换到你的现实世界扬声器。

一枪音频

一次射击3D音频 ,或向导航音频,在特定时间和位置播放一次,除非玩家再次触发它,为玩家提供关于他们行动、环境和周围任何角色的信息。在体验中使用这种类型的听觉反馈是必不可少的,因为它允许玩家做出战略决定,例如避免接受敌人或捡起有用的物品。

以下部分提供了玩家需要及时、方向性反馈的常见游戏场景的实现细节,包括情景游戏事件、对象互动和不可玩角色对话。

事件反馈

当玩家触发环境内的关键情节事件,例如解锁新的游戏区域或提示敌人开火,他们必须理解自己在 3D 空间中需要集中注意力和关注的地方。如果他们没有立即的听觉反馈,他们可能会错过对他们游戏重要的信息,导致不知道去哪里或下一步该做什么。

为了展示为什么这很重要,让我们回顾从每个玩家的激光标签中播放的一发 3D 音频模板:

  • 深色爆炸声响应玩家从他们的爆炸器发射的每一次爆炸。
  • 单击和机器人哨兵声每次重新加载玩家的冲击波时播放。

这两种声音都可以提供情景意识,通知附近的所有玩家爆炸的方向,以便他们做出有针对性的决定,加入乐趣或避免潜在危险。

样本使用相同的技巧为玩家提供关于完成体验主目标的奖励情报。在他们收集三个橡皮糖之后,姜饼屋的门打开,允许玩家进入现在的内部。

因为玩家需要收集橡皮糖的特定顺序不存在,所以玩家必须意识到门打开,无论收集哪个橡皮糖最后。位置声使这成为可能,让玩家意识到他们的成功以及下一步需要去哪里,无论他们相对距离和方向与门有多远。

要重新创建样本 姜饼屋 - 完整音频 中的一次射击事件反馈 3D 音频文件:

  1. 资源管理器 窗口中,导航到 工作区 > ,然后:

    1. 插入一个 音频播放器 对象来创建音量的音源。
    2. 插入一个 音频发射器 对象,从 发出位置流。
    3. 插入一个 电线 对象,将流从音频播放器传送到音频发射器。
  2. 选择 音频播放器 ,然后在 属性 窗口中将 资产ID 设置为rbxassetid://5930776613

  3. 选择 电线 ,然后在 属性 窗口中,

    1. 源实例 设置为你的新 音频播放器 以指定你希望电线从特定音频播放玩家传输音频。
    2. 目标实例 设置为您的新 音频发射器 以指定您希望电缆将音频传送到此特定音频发射器内的音量中。
  4. 回到 浏览器 窗口,导航到 ServerScriptService ,然后插入一个 脚本 ,重命名为 GumdropService ,将其 运行上下文 属性设置为 服务器 ,然后将以下代码粘贴到脚本中:


    -- 初始化变量
    local Workspace = game:GetService("Workspace")
    local Players = game:GetService("Players")
    local ServerStorage = game:GetService("ServerStorage")
    local TweenService = game:GetService("TweenService")
    -- 模块
    local Leaderboard = require(ServerStorage.Leaderboard)
    local PlayerData = require(ServerStorage.PlayerData)
    -- 变量
    local gumdropsFolder = Workspace.Gumdrops
    local gumdrops = gumdropsFolder:GetChildren()
    local GUMDROP_KEY_NAME = PlayerData.GUMDROP_KEY_NAME
    local GUMDROP_AMOUNT_TO_ADD = 1
    local function updatePlayerGumdrops(player, updateFunction)
    -- 更新 gumdrop 表
    local newGumdropAmount = PlayerData.updateValue(player, GUMDROP_KEY_NAME, updateFunction)
    -- 更新橡皮掉落排行榜
    Leaderboard.setStat(player, GUMDROP_KEY_NAME, newGumdropAmount)
    -- 检查玩家是否收集了三个橡皮糖
    if newGumdropAmount >= 3 then
    -- 当玩家收集三个橡皮糖时,播放门事件音频
    local audioPlayer = Workspace.Door.AudioPlayer
    audioPlayer:Play()
    -- 动画门向下移动
    local doorPart = Workspace.Door
    local tweenInfo = TweenInfo.new(2, Enum.EasingStyle.Linear)
    local tween = TweenService:Create(doorPart, tweenInfo, {Position = doorPart.Position + Vector3.new(0, -15, 0)})
    tween:Play()
    end
    end
    -- 定义事件处理器
    local function onGumdropTouched(otherPart, gumdrop)
    if gumdrop:GetAttribute("Enabled") then
    local character = otherPart.Parent
    local player = Players:GetPlayerFromCharacter(character)
    if player then
    -- 玩家触摸了一块橡皮糖
    local audioPlayer = gumdrop.AudioPlayer
    audioPlayer:Play()
    gumdrop.Transparency = 1
    gumdrop:SetAttribute("Enabled", false)
    updatePlayerGumdrops(player, function(oldGumdropAmount)
    oldGumdropAmount = oldGumdropAmount or 0
    return oldGumdropAmount + GUMDROP_AMOUNT_TO_ADD
    end)
    print("Player collected gumdrop")
    end
    end
    end
    -- 设置事件听众
    for _, gumdrop in gumdrops do
    gumdrop:SetAttribute("Enabled", true)
    gumdrop.Touched:Connect(function(otherPart)
    onGumdropTouched(otherPart, gumdrop)
    end)
    end

    该脚本首先初始化 WorkspacePlayersServerStorageTweenService 服务,以便它可以引用其子服务和功能。然后,它需要 排行榜玩家数据 模块在 ServerStorage ;这些模块负责在屏幕右上角创建和更新一个排行榜,该排行榜记录了玩家在环境中收集的橡皮球数量。

    脚本的 updatePlayerGumdrops 函数是触发事件反馈 3D 音频的主要地方,需要两个参数:

    • player - 收集橡皮糖的玩家。
    • updateFunction - 一个回调函数,可更新玩家收集到的橡皮糖数量。

    当玩家与橡皮糖发生碰撞时,脚本:

    • 通过调用 PlayerData.updateValue 函数获得玩家的新橡皮糖收集量值。
    • 通过调用 Leaderboard.setStat 函数来更新排行榜以此新数量。
    • 检查是否超过或等于 3 的数量。

    当此值大于或等于 3 时,脚本:

    • 从音频播放器播放 3D 音频轨道到音频发射器。
    • 将门线性地移动15格向下,距离其当前位置。

    脚本的剩余部分主要负责检测任何与橡皮糖发生碰撞的东西是玩家,以便它可以触发收集反馈的非位置声音。了解脚本的这一部分的更多信息,请参阅添加 2D 音频 - 游戏回馈

  5. 在环境中收集所有三个橡皮糖后,播放体验以听到幻灯门的声音。当你旋转相镜头时,声音会在你的实世扬声器动态切换,以便你根据发射器在 3D 空间中的位置听到它。

对象互动

当玩家与环境中的 3D 对象互动时,例如打开灯开关或拿起武器,很重要提供即时反馈,以便他们直观地理解他们与对象的互动方式。将视觉和听觉反馈匹配在一起可以增强玩家的行动与环境反应之间的原因和效果关系。

为了扩展这一概念,让我们审查植物样本中的以下一次性 3D 音频,用于用户流程种植橙子:

  • 当玩家种下种子时,轻微的咕咕声播放。
  • 当玩家给他们的成长植物浇水时,发出类似于溅水的湿声音。
  • 当玩家收集完全成长的植物时,会播放片段声音。
  • 当玩家将泡菜放入车厢时,播放柔和的噗音。

这些声音都强化了玩家的靠近提示键与在3D空间中改变形状的对象互动。对于那些患有视觉障碍的玩家,其中颜色变化或动画更难独拥有识别,提供这些多种形式的感官反馈帮助您的 3D 对象互动继续对尽可能多的玩家开放并易于理解。

为了提供有关如何配置对多种感官反馈的对象互动的不同示例,样本在玩家步入姜饼屋内的 3D 薄荷按钮时提供视觉和听觉反馈。当玩家不与按钮交互时,它看起来像一个典型的薄荷糖,但当他们踩到按钮时,样本:

  • 播放庆祝铃声音频轨道。
  • 将按钮的侧面染成绿色。
  • 将按钮移到地面。

从这里,您可以将此交互连接到各种独特的游戏行动,例如解锁物品或触发特殊能力。

默认视图
>

压缩状态
>

要在样本 姜饼屋 - 完整音频 位置文件中重现一次射击对象互动 3D 音频:

  1. 资源管理器 窗口中,导航到 工作区 > 3DAudioButton ,然后:

    1. 插入一个 音频播放器 对象来创建按钮的音频来源。
    2. 插入一个 音频发射器 对象,从 3DAudioButton 发出位置流。
    3. 插入一个 电线 对象,将流从音频播放器传送到音频发射器。
  2. 选择 音频播放器 ,然后在 属性 窗口中将 资产ID 设置为rbxassetid://1846248593

  3. 选择 电线 ,然后在 属性 窗口中,

    1. 源实例 设置为你的新 音频播放器 以指定你希望电线从特定音频播放玩家传输音频。
    2. 目标实例 设置为您的新 音频发射器 以指定您希望电线将音频传送到此特定音频发射器内的按钮。
  4. 回到 Explorer 窗口,将 脚本 插入 3DAudioButton 中,重命名为 播放音频时按下 ,然后将以下代码粘贴到脚本中:


    local TweenService = game:GetService("TweenService")
    local buttonModel = script.Parent.Parent
    local buttonPart = buttonModel.ButtonPart
    local buttonPressedAudioPlayer = buttonModel.ButtonPressedAudioPlayer
    local tweenInfo = TweenInfo.new(.2, Enum.EasingStyle.Exponential)
    local buttonTweenByIsPressed = {
    -- 已按下
    [true] = TweenService:Create(buttonPart, tweenInfo, {
    Size = buttonPart.Size / Vector3.new(2, 1, 1),
    Color = Color3.fromRGB(75, 151, 75),
    }),
    -- 默认
    [false] = TweenService:Create(buttonPart, tweenInfo, {
    Size = buttonPart.Size,
    Color = Color3.fromRGB(196, 40, 28),
    }),
    }
    local function onIsPlayingChanged()
    local isPlaying = buttonPressedAudioPlayer.IsPlaying
    local tween = buttonTweenByIsPressed[isPlaying]
    tween:Play()
    end
    onIsPlayingChanged()
    buttonPressedAudioPlayer:GetPropertyChangedSignal("IsPlaying"):Connect(onIsPlayingChanged)
    buttonPressedAudioPlayer.Ended:Connect(onIsPlayingChanged)
    buttonPart.Touched:Connect(function(_hit)
    buttonPressedAudioPlayer:Play()
    end)

    脚本首先通过获取:

    • The TweenService 所以它可以动画地面上突出的按钮部分。
    • 脚本的父辈 3DAudioButton 模型。
    • 按钮的部分突出地面。
    • 与你的庆祝音效音频轨道相关的音频播放器。

    接下来,脚本定义了:

    • 一个 TweenInfo 对象,指定按钮的动画将使用指数动画风格播放。
    • 两个青少年代表按钮的压下或未压下状态。
      • 按下 true 状态将按钮稍微向下移动到地面,并将零件的侧面染成绿色调色。
      • false 未压缩状态将按钮移回原始位置并移除前一个颜色。

    脚本的剩余部分是工作对象互动反馈的主要发生地方,因此让我们审查 onIsPlayingChanged 函数和事件听机如何一起工作:

    1. buttonPart.Touched 监听玩家触碰按钮,然后调用 Play() 函数开始播放相关的音频从音频播放器。该过程将AudioPlayer.IsPlaying属性从false切换到true
    2. buttonPressedAudioPlayer:GetPropertyChangedSignal("IsPlaying") 监听音频播放玩家的 IsPlaying 属性发生变化,然后调用 onIsPlayingChanged 函数。
    3. onIsPlayingChanged 函数使用此信息触发在 3D 空间中更改其视觉外观的青少年。
    4. 为了防止玩家在快速跳到按钮的快速连续中误启动音频,buttonPressedAudioPlayer.Ended 听取音频播放器完成播放后再次调用 onIsPlayingChanged 函数。

    要注意的是,onIsPlayingChanged事件只在它从false变为true时发生,这意味着它不会在从true变为false时发生。这是由于服务器到客户端复制属性的时间复杂性导致的行为。因此,在这个例子中提供了 Ended 事件,用于监听以覆盖两种情况。

  5. 播放体验以听到玩家角色在姜饼屋中触碰 3D 按钮时发出的庆祝声音。当你走开按钮时,音量会下降。

角色对话

从你的非可玩角色(NPC)提供方向性音频有助于引导玩家走向环境中的有兴趣点,并为他们与其他角色的互动增加深度。事实上,在故事驱动的游戏中,许多游戏设计师战略性地使用角色对话教授玩家关于他们的角色、盟友和敌人角色以及世界本身的信息。

这种技术的常见例子包括:

  • 对话风格 来设置你游戏的调调。
  • 幽默 向玩家教授关于角色关系的知识。
  • 敌人对话 向玩家承认动机或与玩家关系。
  • 玩家角色大声说出他们的想法 来轻轻引导玩家做下一步,例如治疗自己、移动到另一个地点或找到物品。
  • 盟友角色与玩家角色交谈 来揭示经验世界的细节,例如历史、文化和社会问题。

为了展示这在实践中可能看起来的样子,让我们回顾一下来自 超越黑暗 展示的以下一次性 3D 音频,该音频在玩家在空间站主大厅区域时定期播放。

使用宇宙空间站作为角色,此对话片段为玩家提供关于整体设置的重要背景和知识。例如,从这句单独的句子中,玩家学到了:

  • 他们在外太空,具体是在名为 Kerr-Newman Deep Space Relay 14 的空间站上。
  • 他们的环境是未来主义的和欢迎的。
  • 他们是访客,很可能很快就会离开。

这些细节共同将玩家置身于环境中,并为他们的任务添加紧迫感。然而,如果玩家不知道他们的主要任务是什么,你也可以使用角色对话向玩家通知你希望他们在体验中做什么。

为了说明,样本使用 音量 或 3D 空间内的隐形区域,触发从雪人到玩家的角色对话,引导玩家收集三个橡皮糖以打开他的主页门。作为玩家加入体验的第一件事之一,玩家更可能触发对话并知道他们需要做什么才能成功。

雪人周围的音量使用碰撞反馈播放音频,当玩家进入 3D 区域时。

要在样本 姜饼屋 - 完整音频 位置文件中重创一次射击角色对话 3D 音频:

  1. 探索器 窗口中,导航到 工作区 > 对话音量 ,然后:

    1. 插入一个 音频播放器 对象来创建音量的音源。
    2. 插入一个 音频发射器 对象,从 对话音量 发出位置流。
    3. 插入一个 电线 对象,将流从音频播放器传送到音频发射器。
  2. 选择 音频播放器 ,然后在 属性 窗口中将 资产ID 设置为rbxassetid://92917410841704

  3. 选择 电线 ,然后在 属性 窗口中,

    1. 源实例 设置为你的新 音频播放器 以指定你希望电线从特定音频播放玩家传输音频。
    2. 目标实例 设置为您的新 音频发射器 以指定您希望电缆将音频传送到此特定音频发射器内的音量中。
  4. 回到 Explorer 窗口,导航到 StarterPlayer > StarterCharacterScripts , 插入一个 LocalScript , 重命名为 PlayAudioWhenInVolume , 并将以下代码粘贴到本地脚本中:


    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 audioPlayer = Workspace.DialogueVolume.AudioPlayer
    audioPlayer:Play()
    audioPlayer.Ended:Wait()
    debounce = false
    end)

    该脚本首先获取工作区和玩家服务,以便它可以引用其子服务和功能。对于每个加载或重生回体验的玩家角色,脚本等待:

    • 角色的 HumanoidAnimator 对象。
    • 工作区命名为 对话音量 的音量对象。

    当任何东西与音量发生碰撞时,Touched事件处理器函数获得第一个祖先,即Model,这应该是角色,如果碰撞的BasePart与音量相关的字符是角色模型的后裔,那么这应该是角色。如果是,那么函数将:

    • 将缓冲设置为 true
    • 播放并等待音频结束。
    • 将缓冲设置恢复到 false

    将从 设置为 设置为 后,在音频播放完成后再次设置为 设置为防止音频反复触发,因为玩家不断碰撞音量。了解有关此缓冲模式的更多信息,请参阅检测碰撞

  5. 播放体验,听到玩家角色触碰雪人周围的音量时听到教学角色对话。