Godot入門到棄坑:來點動靜


3樓貓 發佈時間:2024-06-28 11:32:26 作者:cameLcAsE Language

不過這篇文章不會只講怎麼播放聲音。
另外由於聲音工程是另一個專業領域,所以我也不好說太多,也難以避免紕漏,見諒。

使用什麼節點

在Godot中使用AudioStreamPlayer(音頻流播放器)來播放聲音。AudioStreamPlayer還有兩個子類分別叫AudioStreamPlayer2D和AudioStreamPlayer3D。它們各自可以在2D和3D場景中根據聽者和聲音來源的位置關係來調整聲音的效果,以達到身臨其境的感受。
但是我們不用在任何時候都使用這兩個子類,某些聲音在播放時並不需要考慮位置,比如UI交互的音效或者一些BGM,這時可以直接使用AudioStreamPlayer。

音頻總線

音頻總線(audio bus)亦稱通道/頻道(channel)。遊戲最終輸出到揚聲器的聲音由若干總線的聲音混合而成。在編輯器底部的Audio面板中可以看到目前各總線的狀況:
這個面板引用的資源被稱為總線佈局(bus layout)。左上方的文字可以看到這裡引用的是默認的佈局資源。右側的若干按鈕可以對其進行編輯。
下方的區域用以容納若干總線。最左側的Master(主總線)是一個無法被刪除的總線,揚聲器中最終播放出來的聲音就來自這裡。現在我又添加了兩個總線,分別用以控制背景音樂和音效:
位於右側的總線播出來的聲音總是會輸出到左側的某個中線中去,總線層層相連最終混入到Master總線播放出來。注意總線下方的下拉選擇器。最左側的Master總線下方的菜單被禁用了,因為它會向揚聲器(speaker)輸出聲音,這裡無法被調整。而右側的BGM總線可以選擇輸出到哪裡,但是它的左側只剩Master總線,所以也無法進一步修改。而最右側SFX總線就可以選擇輸出到Master或者BGM。
中間類似於聲音滑條的控件確實是控制音量的。但是你可能看到默認的音量是0。這裡的單位為分貝(dB),Godot採用的公式是20 × log10(P/P0)。這是對一個比值求以10為底的對數的20倍。這個比值為1時就是0dB。比值小於1(往下調降低音量)就是負數。為避免失真,主總線不應當超過0dB。
Add Effect可以為總線添加效果,可以自行嘗試。

播放什麼

要指定AudioStreamPlayer播放的內容,需要控制其stream屬性。一般來講這個屬性的值是你的某個音頻素材(資源)。
Godot目前支持三種音頻格式:WAV、Ogg、MP3。Sunny Land素材包中的sound文件夾中有一段聲音可以用來測試一下。當然你也可以像導入其它資源那樣自行導入各種音頻資源。
在場景中添加一個AudioStreamPlayer節點。注意AudioStreamPlayer派生自Node節點,它的位置並不重要,它根本沒有位置相關屬性。
stream屬性就是它要播的內容,既可以在檢視面板中直接設置,也可以在運行時根據需要賦值。Autoplay屬性可以控制是否自動播放聲音。最下方的總線選項就是從當前的總線佈局中選擇我們設置的各總線,也就是這個AudioStreamPlayer要把聲音播放到哪個總線中。
在代碼中像這樣就可以播放出聲音:
這裡偷懶直接用load函數加載音頻文件然後播放(路徑太長就只截一點了)。play方法顧名思義。它有一個可選參數可以指定從哪裡(秒)開始播放。
要在運行時修改播放的內容,直接修改stream屬性的值即可。

問題

在遊戲中時常會發生場景切換,但是這個行為並不一定伴隨著音樂的更改,比如我們可能希望在幾個小場景中都播放同一個BGM。另外,如果播放的聲音不需要空間效果,我們也沒必要每個場景都放上不同的AudioStreamPlayer。另外,即使場景在不斷變化,我們也可能希望一些音樂連續不斷地播放。
簡言之,我們希望在不同的場景之間保持一些東西的可用性——用人話來說就是隨時都能夠訪問和操作這些數據,並且它們是到處都可以共享的。比如我們在不同的場景中都可以訪問一個AudioStreamPlayer並且存取其在播放的音樂。
你也許在想,是否可以用之前做存檔的思路來持久化這些數據呢?可以。但是通過磁盤讀寫數據再快也是要費時間的,這個時間比起訪問內存的時間來說還是太長了,而這個時間本身是不用費的,因為這些數據本身就在內存中,來回倒騰實在是沒必要。
GDScript內置了若干的全局函數和全局變量,這些東西我們在任何地方都可以用。但是目前我們並不能自行定義這樣的函數和變量。
除此之外我們目前能夠想到的能夠“隨時隨地”訪問的就只有腳本中的靜態成員。

