添加 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 音频,或在特定时间和位置播放一次的非定向音频,除非玩家再次触发它,提供了玩家关于任何您不希望他们错过的内容的重要上下文。在您的游戏中使用这种类型的听觉反馈是至关重要的,因为它为关键的游戏功能和玩家动作提供了即时、清晰的响应,例如开始老板战斗或失去大量生命值。

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

游戏反馈

随着玩家在游戏中的进展,重要的是他们能够立即获得关于影响他们游戏状态的任何事物的听觉反馈,例如他们的分数、生命值或关卡进度。因为这种类型的音频直接与玩家目标相关,所以这些声音必须清晰、可听,并传达有用和准确的信息。

为了演示这个概念,让我们回顾以下来自 平台游戏 模板的单次 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 交互是有用的,以便尽可能多的玩家能够访问和直观理解。为了扩展这个概念,让我们考虑以下 UI 状态,这些状态对于 UGC Homestore 模板的购买按钮非常重要:

  • 聚焦中 - 表示玩家正在突出显示一个 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 对象,指定按钮的动画将以指数动画风格播放。
    • 两个表示按钮按下或未按下状态的动画。
      • true 按下状态将按钮稍微向下移动并染成青色。
      • false 未按下状态将按钮移回其原始位置并去除之前的染色。

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

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

    重要的是要注意,onIsPlayingChanged 事件仅在从 false 变为 true 时触发,这意味着在从 true 变为 false 时不会触发。这是预期的行为,因为在服务器到客户端复制属性的时机上存在复杂性。因此,在这个示例中提供并监听 Ended 事件以涵盖这两种情况。

  5. 进行游戏测试以听到每次按下屏幕按钮时的音乐。

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