淺析遊戲中的音頻GameObject管理

2023-11-21     奇億音樂

原標題:淺析遊戲中的音頻GameObject管理

GameObject是使用Wwise進行音頻設計的基礎理念,從Wwise基礎功能的使用(尤其是Profiling),到使用WwiseSDK開發都會涉及這一概念。筆者在日常工作中遇到過不少因為音頻GameObject管理不善而引發的問題。故而嘗試對音頻GameObject的註冊與管理方法進行一定的梳理。

首先我們來介紹一下關於本問題的相關背景和一些基礎概念。

1、什麼是GameObject?

首先我們來統一一下概念。

為了避免大家和廣義上遊戲引擎中的GameObject以及Object-Based Mixing等概念相混淆,此處的定義以Wwise引擎中關於GameObject的定義為準:

同時為了與Wwise的介面及文檔統一,後文中也統一使用(Wwise中的)GameObject這一名稱。

2、為什麼要進行音頻對象管理?

由上文可知,GameObject是Wwise中的核心概念——Wwise對音頻的管理都是基於管理GameObject來實現的。

可以說,在使用Wwise的情況下,遊戲中任何聲音的播放都離不開GameObject(無論聲音是2D/3D)。

若音頻GameObject管理不善,會導致各種問題——輕則Wwise內的功能異常或失效,重則在遊戲引擎開發層面留下隱患。

3、以GameObject為單位管理音頻是Wwise獨有的麼?有什麼特別之處?

以GameObject為單位(Object-based)管理音頻並非Wwise獨有,也存在其他音頻引擎(或音頻管理器實現)支持這一管理方式。

  • 不使用GameObject為單位管理音頻的音頻管理器實現,通常是以事件為單位(Event-based)來管理音頻——這種情況下,音頻設計師只能從Event維度進行控制。
  • 而Wwise引擎則提供了GameObject和Event兩個維度供設計師使用(儘管這裡Event也並非絕對的脫離GameObject),這使得聲音設計的靈活性與表現力都得到了提升。

Wwise Event、GameObject與UE Component的關係梳理

為了方便理解GameObject的重要性及其工作原理,我們以一個最簡單的藍圖接口實現為例,看一下GameObject是如何在代碼層面被創建和引用的。

由於Audiokinetic(Wwise的開發公司)官方維護有一套功能豐富的Unreal引擎集成,因此Unreal引擎集成當中對於Wwise的SDK的用法具有一定的代表性,故而筆者在此處以PostEvent藍圖接口為例來論述。

以下截圖為Unreal中PostEvent藍圖節點的C++代碼:

Unreal中Postevent藍圖節點定義:

該段代碼對應該藍圖介面:

該藍圖節點上AkEventActor兩個輸入點必須連入變量或具體的值,否則將會在編譯時報錯

代碼邏輯簡單講就是:運行PostEvent節點時會給指定Actor對象的RootComponent層級附加AkComponent

附加AkComponent的過程,首先遍歷當前Actor的子級:

如果已有AkComponent則直接啟動,

如果沒有,那就新建一個AkComponent

然後AkComponent會通過類別轉換和Wwise內的GameObject進行強綁定。

所以,每個Actor播放Wwise的聲音時,AkComponent必須存在。

AkComponent的作用:

AkComponent就是存在與UE中的Wwise專用的GameObject(的容器),其可以調用到以下函數:

純看代碼可能不方便部分沒有代碼基礎的同學理解相關概念。

接下來我們換一個更加通俗易懂的方法來描述下這個邏輯:遊戲引擎使用音頻中間件播放聲音的邏輯是這樣的:

總結(第一節)

(Wwise中的)GameObject的數據來源是遊戲引擎中的AkComponent,且該來源不可或缺。(數據可能包括方位、轉向、GameSync等等信息。)

可以說每個聲音的播放都指向了一個目標對象——AkComponent。

Wwise會為目標對象註冊一個與之匹配的(Wwise中的)GameObject來承載聲音,其基礎屬性全部從AkComponent繼承。

所有的以AkComponent為目標對象Post的Event都是以該(Wwise中的)GameObject為目標的。

如果使用一個感性的說法:

(Wwise中的)GameObject就是某個人(AkComponent)在聲音世界的分身,負責承載所有指向這個人的事件。

另外,(Wwise中的)GameObject除可依據AkComponent被註冊,也可被解註冊。

