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 環境中。
- 角色的子 AudioListener 物件在 3D 環境中接收該聲音並將其反饋到他們的耳機中。
- AudioDeviceOutput 物件將聲音從 AudioListener 傳遞到玩家的物理揚聲器,或在這種情況下,傳遞到耳機中。
- AudioDeviceInput 物件捕獲來自現實世界的聲音,並將其反饋到體驗中,以便進行語音聊天。


接下來的章節將深入介紹這些物件,幫助您學習如何播放循環和單次 3D 音效。在您回顧這些物件和即將到來的技術時,您可以更準確地預測如何捕捉和反饋音效從體驗到玩家,再到反向。
循環音效
循環 3D 音效,或方向性音效會在玩家連接到服務器時無縫重播,是一種常見的聲音設計技術,用於增強 3D 空間的氛圍,讓其感覺更生動和動態。此外,循環 3D 音效保持你的環境聲音源一致,例如電視的靜電聲或瀑布的轟鳴聲;如果這些聲音突然停止,環境就會感覺不真實。
為了演示這一概念,請檢查以下兩個大瀑布的 3D 音效如何在不循環的音軌完成後立即停止。雖然水聲最初會讓玩家沉浸在戶外環境中,但突然的聲音變化卻是令人震驚和不自然的,這與現實世界中的瀑布行為不符。
同樣,範例使用此技術對流動的巧克力瀑布進行調整,並根據玩家與音源的距離調整音量。當玩家距離小於 20 個單位時,音源以全音量發出聲音。隨著玩家的移動,從音源距離每增加 20 個單位,水濺的音效音量會減少。這模擬了現實世界中聲音隨著離源的距離而減少的效果。
要在範例 Gingerbread House - Complete Audio 放置文件中重新創建循環 3D 音效:
啟用連接到玩家角色的默認聽者。
- 在 Explorer 窗口中,選擇 SoundService。
- 在 Properties 窗口中,將 DefaultListenerLocation 設置為 Character。當您運行體驗時,引擎會自動:
- 在每個玩家角色的 Humanoid.RootPart 下創建一個 AudioListener,以便您可以根據體驗中聲音源的位置和音量變化來聽到聲音的變化。
- 在 SoundService 下創建一個 AudioDeviceOutput。
在 Explorer 窗口中,導航到 Workspace ⟩ WaterfallAudioObject,然後:
- 插入一個 AudioPlayer 物件來創建瀑布的音頻源。
- 插入一個 AudioEmitter 物件以從 WaterfallAudioObject 發射定位音頻流。
- 插入一個 Wire 物件來攜帶音頻流從音頻播放器到音頻發射器。

