添加 3D 音效

*此內容是使用 AI(Beta 測試版)翻譯,可能含有錯誤。若要以英文檢視此頁面,請按一下這裡

3D 音效 是從 3D 空間中特定位置發出的方向性聲音,根據音效發射器和聽者之間的距離和方向,音量會增加或減少。這意味著,當聽者或發射器在環境中移動時,玩家可以動態地從不同的方向和音量水平聽到這些音效。

使用 Gingerbread House - Start .rbxl 文件作為起點,並以 Gingerbread House - Complete Audio 作為參考,本教程將向您展示如何為您的遊戲添加循環和單次 3D 音效,包括以下指導:

  • 循環的環境音效,當玩家連接到伺服器時立即播放。
  • 觸發音效以告知玩家有關對其遊戲至關重要的關鍵情境事件。
  • 啟用音效以在玩家與 3D 物體互動時提供聽覺反饋。
  • 播放角色音效片段,吸引並指導玩家前往其環境中的興趣點。

如果在過程中遇到困難,您可以使用 Gingerbread House - Complete Audio 作為參考,以比較您的進度。

音效物件

要創建方向性音效,了解您在本教程中將使用的音效物件非常重要。3D 音效有六種主要類型的音效物件:

  • AudioPlayer 物件加載並播放 音效文件
  • AudioEmitter 物件是一個 虛擬揚聲器,將音效發射到 3D 環境中。
  • AudioListener 物件是一個 虛擬麥克風,從 3D 環境中接收音效。
  • AudioDeviceOutput 物件是現實世界中的 物理硬體設備,例如揚聲器或耳機。
  • AudioDeviceInput 物件是現實世界中的 物理麥克風
  • Wires 將音效流從一個物件傳遞到另一個物件。

所有這些音效物件共同工作,發出聲音,就像它們在現實世界中的對應物一樣。讓我們看看這在實踐中是如何運作的,使用一個例子:玩家在使用筆記本電腦玩遊戲時佩戴耳機:

  • AudioPlayer 將音效資產 ID 1516791621 加載到遊戲中,作為雨聲音軌。
  • AudioEmitter 將雨聲音軌的音效流發射到 3D 環境中。
  • 一個 Wire 將音效流從 AudioPlayer 傳遞到 AudioEmitter,使音效從 3D 揚聲器中發出。
  • 角色的子 AudioListener 物件在 3D 環境中聆聽該聲音,並將其反饋到他們的耳機中。
  • AudioDeviceOutput 物件將聲音從 AudioListener 傳遞到玩家的物理揚聲器,或者在這種情況下,傳遞到他們的耳機。
  • AudioDeviceInput 物件捕獲來自現實世界的聲音,並將其反饋到遊戲中以進行語音聊天。
遊戲中的物件表示
現實世界中的物件表示

接下來的部分將深入探討並引用這些物件,幫助您學習如何播放循環和單次 3D 音效。在您回顧這些物件及即將到來的技術時,您可以更準確地預測如何捕獲並將聲音從遊戲傳遞給玩家,反之亦然。

循環音效

循環 3D 音效,或在玩家連接到伺服器時無縫重複的方向性音效,是一種常見的聲音設計技術,旨在通過使 3D 空間感覺生動和動態來增強氛圍。此外,循環 3D 音效保持您的環境音源一致,例如電視的靜電聲或瀑布的轟鳴聲;如果這些聲音突然停止,環境將會感覺不真實。

為了演示這一概念,請查看以下兩個大型瀑布的 3D 音效,當未循環的音軌完成時,它們會立即停止。雖然水聲最初讓玩家沉浸在戶外環境中,但突然的聽覺變化卻令人感到刺耳且不自然,這與現實世界中瀑布的行為不符。

同樣,示例使用此技術來處理流動的巧克力瀑布,並根據玩家與音效發射器的距離調整其音量。當玩家距離小於 20 個單位時,發射器以全音量發出聲音。隨著玩家的移動,音效每距離音效發射器 20 個單位音量減少。這模擬了現實世界中聲音隨著距離源的增加而減少的情況。