明白了這些以後我們可以做什麼呢?

二、Object-based音頻管理與Event-based音頻管理之間的潛在矛盾

1、什麼是Event-based音頻管理

早期的遊戲音頻中間件或者小體量遊戲的音頻管理器實現基本都是以Event-based的理念來管理遊戲中的音頻的。而現如今,通常遊戲中的每一個聲音都是一個獨立的對象。

偶爾會有一些接手音頻開發的同學在缺乏考慮的情況下選擇簡單粗暴的方式來實現音頻播放的管理與控制:

  • 為每次聲音播放都註冊GameObject,在聲音播放完成後便銷毀。
  • 創建少數負責聲音播放的遊戲對象,將所有將要播放的聲音都指派至該對象進行播放。

我在本文中將他們統稱為基於事件的(Event-based)音頻管理。

之所以這樣劃分,是因為他們在實現時通常只考慮了聲音的觸發,並沒有考慮到聲音在觸發後進一步進行控制的必要和複雜性。

儘管上述對象的創建和管理模式存在一定的合理性,但它們肯定是不能滿足所有開發需求的。

先來看一下第一種方式(一個事件伴隨一個隨機對象):

稍有編程功底的同學們都知道,遊戲中對象的創建和銷毀是需要占用系統資源的,低頻率事件可以使用該模式,如果是高頻觸發事件,那麼將會在短時間內大量的創建和銷毀對象,會導致不必要的系統消耗——這是Event-based模式在性能消耗方面可能存在的隱患。

當然某些場合可以採取創建對象池的思路來優化,但並不是所有聲音的播放都適合指派到隨機的對象上。

如果我們的遊戲中存在多個對象同時觸發相同聲音的情況,那麼Event-based模式將很難精細的管理這些聲音,因為所有的Event都是相互獨立的,音頻引擎無法區分哪些Event屬於遊戲里的哪個對象。這導致在進行發聲數上限等音頻管理時就只能進行全局管理,也就是只能使用Wwise中的Global模式進行管理。

再來看一下第二種方式(大量事件指向同一個對象):

通常在遊戲對豐富的聲音設計提出需求的情況下,設計師本身也要將性能的控制考量進去。

而將所有聲音分配至同一個遊戲對象的做法,會導致以實際遊戲內對象為單位的更細緻的聲音並發控制無法實現(與第一種方式一樣會遇到這個問題)。

若此時還對此同一個音頻對象的聲音並發量設置了上限(Global),則可能導致聲音被隨機誤傷,進而產生不合理的聽感。

而若要解決聲音被誤傷的問題,就需要解除聲音並發量上的限制,而這又會導致較大的性能消耗。

總結(第二節)

Event-based音頻管理是將GameObject視為Event的附屬物,為每個聲音事件匹配一個GameObject的做法。

而Object-based音頻管理則是將Event視為Gameobject的附屬物,先註冊和遊戲中對象對應的(Wwise中的)GameObject,然後Event作為子項繼承Gameobject的相關信息的做法。

三、如果GameObject管理不善可能造成的問題

為了更方便大家理解,我們此處直接使用一些工作中可能會接觸到的案例來進行分析:

假定我們當前正在開發的遊戲中並沒有加入Object-based的理念,依然基於Event-based的理念來觸發和管理聲音,那麼Wwise將不會正確接收和遊戲內對象對應的GameObject信息,而是盲目的為每個音頻事件獨立生成GameObject。

那麼我們在使用Wwise時可能會遇到以下問題:

1、Wwise內所有和GameObject相關的功能全部失效或表現異常

  • 所有選擇了Game Object模式的Random和Sequence容器在被觸發播放時不會刷新自己的列表。尤其值得注意的是,若每次播放都註冊新的Game Object,被播放的Sequence容器會永遠都只播放第一個子項。 遇到類似情況,設計師若不通過Profiler仔細檢查不同聲音的目標對象指派情況,將會在Debug上花費大量的時間。

  • 選擇了Per Game Object的playback limit將失效,完全無法限制遊戲內的實際聲部數量,針對並發控制的聲部管理功能將不得不在引擎層面重新被實現,然而這也要看程式設計師同學是否樂意。 如我們在第二部分當中已經提到的,以遊戲對象為單位的聲音並發控制將無法實現。無論是一聲一對象還是多聲一對象,設計師都無法在創作工具層面實現性能上的控制。這相當於程序同學把已經分出去的工作又自己搶了回來。

  • 插件參數的自動化(LFO)作用域的不同選項表現將會變得混亂。