靜態類和靜態成員

首先Godot在語言層面並不存在“靜態類”這一概念。在存在靜態類概念的語言中,其指的就是隻含靜態成員的類。當然非靜態類也可以只包含靜態成員,但是非靜態類將可以被多次實例化——對於一個只有靜態成員的類來說這是沒有必要的。因此使類成為靜態類可以施加這一限制。
類中的靜態成員表達的是一些和具體對象無關的數據,但是要是我們希望隨時隨地訪問一個AudioStreamPlayer,我們還是得有一個AudioStreamPlayer的實例才行。比如我們新建一個腳本:
這樣的代碼已經可以讓我們在任何場景中都可以訪問這個AudioStreamPlayer。但是由於AudioStreamPlayer它是一個節點——一個Node類型的對象,意味著我們通常需要把它放到一個場景樹中它才能正常工作。實際上對於AudioStreamPlayer來說,你可以不把它放到場景樹中,但是要調用它的play方法的話必須把它加入場景樹,否則就會報錯,你可以嘗試一下。但是這樣一來它幾乎等於沒有任何用處。
為了複用這個AudioStreamPlayer,我們必須在進入和離開場景時手動地將這個AudioStreamPlayer加入和移出場景樹才能聽到它播放的內容,這很麻煩。
另外由於GDScript的設計,我們無法控制類中的變量的訪問權限,我們隨時可以任意修改這些靜態成員所指的對象。

靜態成員可以怎麼用

但是這並不意味著只包含靜態成員的類沒有用。我們可以把一些純函數作為靜態成員放在這樣的腳本中便於隨時訪問。這裡說的“純函數”(pure function)指的是什麼呢?純函數指的是一些在給定輸入下總是會返回同樣的結果,並且不會造成任何副作用的函數。換句話說這些函數只依賴輸入,且不會修改任何外部對象的狀態。
這裡用辛普森法估計定積分的值。這樣的函數可以是也應該是一個純函數(只要f也是一個純函數),這樣的函數單純地做計算,它也不會依賴、修改場景樹,我們可以簡單地把它作為一個靜態函數定義。

AutoLoad

那麼如何正確地複用AudioStreamPlayer呢?
Autoload可以解決這樣的問題。Autoload是Godot中的一個具體的概念,顧名思義它是會在遊戲開始後自動加載的腳本(或場景)。
首先我們想象一下一個方便的播放聲音的腳本是怎樣的。對於一些和位置無關的BGM、音效的播放來說,我們希望傳入一個聲音資源就可以播了,我們不太關心其它的事情。
所以我們先不管其它的,新建一個場景,叫它SoundPlayer或者其它你喜歡的名字(如果前面你跟著我講的內容也創建了一個叫SoundPlayer的腳本這裡建議把它先刪掉)。注意,這裡場景根節點的基類建議選擇Node(但至少要選擇Node)。
為其添加AudioStreamPlayer和腳本,我們對我們要複用的AudioStreamPlayer進行一個簡單地包裝,或者專業一點叫它封裝(encapsule)也行:
play方法現在簡單地加載傳入的資源路徑,然後替換掉它播放的stream並播放。
現在把它轉換成AutoLoad。打開項目設置,選擇AutoLoad選項卡。然後添加SoundPlayer場景。注意要選擇SoundPlayer場景而不是腳本。AutoLoad同時支持場景和腳本,如果你的AutoLoad不需要場景樹那麼單純的腳本也可以,但是我們的AudioStreamPlayer需要在場景樹中工作,我們的腳本也引用了場景中的節點,所以這裡要添加場景。
默認情況下Global Variable(全局變量)選項啟用,之後我們就可以通過SoundPlayer這個名字來訪問這個場景腳本中定義的各種方法了。
你也可以按照同樣的方法為這個SoundPlayer添加更多的功能,讓它更加靈活。
簡單來說,AutoLoad會在進入各個場景時自動加入場景樹,保證AutoLoad始終是可用的。這裡借用的Godot文檔中的圖,Godot有時候也把AutoLoad稱為singleton(就是前面提到過的單例)。
在遊戲運行時可以看到作為AutoLoad的SoundPlayer加入到了場景樹中。
切換場景時,音樂會持續播放,也可以隨時切換音樂。
另外,我前面也提到,用路徑來引用資源不太安全。你可以如法炮製,把各個BGM資源組織成一個個靜態變量或者AutoLoad中的屬性來便於訪問。

© 2022 3樓貓 下載APP 站點地圖 廣告合作:asmrly666@gmail.com