要在示例 Gingerbread House - Complete Audio 位置文件中重建循環 3D 音效:

  1. 啟用附加到玩家角色的默認聽者。

    1. Explorer 窗口中,選擇 SoundService
    2. Properties 窗口中,將 DefaultListenerLocation 設置為 Character。當您運行遊戲時,引擎會自動:
      • 在每個玩家角色的 Humanoid.RootPart 下創建一個 AudioListener,以便您可以根據遊戲中聲音源的位置和音量在現實世界的揚聲器中聽到聲音的變化。
      • SoundService 下創建一個 AudioDeviceOutput
  2. Explorer 窗口中,導航到 WorkspaceWaterfallAudioObject,然後:

    1. 插入一個 AudioPlayer 物件以創建瀑布的音效源。
    2. 插入一個 AudioEmitter 物件以從 WaterfallAudioObject 發射位置音效流。
    3. 插入一個 Wire 物件以將音效流從音效播放器傳遞到音效發射器。
  3. 選擇 AudioPlayer,然後在 Properties 窗口中,

    1. AssetID 設置為 rbxassetid://1516791621 以播放雨聲音軌。
    2. 啟用 Looping 以便音效無縫重複播放。
  4. 選擇 AudioEmitter,然後在 Properties 窗口中,將 DistanceAttenuation 設置為 {0: 1}, {20: 0.8}, {40: 0.4}, {80: 0},以便音效每距離音效發射器 20 個單位音量逐漸減少。

  5. 選擇 Wire,然後在 Properties 窗口中,

    1. SourceInstance 設置為您的新 AudioPlayer,以指定您希望該電線從此特定音效播放器傳遞音效。
    2. TargetInstance 設置為您的新 AudioEmitter,以指定您希望該電線將音效傳遞到此特定的瀑布音效發射器。
  6. 返回 Explorer 窗口,將一個 Script 插入到 WaterfallAudioObject 中,將其重命名為 LoopWaterfallMusic,將其 RunContext 屬性設置為 Client,然後將以下代碼粘貼到腳本中:

    local audioPlayer = script.Parent
    audioPlayer:Play()

    該腳本首先聲明一個變量來表示腳本的父級 AudioPlayer。然後,該腳本設置音效源從玩家加入遊戲的那一刻開始播放,直到他們退出遊戲。

  7. 測試遊戲以聽到當您的角色接近瀑布時循環的巧克力雨聲。當您旋轉角色的頭部以查看不同方向時,聲音會根據發射器在 3D 空間中的位置在現實世界的揚聲器中動態變化。

單次音效

單次 3D 音效,或在特定時間和位置播放一次的方向性音效,除非玩家再次觸發它,否則為玩家提供有關其行動、環境和周圍角色的上下文。在您的遊戲中使用這種類型的聽覺反饋至關重要,因為它允許玩家做出戰略決策,例如避免來襲的敵人或撿起有用的物品。

以下部分提供了常見遊戲場景的實施細節,玩家需要及時的方向性反饋,包括情境遊戲事件、物件互動和非可玩角色對話。

事件反饋

當玩家在其環境中觸發關鍵情境事件時,例如解鎖新的遊戲區域或促使敵人開火,了解他們需要在 3D 空間中將注意力集中在哪裡至關重要。如果他們沒有立即的聽覺反饋,他們可能會錯過對其遊戲至關重要的信息,導致不知該往哪裡去或接下來該做什麼而感到沮喪。

為了演示這一點,讓我們回顧一下來自 Laser Tag 模板的單次 3D 音效,該音效從每個玩家的槍械中播放:

  • 每當玩家從槍械中開火時,會播放一聲深沉的爆炸聲。
  • 每當玩家用新一輪子彈重新裝填槍械時,會播放一次點擊和機器人嘟嘟聲。

這兩種聲音都提供了情境意識,通過提醒所有附近的玩家爆炸聲的方向,使他們能夠做出明智的決策,選擇參與樂趣或避免潛在的危險。

該示例使用相同的技術為玩家提供有關完成遊戲主要目標的獎勵的情境意識。在他們收集到所有三個糖果後,通往薑餅屋的門會打開,讓玩家可以進入裡面的禮物。