選擇 AudioPlayer,然後在 Properties 窗口中,
- 將 AssetID 設置為 rbxassetid://1516791621 以播放下雨的音軌。
- 啟用 Looping 以便音效無縫重播。
選擇 AudioEmitter,然後在 Properties 窗口中,將 DistanceAttenuation 設置為 {0: 1}, {20: 0.8}, {40: 0.4}, {80: 0},使音量隨著與音源距離的增加而逐漸減少,每 20 個單位減少一次音量。
選擇 Wire,然後在 Properties 窗口中,
- 將 SourceInstance 設置為您的新 AudioPlayer,以指定您希望這條線從這個特定的音頻播放器攜帶音頻。
- 將 TargetInstance 設置為您的新 AudioEmitter,以指定您希望這條線將音頻攜帶到這個特定的音頻發射器內。
回到 Explorer 窗口,在 WaterfallAudioObject 中插入一個 Script,將其重新命名為 LoopWaterfallMusic,將其 RunContext 屬性設置為 Client,然後將以下代碼粘貼到腳本中:
local audioPlayer = script.ParentaudioPlayer:Play()代碼解釋腳本開始宣告一個變數來表示腳本的父級 AudioPlayer。然後腳本設置音頻源在玩家進入體驗後立即播放,直到他們退出體驗。
測試體驗,當您的角色靠近瀑布時,可以聽到循環的巧克力雨聲。當您轉動角色的頭部以查看不同的方向時,聲音會根據發射器在 3D 空間中的位置動態變化,反映在您的現實揚聲器中。
單次音效
單次 3D 音效,或在特定時間和位置播放一次的方向性音效,除非玩家再次觸發它,否則提供玩家有關他們行動、環境和周圍任何角色的上下文。在您的體驗中使用這種類型的聽覺反饋是至關重要的,因為它允許玩家做出戰略決策,比如避免來襲的敵人或撿起有用的物品。
以下部分提供需要及時、方向性反饋的常見遊戲場景的實施細節,包括情境遊戲事件、物件互動和非玩家角色對話。
事件反饋
當玩家在他們的環境中觸發關鍵情境事件時,例如解鎖新的遊戲區域或觸發敵人的攻擊,他們必須了解在 3D 空間中需要將注意力集中在哪裡。如果他們沒有收到即時的聽覺反饋,他們可能會錯過對他們的遊戲至關重要的信息,導致不知道該去哪裡或接下來做什麼而感到沮喪。
為了演示這一點,我們來檢查從 Laser Tag 模板中播放的單次 3D 音效,它會從每個玩家的槍中播放:
- 每次玩家從槍中射出時都會播放一個深沉的砰砰聲。
- 每次玩家用新子彈重新裝填槍時都會播放一次咔嗒聲和機器人式的嘟嘟聲。
這兩種聲音都提供了情境意識,通過提醒所有附近的玩家射擊聲音的方向,讓他們能做出明智的決策,以便加入樂趣或避免潛在的危險。
範例使用相同的技術為玩家提供有關完成主要目標的獎勵的情境意識。在他們收集到所有三顆糖果後,薑餅屋的門會打開,讓玩家進入裡面的禮物。
因為玩家在收集糖果時並沒有特定的順序,因此玩家必須意識到門的開啟,無論他們最後收集的是哪顆糖果。定位音效使這成為可能,讓玩家了解自己的成功,以及他們接下來需要去的地方,而不論他們與門的相對距離和方向。
要在範例 Gingerbread House - Complete Audio 放置文件中重新創建單次事件反饋的 3D 音效:
在 Explorer 窗口中,導航到 Workspace ⟩ Door,然後:
- 插入一個 AudioPlayer 物件來創建音量的音頻源。
- 插入一個 AudioEmitter 物件以從 Door 發射定位音頻流。
- 插入一個 Wire 物件來攜帶音頻流從音頻播放器到音頻發射器。

