中级教程

添加 2D 音频

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

2D 音频 是一种无方向的声音,不从任何特定位置发出,无论听众在 3D 空间中的位置或方向如何,听到的音频都保持不变。这意味着当玩家在环境中移动他们的听众时,他们可以从任何转向的地方持续听到 2D 音频。

姜饼屋 - 开始 .rbxl 文件为起点,并以 姜饼屋 - 完整音频 作为参考,本教程将向您展示如何将循环和单次 2D 音频添加到您的体验中,包括以下内容的指导:

  • 循环背景音乐,玩家连接到服务器时立即播放。
  • 触发音频,根据玩家的游戏目标提供即时反馈。
  • 启动各种声音以指示不同的用户界面交互。

如果您在过程中遇到困难,可以使用 姜饼屋 - 完整音频 作为参考,以比较您的进度。

2D 音频对象

为了创建无方向音频,理解您在本教程中将要使用的音频对象非常重要。对于 2D 音频,有三种主要类型的音频对象:

  • AudioPlayer 对象加载并播放 音频文件
  • AudioDeviceOutput 对象是现实世界中的 物理硬件设备,例如扬声器或耳机。
  • Wires 将音频流从一个对象传递到另一个对象。

所有这些音频对象共同工作,将声音从文件播放传递到每个玩家的硬件。让我们通过一个示例来了解其在实践中的工作方式:玩家在使用他们的笔记本电脑玩体验时佩戴耳机:

  • AudioPlayer127645268874265 音频资产 ID 加载到体验中,用于货币购买的声音。
  • 一个 Wire 将流从 AudioPlayer 传递到 AudioDeviceOutput,使得流能够通过玩家的耳机输出。
  • AudioDeviceOutput 对象将音频传送到玩家的物理扬声器,或者在这种情况下,是他们的耳机。

接下来的部分将深入探讨并引用这些对象,帮助您学习如何播放循环和单次 2D 音频。在您回顾这些对象与即将到来的技术时,您可以更准确地预测如何将体验中的声音捕捉并传递给玩家。

循环音频

循环 2D 音频,或在玩家连接到服务器时无方向音频无缝重复的音频,是一种常见的声音设计技术,能够提供一致的听觉背景,无论玩家正在做什么或他们在 3D 空间中的位置如何。此外,循环 2D 音频确保您的体验不会完全安静,这可能会使玩家感到迷失并分散他们的目标。

为了演示这个概念,请回顾以下 2D 音频如何显著提升 有害空间站 示例的质量。没有背景音乐时,空间站显得空洞和无生气。然而,当空间站包含背景音乐时,空间站显得既阴郁又危险,增加了整体的恐怖氛围。

没有循环的 2D 音频
有循环的 2D 音频

循环音频特别适用于确定您想要玩家在加入体验时的感受。例如,示例旨在为玩家提供在冬季仙境中的愉快冒险,因此它使用一个欢快的音轨,以积极影响他们在进入环境时的情绪。要查看该技术可以产生多大的影响,请考虑如果您将循环音频替换为金属轨道,玩家的体验将会发生怎样变化。

要在示例 姜饼屋 - 完整音频 文件中重现循环 2D 音频:

  1. 启用附加到玩家角色的默认监听器。

    1. 资源管理器 窗口中,选择 SoundService
    2. 属性 窗口中,将 DefaultListenerLocation 设置为 Character。运行体验时,引擎会在 SoundService 下自动创建一个 AudioDeviceOutput
  2. 资源管理器 窗口中,导航到 SoundService,然后:

    1. 插入一个 AudioPlayer 对象以创建音频源。
    2. 插入一个 AudioDeviceOutput 对象以创建一个在整个体验中播放的扬声器。
    3. 插入一个 Wire 对象以传递音频流从音频播放器到扬声器。
  3. 选择 AudioPlayer,然后在 属性 窗口中,

    1. AssetID 设置为 rbxassetid://1841461968 以播放一个欢快的音轨。
    2. 启用 Looping 使音频无缝重复。
    3. Volume 设置为 0.2,使音频以较低音量播放,以便您仍然能够听到体验中的其他音频源。
  4. 选择 Wire,然后在 属性 窗口中,

    1. SourceInstance 设置为您的新 AudioPlayer,以指定希望电线从该特定音频播放器传输音频。
    2. TargetInstance 设置为您的新 AudioDeviceOutput,以指定希望电线将音频传输到该特定扬声器。
  5. 返回到 资源管理器 窗口,在 AudioPlayer 中插入一个 Script,将其重命名为 LoopBackgroundMusic,将其 RunContext 属性设置为 Client,然后将以下代码粘贴到脚本中:


    local audioPlayer = script.Parent
    audioPlayer:Play()

    该脚本首先声明一个变量以表示脚本的父级 AudioPlayer。然后脚本设置音频源,以便在玩家加入体验的那一刻播放音频,直到他们退出体验为止。

  6. 体验测试播放,以听到循环背景音乐。