由於玩家收集糖果的順序並不特定,因此無論玩家最後收集哪個糖果,都必須讓他們意識到門的打開。位置音效使這一點成為可能,讓玩家意識到他們的成功以及接下來需要去的地方,無論他們與門的相對距離和方向如何。

要在示例 Gingerbread House - Complete Audio 位置文件中重建單次事件反饋 3D 音效:

  1. Explorer 窗口中,導航到 WorkspaceDoor,然後:

    1. 插入一個 AudioPlayer 物件以創建音效源。
    2. 插入一個 AudioEmitter 物件以從 Door 發射位置音效流。
    3. 插入一個 Wire 物件以將音效流從音效播放器傳遞到音效發射器。
  2. 選擇 AudioPlayer,然後在 Properties 窗口中,將 AssetID 設置為 rbxassetid://5930776613 以播放滑動金屬門的音軌。

  3. 選擇 Wire,然後在 Properties 窗口中,

    1. SourceInstance 設置為您的新 AudioPlayer,以指定您希望該電線從此特定音效播放器傳遞音效。
    2. TargetInstance 設置為您的新 AudioEmitter,以指定您希望該電線將音效傳遞到此特定的音效發射器。
  4. 返回 Explorer 窗口,導航到 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("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("玩家收集了糖果")
    end
    end
    end
    -- 設置事件監聽器
    for _, gumdrop in gumdrops do
    gumdrop:SetAttribute("Enabled", true)
    gumdrop.Touched:Connect(function(otherPart)
    onGumdropTouched(otherPart, gumdrop)
    end)
    end

    此腳本首先初始化 WorkspacePlayersServerStorageTweenService 服務,以便引用它們的子項和功能。然後,它在 ServerStorage 中要求 LeaderboardPlayerData 模組;這些模組負責創建和更新屏幕右上角的排行榜,跟踪玩家在環境中收集的糖果數量。

    腳本的 updatePlayerGumdrops 函數是觸發事件反饋 3D 音效的主要工作所在,並接受兩個參數:

    • player - 收集糖果的玩家。
    • updateFunction - 更新玩家收集的糖果數量的回調函數。

    當玩家與糖果碰撞時,腳本:

    • 通過調用 PlayerData.updateValue 函數獲取玩家的新糖果收集數量值。
    • 通過調用 Leaderboard.setStat 函數使用此新數量更新排行榜。
    • 檢查數量是否大於或等於 3

    當此值大於或等於 3 時,腳本:

    • 從音效播放器播放 3D 音軌到音效發射器。
    • 將門線性地移動 15 單位到其當前位置下方。

    腳本的其餘部分主要負責檢測與糖果碰撞的任何物體是否為玩家,以便觸發收集反饋的非位置音效。要獲取有關此部分腳本的更多信息,請參見 添加 2D 音效 - 遊戲反饋

  5. 測試遊戲以聽到在環境中收集所有三個糖果後的滑動門音效。當您旋轉相機時,聲音會根據發射器在 3D 空間中的位置在現實世界的揚聲器中動態變化。

物件互動

當玩家與其環境中的 3D 物件互動時,例如打開燈開關或撿起武器,提供即時反饋非常重要,以便他們直觀地理解 如何 與該物件互動。視覺和聽覺反饋的配合強化了玩家行動與環境反應之間的因果關係。

為了擴展這一概念,讓我們回顧以下來自 Plant 示例的單次 3D 音效,該示例展示了種植捲心菜的用戶流程:

  • 當玩家種下種子時,會播放輕微的叮噹聲。
  • 當玩家給正在生長的植物澆水時,會播放濺水聲。
  • 當玩家收集成熟的植物時,會播放剪切聲。
  • 當玩家將捲心菜放入手推車時,會播放輕微的墜落聲。

所有這些聲音都強化了玩家的 接近提示 與物件在 3D 空間中變化形式的關鍵互動。對於視力受損的玩家來說,顏色變化或動畫可能更難以獨立解讀,提供這些多種感官反饋有助於使您的 3D 物件互動對盡可能多的玩家保持可訪問性和直觀性。

為了給出如何配置物件互動的多種感官反饋的不同示例,該示例在玩家踩到薑餅屋內的 3D 薄荷糖按鈕時提供視覺和聽覺反饋。當玩家不與按鈕互動時,它看起來像一顆典型的薄荷糖,但當他們踩到按鈕時,該示例:

  • 播放慶祝音樂音軌。
  • 將按鈕的邊緣染成綠色。
  • 將按鈕向下移入地面。

從這裡,您可以將此互動連接到各種獨特的遊戲行動,例如解鎖物品或觸發特殊能力。

默認視圖
按下狀態

要在示例 Gingerbread House - Complete Audio 位置文件中重建單次物件互動 3D 音效:

  1. Explorer 窗口中,導航到 Workspace3DAudioButton,然後:

    1. 插入一個 AudioPlayer 物件以創建按鈕的音效源。
    2. 插入一個 AudioEmitter 物件以從 3DAudioButton 發射位置音效流。
    3. 插入一個 Wire 物件以將音效流從音效播放器傳遞到音效發射器。
  2. 選擇 AudioPlayer,然後在 Properties 窗口中,將 AssetID 設置為 rbxassetid://1846248593 以播放一首愉快的慶祝音樂音軌。

  3. 選擇 Wire,然後在 Properties 窗口中,

    1. SourceInstance 設置為您的新 AudioPlayer,以指定您希望該電線從此特定音效播放器傳遞音效。
    2. TargetInstance 設置為您的新 AudioEmitter,以指定您希望該電線將音效傳遞到此特定的音效發射器。
  4. 返回 Explorer 窗口,將一個 Script 插入到 3DAudioButton 中,將其重命名為 PlayAudioWhenPressed,然後將以下代碼粘貼到腳本中:

    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)

    該腳本首先獲取:

    • TweenService,以便可以動畫化從地面突出的按鈕部分。
    • 腳本的父級 3DAudioButton 模型。
    • 突出於地面的按鈕部分。
    • 相關的音效播放器,包含您的慶祝音效音軌。

    然後,該腳本定義:

    • 一個 TweenInfo 物件,指定按鈕的動畫將以指數動畫風格播放。
    • 兩個 tween,表示按鈕的按下或未按下狀態。
      • true 按下狀態將按鈕稍微向下移入地面,並將部分的邊緣染成綠色。
      • false 未按下狀態將按鈕移回其原始位置並去除之前的染色。

    腳本的其餘部分是觸發物件互動反饋的主要工作所在,因此讓我們回顧 onIsPlayingChanged 函數和事件監聽器如何協同工作:

    1. buttonPart.Touched 監聽玩家觸碰按鈕,然後調用 Play() 函數開始播放音效播放器中的相關音效。這一過程將 AudioPlayer.IsPlaying 屬性從 false 切換為 true
    2. buttonPressedAudioPlayer:GetPropertyChangedSignal("IsPlaying") 監聽音效播放器的 IsPlaying 屬性變化,然後調用 onIsPlayingChanged 函數。
    3. onIsPlayingChanged 函數利用這一信息觸發 tween,改變其在 3D 空間中的視覺外觀。
    4. 為了防止玩家在快速跳上按鈕時意外重新啟動音效,buttonPressedAudioPlayer.Ended 監聽音效播放器播放結束後再次調用 onIsPlayingChanged 函數。

    重要的是要注意,onIsPlayingChanged 事件僅在從 false 變為 true 時觸發,這意味著在從 true 變為 false 時不會觸發。這是預期的行為,因為從伺服器到客戶端複製屬性的時機會出現問題。因此,在此示例中提供並監聽 Ended 事件以涵蓋這兩種情況。

  5. 測試遊戲以聽到當您的玩家角色觸碰薑餅屋中的 3D 按鈕時的慶祝音效。當您遠離按鈕時,聲音的音量會減少。