選擇 AudioPlayer,然後在 Properties 窗口中,將 AssetID 設置為 rbxassetid://5930776613 以播放滑動金屬門的音軌。
選擇 Wire,然後在 Properties 窗口中,
- 將 SourceInstance 設置為您的新 AudioPlayer,以指定您希望這條線從這個特定的音頻播放器攜帶音頻。
- 將 TargetInstance 設置為您的新 AudioEmitter,以指定您希望這條線將音頻攜帶到這個特定的音頻發射器內。
回到 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.Gumdropslocal gumdrops = gumdropsFolder:GetChildren()local GUMDROP_KEY_NAME = PlayerData.GUMDROP_KEY_NAMElocal GUMDROP_AMOUNT_TO_ADD = 1local 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.AudioPlayeraudioPlayer:Play()-- 動畫門向下移動local doorPart = Workspace.Doorlocal tweenInfo = TweenInfo.new(2, Enum.EasingStyle.Linear)local tween = TweenService:Create(doorPart, tweenInfo, {Position = doorPart.Position + Vector3.new(0, -15, 0)})tween:Play()endend-- 定義事件處理器local function onGumdropTouched(otherPart, gumdrop)if gumdrop:GetAttribute("Enabled") thenlocal character = otherPart.Parentlocal player = Players:GetPlayerFromCharacter(character)if player then-- 玩家觸碰到了一顆糖果local audioPlayer = gumdrop.AudioPlayeraudioPlayer:Play()gumdrop.Transparency = 1gumdrop:SetAttribute("Enabled", false)updatePlayerGumdrops(player, function(oldGumdropAmount)oldGumdropAmount = oldGumdropAmount or 0return oldGumdropAmount + GUMDROP_AMOUNT_TO_ADDend)print("玩家收集了糖果")endendend-- 設定事件監聽器for _, gumdrop in gumdrops dogumdrop:SetAttribute("Enabled", true)gumdrop.Touched:Connect(function(otherPart)onGumdropTouched(otherPart, gumdrop)end)end代碼解釋此腳本首先初始化 Workspace、Players、ServerStorage 和 TweenService 服務,以便能夠引用其子項和功能。然後,它從 ServerStorage 中要求 Leaderboard 和 PlayerData 模組;這些模組負責創建和更新位於屏幕右上角的排行榜,該排行榜跟踪玩家在環境中收集的糖果數量。
腳本的 updatePlayerGumdrops 函數是觸發事件反饋的 3D 音效的大部分工作所在,它接受兩個參數:
- player - 收集糖果的玩家。
- updateFunction - 更新玩家已收集糖果數量的回調函數。
當玩家與糖果發生碰撞時,該腳本:
- 通過調用 PlayerData.updateValue 函數獲取玩家的新糖果收集數量值。
- 通過調用 Leaderboard.setStat 函數用這個新數量更新排行榜。
- 檢查該數量是否大於或等於 3。
當此值大於或等於 3 時,腳本:
- 從音頻播放器播放 3D 音軌到音頻發射器。
- 將門向下線性移動 15 個單位。
腳本的其餘部分基本上負責檢測與糖果碰撞的物體是否為玩家,這樣可以觸發收集反饋的非定位聲音。要獲取有關腳本這部分的更多信息,請參見 添加 2D 音頻 - 遊戲反饋。
測試體驗,收集環境中的所有三顆糖果後,能聽到滑動門的音效。當您旋轉攝像機時,聲音在您的現實揚聲器中發生動態變化,以便您根據發射器在 3D 空間中的位置聽到它。
物件互動
當玩家與他們環境中的 3D 物件互動時,例如開啟燈開關或撿起武器,提供即時反饋非常重要,以便他們直觀地理解他們與物件的互動 是如何發生的。視覺和聽覺反饋的結合強化了玩家的行動與環境反應之間的因果關係。
為了擴展這一概念,讓我們回顧以下來自 Plant 範例的單次 3D 音效,用於種植捲心菜的用戶流程:
- 當玩家種下種子的時候,會播放輕柔的叮噹聲。
- 當玩家給正在生長的植物澆水時,會播放一種潮濕的潑水聲。
- 當玩家收集成熟的植物時,會播放一種剪切聲音。
- 當玩家將捲心菜放入小車時,會播放一種柔軟的墜落聲。
這些聲音增強了玩家的 接近提示 與物件在 3D 空間中形狀變化的主要互動。對於視覺障礙的玩家來說,顏色變化或動畫本身可能更難理解,提供這些多種感官反饋幫助您的 3D 物件互動對於盡可能多的玩家來說仍然保持可訪問和直觀。
要提供不同的示例,說明如何使用多種感官反饋配置物件互動,範例在玩家踩到薑餅屋內的 3D 薄荷按鈕時,提供視覺和聽覺反饋。當玩家不與按鈕互動時,它看起來像一塊典型的薄荷糖,但當他們踩上按鈕時,範例:
- 播放慶祝曲。
- 按鈕的側面被染成綠色。
- 按鈕向地面移動。
從這裡,您可以將這一互動連接到各種獨特的遊戲行為,例如解鎖物品或觸發特殊能力。


要在範例 Gingerbread House - Complete Audio 放置文件中重新創建單次物件互動的 3D 音效:
在 Explorer 窗口中,導航到 Workspace ⟩ 3DAudioButton,然後:
- 插入一個 AudioPlayer 物件來為按鈕創建音頻源。
- 插入一個 AudioEmitter 物件以從 3DAudioButton 發射定位音頻流。
- 插入一個 Wire 物件來攜帶音頻流從音頻播放器到音頻發射器。