单次音频

单次 2D 音频,或在特定时间和位置播放一次的无方向音频,除非玩家再次触发它,为玩家提供重要的上下文,关于任何您希望他们注意的事情。在您的体验中使用这种类型的听觉反馈非常重要,因为它为关键的游戏功能和玩家行为提供了即时且清晰的响应,例如开始一场 Boss 战或失去大量生命值。

以下部分提供了常见游戏场景的实现细节,在这些场景中,玩家需要及时的无方向反馈,包括游戏反馈、用户界面元素交互和教程的旁白。

游戏反馈

当玩家在您的体验中进展时,立即接收关于影响他们游戏的任何事物的 auditory feedback,如他们的分数、生命值或等级进度非常重要。因为这种类型的音频与玩家目标直接相关,所以这些声音必须清晰、可听,并传达有用和准确的信息。

为了演示这个概念,让我们回顾在 平台游戏 模板中,玩家跳跃并收集硬币时播放的以下单次 2D 音频:

  • 当玩家按下设备上的跳跃键时,空气中响起一声呼啸声。
  • 当玩家的角色触碰到硬币时,会播放高音的叮咚声和硬币的碰撞声。

这两种声音在 3D 空间的上下文中都是瞬时可识别的,它们帮助玩家理解自己行为的成功,而无须仅仅依靠视觉提示。

当您同时播放多个音效时,某些声音可能对玩家而言比其他声音更重要。在这种情况下,收集硬币的声音传达了比跳跃声音更有价值的游戏信息,因此体验会以稍高的音量播放它。不过,没有标准或规则可以实现完美的混音,因此请根据您认为最重要的内容调整您体验中的声音。

例如,该示例在体验中包含多种音频触发器,但并不是所有触发器在理解体验的主要目标时的同等重要性:收集糖豆以打开姜饼屋。为了确保玩家了解他们需要做什么,该示例在玩家每次收集糖豆时将听觉反馈的振幅放大三倍。