角色對話

提供來自非可玩角色 (NPC) 的方向性音效有助於指導玩家前往其環境中的興趣點,並為他們與其他角色的互動增添深度。事實上,在以敘事為驅動的遊戲中,許多設計師會策略性地使用角色對話來間接教導玩家有關他們的角色、盟友和敵人角色或世界本身的知識。

這種技術的常見示例包括:

  • 對話風格 以設置遊戲的基調。
  • 玩笑 以教導玩家有關角色關係的知識。
  • 敵人對話 以坦白動機或他們與玩家的關係。
  • 玩家角色大聲說出自己的想法 以輕柔地指導玩家該做什麼,例如治療自己、移動到另一個位置或尋找物品。
  • 盟友角色與玩家角色交談 以揭示遊戲世界的細節,例如其歷史、文化和社會問題。

為了演示這在實踐中可能是什麼樣子,讓我們回顧以下來自 Beyond the Dark 展示的單次 3D 音效,該音效會在玩家位於太空站的主大廳區域時定期播放。

使用太空站作為角色,這段對話片段為玩家提供了有關整體設置的重要上下文和背景。例如,從這句話中玩家了解到:

  • 他們身處外太空,具體來說是在名為 Kerr-Newman Deep Space Relay 14 的太空站上。
  • 他們的環境是未來主義的且令人愉快。
  • 他們是訪客,可能很快就會離開。