選擇 AudioPlayer,然後在 Properties 窗口中,將 AssetID 設置為 rbxassetid://1846248593 以播放愉快的慶祝音軌。
選擇 Wire,然後在 Properties 窗口中,
- 將 SourceInstance 設置為您的新 AudioPlayer,以指定您希望這條線從這個特定音頻播放器攜帶音頻。
- 將 TargetInstance 設置為您的新 AudioEmitter,以指定您希望這條線將音頻攜帶到這個特定的音頻發射器內。
回到 Explorer 窗口,在 3DAudioButton 中插入一個 Script,將其重新命名為 PlayAudioWhenPressed,然後將以下代碼粘貼到腳本中:
local TweenService = game:GetService("TweenService")local buttonModel = script.Parent.Parentlocal buttonPart = buttonModel.ButtonPartlocal buttonPressedAudioPlayer = buttonModel.ButtonPressedAudioPlayerlocal 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.IsPlayinglocal tween = buttonTweenByIsPressed[isPlaying]tween:Play()endonIsPlayingChanged()buttonPressedAudioPlayer:GetPropertyChangedSignal("IsPlaying"):Connect(onIsPlayingChanged)buttonPressedAudioPlayer.Ended:Connect(onIsPlayingChanged)buttonPart.Touched:Connect(function(_hit)buttonPressedAudioPlayer:Play()end)代碼解釋此腳本首先獲取:
- TweenService,以便能夠動畫化突出於地面的按鈕部件。
- 腳本的父級 3DAudioButton 模型。
- 突出於地面的按鈕部件。
- 與您的慶祝聲音音軌相關的音頻播放器。
腳本然後定義:
- 一個 TweenInfo 物件,指定按鈕的動畫將以指數動畫樣式播放。
- 兩個表示按鈕被按下或未按下狀態的 tween。
- true 被按下狀態會使按鈕稍微向下移動進入地面,並將部件的側面染成綠色。
- false 未按下狀態將按鈕移回其原始位置並去掉之前的染色。
腳本的其餘部分涉及到實現物件互動反饋的主要工作,讓我們來檢查 onIsPlayingChanged 函數和事件監聽器如何協同工作:
- buttonPart.Touched 監聽玩家觸摸按鈕,然後調用 Play() 函數以開始播放來自音頻播放器的相關音效。這一過程將 AudioPlayer.IsPlaying 屬性從 false 切換到 true。
- buttonPressedAudioPlayer:GetPropertyChangedSignal("IsPlaying") 監聽音頻播放器的 IsPlaying 屬性變化,然後調用 onIsPlayingChanged 函數。
- onIsPlayingChanged 函數利用這個信息來觸發在 3D 空間中改變其視覺外觀的動畫。
- 為了防止玩家如果快速接連脫離按鈕再次啟動音效,buttonPressedAudioPlayer.Ended 監聽音頻播放器播放結束後,才能再次調用 onIsPlayingChanged 函數。
重要的是,onIsPlayingChanged 事件僅在從 false 變為 true 時觸發,即當變為 true 到 false 時不會觸發。這是預期的行為,由於服務器到客戶端屬性複製的時間問題。正因如此,這個示例提供並監聽 Ended 事件以覆蓋這兩種情況。
測試體驗,以便在您的玩家角色觸摸薑餅屋中的 3D 按鈕時聽到慶祝音效。當您離開按鈕時,聲音的音量會減少。
角色對話
為您的非玩家角色 (NPC) 提供方向性音效有助於引導玩家朝向其環境中的重要點,並增加他們與其他角色之間互動的深度。事實上,在敘事驅動的體驗中,許多設計師會策略性地使用角色對話間接教導玩家有關他們的角色、盟友和敵對角色或整個世界的知識。
這種技術的常見示例包括:
- 對話風格 以設定您體驗的基調。
- 玩笑 以教導玩家有關角色之間的關係。
- 敵人之間的對話 以表達動機或他們與玩家的關係。
- 玩家角色大聲表達自己的想法,以輕柔地指導玩家應該接下來做什麼,例如治癒自己、移動到其他位置或找到某個物品。
- 盟友角色與玩家角色交談,以揭示遊戲內世界細節,如其歷史、文化和社會問題。
為了展示這在實踐中可能是什麼樣的,我們來檢查 Beyond the Dark 展示中從玩家進入太空站主要大廳時定期播放的單次 3D 音效。
將太空站作為一個角色,此對話片段為玩家提供了有關整體環境的重要上下文和傳說。例如,僅從這一句話中,玩家可以了解到:
- 他們正身處外太空,特別是在一個名為 Kerr-Newman Deep Space Relay 14 的太空站上。
- 他們的環境是未來主義的並且很受歡迎。
- 他們是訪客,並且很可能會很快離開。
這些細節共同將玩家融入到他們的環境中,並為他們的任務增添了一種緊迫感。然而,如果玩家不知道他們的主要任務是什麼,您還可以使用角色對話告知玩家您希望他們在您的體驗中做什麼。
為了說明,範例使用一個 音量,或 3D 空間中的隱形區域,觸發來自雪人的角色對話,以引導玩家收集三顆糖果來打開通往他們家的門。由於玩家在進入體驗時最先看到的事物之一,玩家更有可能觸發這段對話並知道他們需要做什麼才能成功。