具體會如何混亂,參見下圖:

  • 在一聲一對象的情況下,Stop和Break等音頻事件將無法針對直觀存在的Game Object使用(這些行為都不得不交由程序管理),只能使用Stop all等針對全局性的功能。 這與前面提到的播放限制問題一樣,相當於程序同學把已經分出去的工作又自己搶了回來。
  • 無法使用舊版本Wwise中的Game Object Profiler,因為Game Object都是順序生成的,你很難知道即將播放的聲音的Game Object ID是啥,除非你需要監視的是一個持續的Loop聲音。 這給音頻設計師測試與Debug帶來了進一步的困難。

2、產生不必要的系統消耗

  • 如果有高頻次觸發的One-Shot聲音,會導致聲音對象高頻次的反覆創建和銷毀,這個情況我們前文已經講述了,這會產生不必要的系統消耗;
  • 因為每個聲音都獨立註冊GameObject,那麼每個聲音也都會獨立計算方位與轉向、讀取自己的GameSyncs,這會進一步造成無意義的系統消耗。

以玩家的腳步為例:

  • 如果玩家身上有一個常駐的Game Object,那麼Wwise只會在玩家腳下的地面材質發生改變時接到SetSwitch API的調用,然後播放正確的音頻內容;
  • 如果不這麼做,那麼玩家的腳每踩一下,都會檢測一次地面材質、註冊一個隨機ID的Game Object,然後為此Game Object調用SetSwitch API確保播放正確的內容,再進行音頻內容的播放,在音頻播放完畢後解註冊掉Game Object,走每一步都會重複以上過程。

再以一輛車為例:

若車的引擎、胎噪、風噪彼此是獨立事件觸發的,它們都需要讀取車輛的車速。

  • 如果車輛上有一個常駐的Game Object,那麼該Game Object對應的三個事件的車速參數可通過同一個RTPC進行控制(調用SetRTPCValue一次);
  • 反之,若採取一聲一對象的方式實現,則會生成三個Game Object,並且需要為每個Game Object傳遞車速RTPC(調用SetRTPCValue三次),哪怕這幾個Game Object需要的RTPC數值是一樣的。 這便會導致計算車速這個RTPC的導致的內存/CPU消耗上升到合理做法的三倍。

雖然這個消耗可能依然不算大,但是這也是一個不健康的狀態。

3、針對GameObject設置的插件反覆實例化

這個前文已經提到過一次,因為每個聲音都是一個獨立的object,所以每有一個聲音都會實例化一個新的插件,每個插件都有自己獨立的運行參數,而不是從屬於同一個GameObject的插件擁有相同的參數。

4、限制了虛實例技術的使用

因為不能獨立註冊和銷毀GameObject,也沒有辦法通過讓多個發出相同聲音的發聲體只實例化其中一個的方式來減少系統消耗,所有的發聲體都將是真實實例化的。

總結(第三節)

如果大家在工作中遇到了類似以上所述的情況,可以嘗試查詢一下遊戲內針對音頻的GameObject的創建和註銷機制是否正常。有可能有助於大家定位問題產生的原因。

四、良好的GameObject管理理念

1、遊戲開發初期應當規劃音頻對象池,對於可能高頻重複觸發的聲音事件採用對象池技術管理,甚至所有的音頻對象都採用對象池技術進行管理。

2、遊戲開發時除了規劃聲音的觸發和停止機制以外,還要注意規劃聲音所屬GameObject的創建和銷毀(引用和解引用)時機。

3、遊戲引擎的音頻接口開發時應注意儘量保持和Wwise官方SDK的參數設置保持統一,必要的時候給Wwise開發獨立的音頻接口。

4、進行音頻中間件的更換或升級時應注意新舊中間件SDK的兼容度。如果兼容度低,升級過程中可能需要開發新的音頻接口和同時修改聲音的掛接邏輯。

結語

以上就是截止目前我們對於音頻GameObject管理的相關研究,目前的結論也可能存在一定的疏漏,我們會在此基礎上不斷完善。

文章來源: https://twgreatdaily.com/zh-my/4618a6f444030915c4c29be2dee864a2.html