這些細節共同使玩家沉浸在他們的環境中,並為他們的任務增添了一種緊迫感。然而,如果玩家不知道他們的主要任務是什麼,您也可以使用角色對話來告知玩家您希望他們在遊戲中做什麼。

為了說明,該示例使用一個 音量,或在 3D 空間中的隱形區域,來觸發雪人的角色對話,以指導玩家收集三個糖果以打開通往他家門的門。作為玩家加入遊戲時看到的第一件事之一,玩家更有可能觸發對話並知道他們需要做什麼才能成功。

雪人周圍的音量使用碰撞反饋在玩家進入 3D 區域時播放音效。

要在示例 Gingerbread House - Complete Audio 位置文件中重建單次角色對話 3D 音效:

  1. Explorer 窗口中,導航到 WorkspaceDialogueVolume,然後:

    1. 插入一個 AudioPlayer 物件以創建音效源。
    2. 插入一個 AudioEmitter 物件以從 DialogueVolume 發射位置音效流。
    3. 插入一個 Wire 物件以將音效流從音效播放器傳遞到音效發射器。
  2. 選擇 AudioPlayer,然後在 Properties 窗口中,將 AssetID 設置為 rbxassetid://92917410841704 以播放有關遊戲目標的指導音軌。

  3. 選擇 Wire,然後在 Properties 窗口中,

    1. SourceInstance 設置為您的新 AudioPlayer,以指定您希望該電線從此特定音效播放器傳遞音效。
    2. TargetInstance 設置為您的新 AudioEmitter,以指定您希望該電線將音效傳遞到此特定的音效發射器。
  4. 返回 Explorer 窗口,導航到 StarterPlayerStarterCharacterScripts,插入一個 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)

    該腳本首先獲取 Workspace 和 Players 服務,以便引用它們的子項和功能。對於每個加載或重生回到遊戲中的玩家角色,該腳本會等待:

    • 角色的 HumanoidAnimator 物件。
    • 工作區中名為 DialogueVolume 的音量物件。

    當任何物體與音量碰撞時,Touched 事件處理函數獲取第一個祖先,該祖先是 Model,如果與音量碰撞的 BasePart 是角色模型的子項,則應該是角色。如果是,則該函數會:

    • 將 debounce 設置為 true
    • 播放並等待音效結束。
    • 將 debounce 設置回 false

    將 debounce 從 false 變為 true,然後在音效播放結束後再次設置為 false 是一種防抖模式,防止音效在玩家不斷與音量碰撞時重複觸發。要獲取有關此防抖模式的更多信息,請參見 防抖 - 檢測碰撞

  5. 測試遊戲以聽到當您的玩家角色觸碰雪人周圍的音量時的指導角色對話音效。

©2026 Roblox Corporation、Roblox、Roblox 標誌及 Powering Imagination 是我們在美國及其他國家地區的部分註冊與未註冊商標。