要在範例 Gingerbread House - Complete Audio 放置文件中重新創建單次角色對話的 3D 音效:
在 Explorer 窗口中,導航到 Workspace ⟩ DialogueVolume,然後:
- 插入一個 AudioPlayer 物件來創建音量的音頻源。
- 插入一個 AudioEmitter 物件以從 DialogueVolume 發射定位音頻流。
- 插入一個 Wire 物件來攜帶音頻流從音頻播放器到音頻發射器。

選擇 AudioPlayer,然後在 Properties 窗口中,將 AssetID 設置為 rbxassetid://92917410841704 以播放關於體驗目標的指導音軌。
選擇 Wire,然後在 Properties 窗口中,
- 將 SourceInstance 設置為您的新 AudioPlayer,以指定您希望這條線從這個特定音頻播放器攜帶音頻。
- 將 TargetInstance 設置為您的新 AudioEmitter,以指定您希望這條線將音頻攜帶到這個特定音頻發射器內。
回到 Explorer 窗口,導航到 StarterPlayer ⟩ StarterCharacterScripts,插入一個 LocalScript,將其重新命名為 PlayAudioWhenInVolume,並將以下代碼粘貼到本地腳本中:
local Workspace = game:GetService("Workspace")local Players = game:GetService("Players")local humanoid = script.Parent:WaitForChild("Humanoid")local volumeDetector = Workspace.DialogueVolumelocal trigger = humanoid:WaitForChild("Animator")local debounce = falselocal localPlayer = Players.LocalPlayervolumeDetector.Touched:Connect(function(hit)if debounce thenreturnendlocal hitCharacter = hit:FindFirstAncestorWhichIsA("Model")local hitPlayer = Players:GetPlayerFromCharacter(hitCharacter)if hitPlayer ~= localPlayer thenreturnenddebounce = truelocal audioPlayer = Workspace.DialogueVolume.AudioPlayeraudioPlayer:Play()audioPlayer.Ended:Wait()debounce = falseend)代碼解釋此腳本開始通過獲取 Workspace 和 Players 服務以便能夠引用他們的子項和功能。對於每個載入或重生進入體驗的玩家角色,腳本等待:
- 工作區中名為 DialogueVolume 的體積物件。
當任何物體與該音量發生碰撞時,Touched 事件處理程序函數會獲取第一個祖先是 Model 的物體,這應該是角色,如果碰撞的 BasePart 是角色模型的後代。如果是,函數則:
- 將 debounce 設置為 true。
- 播放並等待音頻結束。
- 將 debounce 再次設置為 false。
將 debounce 從 false 設置為 true 然後再設置為false 是一種防抖模式,可以防止音頻在玩家不斷與音量碰撞時重複觸發。要獲取有關此防抖模式的更多信息,請參見 防抖 - 偵測碰撞。
測試體驗,當您的玩家角色觸摸雪人周圍的音量時,能聽到指導角色的對話音效。