要在示例 姜饼屋 - 完整音频 文件中重现单次状态反馈音频:

  1. 资源管理器 窗口中,导航到 WorkspaceGumdropsGumdrop,然后:

    1. 插入一个 AudioPlayer 对象以创建音频源。
    2. 插入一个 AudioDeviceOutput 对象以创建一个在整个体验中播放的扬声器。
    3. 插入一个 Wire 对象以传递音频流从音频播放器到扬声器。
  2. 选择 AudioPlayer,然后在 属性 窗口中,

    1. AssetID 设置为 rbxassetid://9113723699 以播放一段温和的咀嚼音轨。
    2. TimePosition 设置为 0.15,以便音频轨道更接近实际的咀嚼声音。
    3. Volume 设置为 3,以在体验中以较高音量播放音频,确保您能听到糖豆的声音。
  3. 选择 Wire,然后在 属性 窗口中,

    1. SourceInstance 设置为您的新 AudioPlayer,以指定希望电线从该特定音频播放器传输音频。
    2. TargetInstance 设置为您的新 AudioDeviceOutput,以指定希望电线将音频传输到该特定扬声器。
  4. 复制 Gumdrop,直到您有三个糖豆,然后将它们散布在环境中。

  5. 返回到 资源管理器 窗口,导航到 ServerScriptService,然后插入一个 Script,将其重命名为 GumdropService,将其 RunContext 属性设置为 Server,然后将以下代码粘贴到脚本中:


    -- 初始化服务
    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)
    -- 更新糖豆表
    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("Active") then
    local character = otherPart.Parent
    local player = Players:GetPlayerFromCharacter(character)
    if player then
    -- 玩家碰到糖豆
    local audioPlayer = gumdrop.AudioPlayer
    audioPlayer:Play()
    gumdrop.Transparency = 1
    gumdrop:SetAttribute("Active", false)
    updatePlayerGumdrops(player, function(oldGumdropAmount)
    oldGumdropAmount = oldGumdropAmount or 0
    return oldGumdropAmount + GUMDROP_AMOUNT_TO_ADD
    end)
    end
    end
    end
    -- 设置事件监听器
    for _, gumdrop in gumdrops do
    gumdrop:SetAttribute("Active", true)
    gumdrop.Touched:Connect(function(otherPart)
    onGumdropTouched(otherPart, gumdrop)
    end)
    end

    该脚本首先初始化 WorkspacePlayersServerStorageTweenService 服务,以便引用它们的子项和功能。然后,它在 ServerStorage 中要求 LeaderboardPlayerData 模块;这些模块负责在屏幕右上角创建和更新一个排行榜,跟踪玩家在环境中收集的糖果数量。

    脚本的 updatePlayerGumdrops 函数主要负责在玩家收集糖豆时更新排行榜,并等待他们收集所有三个糖豆的时刻,以便触发姜饼屋门的定位声音和动画。有关脚本此部分的更多信息,请参见 添加 3D 音频 - 事件反馈

    脚本的 onGumdropTouched 函数是触发游戏反馈的 2D 音频的主要工作发生的地方,并接受两个参数:

    • otherPart - 与糖豆碰撞的部件。
    • gumdrop - 代表糖豆的球形部件。

    当任何东西与糖豆部件碰撞时,脚本首先检查糖豆是否处于激活状态。如果是,脚本会验证碰撞对象是否为玩家。如果是,脚本然后:

    • 播放来自音频播放器的 2D 音频轨道。
    • 使糖豆不可见。
    • 禁用糖豆,以便玩家不能与不可见的糖豆交互。
    • 更新触碰糖豆的玩家的排行榜。

    最后,脚本设置事件监听器,以便对于每个糖豆,连接一个函数到 Touched 事件,以便在玩家触碰糖豆时调用 onGumdropTouched 函数。

  6. 体验测试播放,以在收集糖豆后听到咀嚼声。

用户界面交互

当玩家与您的用户界面 (UI) 元素交互,例如悬停在复选框上或选择购买按钮时,提供即时反馈很重要,以便他们直观地理解 如何 与您的 UI 进行交互。例如,许多设计师会在玩家启用或禁用设置时更改 UI 的视觉特征,以传达该设置的状态。

但是,对于在视觉上有障碍的玩家来说,颜色变化或动画更难以独立识别,提供多种形式的感觉反馈对于您的 UI 交互是非常有用的,以便尽可能多的玩家都能保持可及性和直观性。为了扩展这一概念,让我们考虑对 UGC 家居商店 模板购买按钮的重要 UI 状态:

  • 获取焦点 - 表示玩家正在准备选择 UI 元素。
  • 悬停 - 表示玩家的光标正在悬停在 UI 元素上。
  • 已选择 - 表示玩家通过输入选择了某个 UI 元素。

这三种交互对于模板的主要用户流程(浏览、选择和购买玩家的头像服装和配饰)至关重要,因此确保任何人都不会对如何与 UI 进行交互感到困惑是至关重要的。为了确保玩家成功,模板为每个 UI 交互提供了视觉和听觉反馈。

视觉反馈
默认视图
获取焦点状态
悬停状态
已选择状态

举个例子,展示您可以如何配置多种形式的感觉反馈,示例在玩家按下屏幕上的薄荷按钮时提供了视觉和听觉反馈。当玩家没有与按钮交互时,它看起来就像一颗典型的薄荷糖,但当他们按下按钮时,示例:

  • 播放一段令人愉快的音乐音轨。
  • 将按钮的颜色染成蓝绿色。
  • 将按钮稍微向下移动。

从这里,您可以将此交互与各种有用的游戏动作联系起来,例如打开菜单或购买物品。

默认视图
按下状态

要在示例 姜饼屋 - 完整音频 文件中重现单次 UI 交互音频:

  1. 资源管理器 窗口中,导航到 StarterGui2DAudioButton,然后:

    1. 插入一个 AudioPlayer 对象以创建音频源。
    2. 插入一个 AudioDeviceOutput 对象以创建一个在整个体验中播放的扬声器。
    3. 插入一个 Wire 对象以传递音频流从音频播放器到扬声器。
  2. 选择 AudioPlayer,然后在 属性 窗口中,将 AssetID 设置为 rbxassetid://3422389728 以播放一段复古的音乐音轨。

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

    1. SourceInstance 设置为您的新 AudioPlayer,以指定希望电线从该特定音频播放器传输音频。
    2. TargetInstance 设置为您的新 AudioDeviceOutput,以指定希望电线将音频传输到该特定扬声器。
  4. 返回到 资源管理器 窗口,在 2DAudioButton 中插入一个 LocalScript,将其重命名为 PlayAudioWhenPressed,然后将以下代码粘贴到脚本中:


    local TweenService = game:GetService("TweenService")
    local buttonGui = script.Parent
    local buttonImageButton = buttonGui.ButtonFrame.ButtonImageButton
    local buttonAudioPlayer = buttonGui.AudioPlayer
    local tweenInfo = TweenInfo.new(.2, Enum.EasingStyle.Exponential)
    local buttonTweenByIsPressed = {
    -- 按下
    [true] = TweenService:Create(buttonImageButton, tweenInfo, {
    Position = buttonImageButton.Position + UDim2.fromScale(0, .1),
    ImageColor3 = Color3.fromRGB(117, 255, 255),
    }),
    -- 默认
    [false] = TweenService:Create(buttonImageButton, tweenInfo, {
    Position = buttonImageButton.Position,
    ImageColor3 = Color3.fromRGB(255, 255, 255),
    }),
    }
    local function onIsPlayingChanged()
    local isPlaying = buttonAudioPlayer.IsPlaying
    local tween = buttonTweenByIsPressed[isPlaying]
    tween:Play()
    end
    onIsPlayingChanged()
    buttonAudioPlayer:GetPropertyChangedSignal("IsPlaying"):Connect(onIsPlayingChanged)
    buttonAudioPlayer.Ended:Connect(onIsPlayingChanged)
    buttonImageButton.Activated:Connect(function(_hit)
    buttonAudioPlayer:Play()
    end)

    该脚本首先获取:

    • TweenService,以便可以动画 UI 元素。
    • 脚本的父级 2DAudioButton ScreenGui 对象。
    • 薄荷糖的 ImageButton
    • 拥有您的音乐音轨的相关音频播放器。

    然后脚本定义:

    • 一个指定按钮动画将以指数动画样式播放的 TweenInfo 对象。
    • 两个表示按钮 pressed 和 unpressed 状态的 tween。
      • true 按下状态使按钮在屏幕上稍微向下移动,并将其染成蓝绿色。
      • false 未按下状态将按钮移回其在屏幕上的原始位置,移除之前的染色。

    剩下的脚本是触发 2D UI 交互反馈的主要工作发生的地方,让我们回顾 onIsPlayingChanged 函数和事件监听器在 UI 用户流程中的协同工作:

    1. buttonImageButton.Activated 监听玩家点击按钮,然后调用 Play() 函数开始播放音频播放器中的相关音频。该过程将 AudioPlayer.IsPlaying 属性从 false 切换为 true
    2. buttonAudioPlayer:GetPropertyChangedSignal("IsPlaying") 监听音频播放器的 IsPlaying 属性变化,然后调用 onIsPlayingChanged 函数。
    3. onIsPlayingChanged 函数利用此信息触发会改变其在屏幕上视觉外观的 tween。
    4. 为了防止玩家在快速点击按钮时意外重新启动音频,buttonAudioPlayer.Ended 监听音频播放器完成播放,然后再次调用 onIsPlayingChanged 函数。

    值得注意的是,onIsPlayingChanged 事件仅在从 false 变为 true 时触发,意味着在从 true 变为 false 时不会触发。这是由于从服务器到客户端复制属性的时序问题而导致的预期行为。因此在此示例中提供并监听 Ended 事件,以涵盖这两种情况。

  5. 体验测试播放,以在每次按下屏幕按钮时听到音效。

©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。