遊戲角色口型老是對不上?這裡有一個高效解決方案

遊資網發表於2020-01-07
編者按:本文將為大家介紹遊戲製作過程中口型同步的常規制作方法,以及如何通過新的解決方案,高效的在Unreal以及Unity中建立原型與測試效果。

一個角色的性格和形象特徵主要凸顯在三點上:語音、動作以及表情。

對於語音和動作,通過使用合適的聲優以及動作捕捉或動作製作,我們能夠獲得非常好的角色語音和動作特徵,但如果沒有口型動作,我們在遊戲中往往會覺得該角色在劇情表現中缺失了一部分靈魂,也會使得遊戲的代入降低不少。

為了解決這一問題,在當前傳統的口型同步方案中主要有兩種:全系列動作表現,以及跟隨語音音量變化

遊戲角色口型老是對不上?這裡有一個高效解決方案

當前口型同步方案有兩種,全系列動作表現以及語音音量變化。

1.1 全系列動作表現

所謂全系列動作表現,指的是我們遊戲中用到的所有和口型或表情相關的所有內容均由美術製作效果,將其作為一個動作進行輸出,當需要使用該表情或口型時,呼叫相關的動作來達到表現的效果。

遊戲角色口型老是對不上?這裡有一個高效解決方案

這種方式使得角色表情豐富,遊戲代入更深。但是也面臨一些問題:

  • 製作量極大,美術需要針對每一句語音製作對應的動作,當語音較多時製作量太大,對於普通廠商來說是一個非常難以解決的問題。
  • 遊戲動作邏輯複雜,因為輸出方式以動作進行輸出,因此當需要進行表現時就要考慮如何在不同的環境下播放相關的動作,相對來說邏輯較為複雜。


綜合來看,雖然直接使用動作進行製作的效果會更加自然但是成本和工作量太大,對於大量語音的遊戲不太實用。

1.2 語音音量變化

所謂語音音量變化指使用語音的音量,來控制遊戲內口型的張閉,以音量的RMS進行動態的變更,使得模型口型匹配語音。

遊戲角色口型老是對不上?這裡有一個高效解決方案

這種方式非常簡單,易於使用,但是分析太過簡化,僅僅使用RMS去控制口型形狀變化維度太低,沒辦法實現複雜的口型效果。

  • 雖然製作量少,但是表現力較差。


這種方式對於精品化遊戲沒有太大的幫助,因為分析有限所以能夠變化口型的維度太低表現力自然較弱。

我們可以看下面的動態效果:

遊戲角色口型老是對不上?這裡有一個高效解決方案

從效果中我們明顯看出,如果單純的使用RMS去控制口部形狀變化表現力較差,而且會有非常明顯的抖動效果,因為音量的變化是非常迅速的,當口型每幀重新整理變形效果也使得口型變動較為鬼畜,影響觀賞效果。

那麼這兩種方式都有各自的優缺點,無論是成本還是效果都不是最佳選擇,我們還有其他方法嘛?

這將是本文說明的重心——ADX LipSync口型解決方案。

遊戲角色口型老是對不上?這裡有一個高效解決方案

ADX LipSync是Criware公司推出的,針對其音訊中介軟體ADX2的口型解決方案。

實際上除了可以和音訊中介軟體ADX2連用外,還能夠單獨進行使用。

2.1 LipSync說明

LipSync是最新推出的,針對口型匹配的新解決方案。能夠實時以及預先分析聲音素材,得到聲音素材所包含的資訊後,將其運用於模型上,通過變更模型的變形效果,綜合控制口型的變化,以達到口型和語音匹配的目的。

LipSync主要有兩種應用模式,實時解析和預先分析。

  • 實時解析可以通過話筒錄入實時分析錄入的聲音內容,進行分析最終得到相關的口型資料,進行模型變形匹配聲音口型。
  • 預先分析是通過將已經錄音處理好的wav檔案進行分析,得到相關的分析資料,當播放相關的wav檔案時,可以使用事先分析好的資料進行模型的變形以匹配聲音口型。


LipSync也擁有兩種應用方法,單獨使用和與ADX2聯合使用。

  • 單獨使用是LipSync單獨應用,直接使用LipSync進行聲音分析,最終得到口型資料和效果。
  • 與ADX2聯合使用,這種方式LipSync作為ADX2的外掛使用,能夠分析ADX2中Cue中Track軌道上的波形檔案,生成相應的聲音資料。分析相應的資料也可以事先分析或者實時分析,這種應用方式稱為ADX LipSync也是本文中介紹的重點內容。


想要使用ADX LipSync口型同步,我們首先需要能夠變更口型的模型,下面將通過3D Max簡單的介紹如何製作口型資料。

2.2 模型口型資料創作

首先我們需要建立好一個包含口型的模型,然後匯入到3D Max中:

遊戲角色口型老是對不上?這裡有一個高效解決方案

選中模型後按住Shift複製一個副本。

遊戲角色口型老是對不上?這裡有一個高效解決方案

建立變形器。

遊戲角色口型老是對不上?這裡有一個高效解決方案

在建立的副本模型上更改相關變形效果。

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

回到原始模型,選擇變形目標建立變形資料,完成後更改資料的值,我們就能夠看到變形效果在模型上產生了。

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

選擇當前模型,進行匯出,匯出後的模型就可以為我們的口型效果提供變形資料。

遊戲角色口型老是對不上?這裡有一個高效解決方案

到此為止我們的模型資料就製作完成。

2.3 聲音資料處理

完成了模型處理後,我們需要準備聲音部分的處理。

ADX LipSync是跟隨音訊中介軟體ADX2進行連用,因此我們可以使用音訊中介軟體ADX2來進行相關處理。

當使用當使用ADX LipSync時,將素材拖入到Track後,就能夠看到聲音檔案對應的口型資料分析結果。

遊戲角色口型老是對不上?這裡有一個高效解決方案

如上圖,分析結果位於圖片的右下方,我們可以看到相關的高度和寬度資料,這些是自動分析的結果,自動分析結果我們可以進行手動更改。

經過分析後,我們主要產出兩種分析資料:

  • 寬度高度模式,這種分析結果資料,對應於口型變化的寬度和高度結果,通過對聲音進行分析,得到對應聲音時刻變化的寬度和高度曲線,同時可以在ADX2中進行高度和寬度曲線的調整,以應對自動分析不滿意的效果。

遊戲角色口型老是對不上?這裡有一個高效解決方案


  • 音素混合量分析模式,這種分析結果資料是分析語音中各個音素量的佔比,通過佔比決定各個音素口型的權重從而動態的變化模型變形效果,同樣如果對於自動分析結果並不滿意,則可以手動修改。


遊戲角色口型老是對不上?這裡有一個高效解決方案

通過這兩種資料,我們可以變更口型變化效果,來實現口型同步。

注意:

  • 對於寬度和高度模式,沒有任何語言限制,所有語種都可以使用,但是解析精度相對低。
  • 對於因素量混合分析模式,和語種相關,目前提供日語的AEUIO幾個母音進行解析,因此對日語的匹配度較高。


在使用ADX LipSync時,會和ADX2一起連用,由音訊中介軟體ADX2匯出資原始檔,這些資原始檔包含了音訊中介軟體中製作的聲音資原始檔也包含了其中的口型資料結果。

完成了以上內容後,我們的資原始檔就準備完成,下面我們將介紹如何使用這些資原始檔來實現口型同步效果。

2.4 Unreal應用

準備好資原始檔後,我們將資原始檔匯入到Unreal中主要包含兩個檔案,首先是建立的變形資料的模型檔案,匯入後我們在Unreal中開啟,可以看到如下內容:

遊戲角色口型老是對不上?這裡有一個高效解決方案

我們可以看到,在3D Max中建立的變形資料已經顯示在Unreal中了。此時我們在Unreal中更改相關引數也能看到引數對模型產生的變形效果。

遊戲角色口型老是對不上?這裡有一個高效解決方案

上圖顯示的是匯入的ADX2的聲音資原始檔,匯入後會自動顯示出建立的Cue(事件)內容。

我們將使用這些Cue內容,以及相關介面建立藍圖,用以播放聲音時變更模型的變形效果完成口型和語音的匹配。

下面我們開始製作口型同步內容。

①藍圖類創作

選擇我們的模型檔案,建立藍圖類:

遊戲角色口型老是對不上?這裡有一個高效解決方案

同時建立Atom Component,Atom Component主要用於Cue的播放,由於Cue中包含wav資訊,而ADX LipSync則是分析Cue中的聲音資訊生成對應的口型資料,因此我們需要播放相關的Cue來同步口型。

②初始化LipSync

藍圖類建立完成後,我們要進行藍圖功能的建立,首先我們需要建立初始化LipSync相關邏輯,我們建立一個名稱為Set ADXLipSync的巨集,該巨集用於初始化LipSync,邏輯如下:

遊戲角色口型老是對不上?這裡有一個高效解決方案

建立了構建Lips Atom Analyzer,並將其設定為變數,便於呼叫。並新增Init相關,需要注意的是Init必須要建立Attach前。

而後用Get Info並將Lip Width的值設定為引數的原因是當處於無聲狀態時,口型的寬度並不一定等於0,這些由美術在製作模型變形資料時決定。

初始化建立完成後,我們回到事件圖表中,並建立下面藍圖內容:

遊戲角色口型老是對不上?這裡有一個高效解決方案

通過Event BeginPlay我們執行了Set ADXLipSync巨集,初始化相關內容,同時我們Play Atom使得執行時能夠直接聽到聲音效果。

③口型分析模式建立

初始化完成後,我們需要建立口型分析模式,並且通過口型分析模式來決定我們怎麼使用模型的變形效果,通過分析的聲音口型資料動態的變更模型的變形引數來實現口型變化效果。

因此我們建立兩個巨集分別對應寬度高度模式,音素量混合分析模式,為了做對比,我們再建立一個巨集用於音量控制。

遊戲角色口型老是對不上?這裡有一個高效解決方案

寬度高度模式相關邏輯如下:

遊戲角色口型老是對不上?這裡有一個高效解決方案

我們使用了Get Info的相關資料,使得Lip Width控制模型檔案的LipWidthOpen引數,Lip Height控制模型的LipHeightOpen引數,使用Tongue Position控制模型的Tongue_Up引數,當Info的資料跟隨聲音進行變化時,能夠同時變更模型相關的各個引數值變化,引起口型變化。

音素混合量分析模式相關邏輯如下:

遊戲角色口型老是對不上?這裡有一個高效解決方案

與高度寬度類似,只是使用的資料不同控制的模型變形引數不同,同樣可以控制模型的變形引數實現口型的變化。

音量模式相關邏輯如下:

遊戲角色口型老是對不上?這裡有一個高效解決方案

音量模式我們建立的目的是為了進行對比,以此來通過模式切換非常方便的看到不同模式下口型的效果。

音量模式下,我們採用的是通過Bus中的Peak Leavels值來變更模型的LipWidthOpen以及LipHeightOpen引數。

通過上述步驟,我們完成了建立寬度高度模式、音素混合量分析模式以及音量模式的巨集,下面需要建立如何使用這些巨集來變化口型效果。

④介面顯示及其功能建立

我們想通過Unreal直接進行執行中更改模式,並且顯示相關Cue(事件)名稱以及當前的分析模式以便於我們進行測試和效果演示,因此我們建立一個UI控制元件藍圖,並新增以下內容:

遊戲角色口型老是對不上?這裡有一個高效解決方案

完成後,我們選擇"TextBlock300"和"TextBlock492"並做如下的顯示TXT繫結內容:

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

同時建立兩個String型別的引數分別為:CueNameParam以及LipsyncModleNamePara用於儲存Cue的名稱和當前模式的名稱。

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

回到我們的藍圖類中,我們已經完成了3個巨集的建立,並且擁有了相關UI,那麼下面需要將UI建立在介面上,並且進行模式的更改:

遊戲角色口型老是對不上?這裡有一個高效解決方案

如上圖所示,我們建立相關UI控制元件,使之顯示在執行中的場景中。

同時為了更改口型分析的模式,我們需要建立三個不同的bool型別變數。

遊戲角色口型老是對不上?這裡有一個高效解決方案

這三個bool型別的引數用於我們的模式選擇。

我們再建立三個自定義事件,用於控制模式更改時的bool型別值變化以及建立的UI中的當前模式的顯示文字:

遊戲角色口型老是對不上?這裡有一個高效解決方案

在上圖中我們可以看到,當執行不同的自定義事件時,會更改相關的bool引數值,同時會設定UI中的LipsyncModleNamePara引數顯示。

完成上圖中內容後,我們擁有了能夠改變bool值的引數,而我們想通過bool值引數來變化混合模式的型別,下面將做相關內容邏輯:

遊戲角色口型老是對不上?這裡有一個高效解決方案

我們通過呼叫Event Tick使得每幀重新整理,通過三個bool變數選擇最終的混合模式,而在混合模式中,由於每幀重新整理,因此會不斷的根據Cue的聲音內容變更相關的info資訊,從而每幀更改模型的變形引數,形成最終的口型效果。

完成上述內容後,我們僅僅是完成了各個口型模式的邏輯,以及如何選擇任意一個模式的邏輯,但是沒有選擇相關模式的觸發內容,因此我們回到UI控制元件藍圖中:

在控制元件藍圖中我們選擇如下的按鈕並且使用其點選事件:

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

滑鼠點選後將會跳轉到事件圖表中,我們做如下的邏輯:

遊戲角色口型老是對不上?這裡有一個高效解決方案

可以看到我們在控制元件藍圖中呼叫了剛才在模型藍圖事件圖表中建立的三個自定義事件,如此一來我們每次點選按鈕都會按照順序執行三個自定義事件中的一個,以此來更改設定的bool引數值,從而在Event Tick呼叫時轉變呼叫的混合模式巨集,驅動不同的模型變形引數效果。

注意:在上圖中的Sequence Flip是筆者自己定義的流程控制巨集,用於每次執行時按照順序依次執行一條,如此反覆,相關邏輯如下:

遊戲角色口型老是對不上?這裡有一個高效解決方案

按照上述內容完成後,我們就可以得到每次點選按鈕就會變化混合模式,當聲音播放時我們就能夠看到不同模式下口型變化效果進行對比和分析。

而各位在實際測試中會發現,當我們變更模式時有時候口型會變得非常大,這是因為在我們點選按鈕變更模式時,所有資料依然是當前點選時的資料,因此會被記錄並且保持不變,當使用其他模式時,其他模式下的聲音資料引起的口型變化效果和當前點選時的口型變化值相疊加導致口型變大。

為了解決這一個問題,我們需要在點選按鈕變化模式時,將所有變形效果重置為0,因此我們建立Reset巨集,邏輯如下:

遊戲角色口型老是對不上?這裡有一個高效解決方案

如上圖,Reset巨集的作用是當執行該巨集時,會重置所有模型的變形引數值,使得其迴歸到0,當被其他聲音資料再次驅動時能夠不記錄前一個資料的值而引發口型變大效果。

同樣我們需要在Event Tick時進行處理:

遊戲角色口型老是對不上?這裡有一個高效解決方案

如此一來,當每幀呼叫時,每次更改模式,都會觸發一次Finish為False而執行一次Reset,通過Reset我們將所有模型的變形資料還原修復口型變大的問題。

當完成上述內容後,針對如何使用ADX LipSync的介紹已經完成,我們可以通過上述內容測試和製作相關口型同步原型。

但筆者這裡再進行一步優化使得能夠在執行中時刻測試不同語言在不同模式下的表現。

⑤優化內容

由於ADX LipSync是由聲音中介軟體ADX2建立的資料進行分析後得到的資料進行口型處理,那麼我們可以建立相關的變數用於儲存不同語種的Cue內容,使得在執行中可以實時切換便於測試。

首先我們建立一個名稱為CueList,變數型別為Sound Atom Cue的陣列,並將不同語言的Cue內容新增到陣列中:

遊戲角色口型老是對不上?這裡有一個高效解決方案

完成後我們再建立一個名稱為CueChoice的自定義事件,並新增如下邏輯:

遊戲角色口型老是對不上?這裡有一個高效解決方案

其中從UI中獲得的Cue Index稍後介紹,需要在UI控制元件藍圖中進行建立。

建立上述的邏輯目的是為了當我們點選一個按鈕時,能夠動態的選擇其上一個或者下一個CueList中的Cue進行播放,同時將Cue的名稱顯示在介面上。

CueIndex引數是需要在UI控制元件藍圖中進行建立,因此我們回到UI控制元件藍圖中,建立一個整型的名稱為CueIndex的變數:

遊戲角色口型老是對不上?這裡有一個高效解決方案

此變數主要用於記錄當前的Cue的Index並且,當我們點選按鈕時能夠動態的切換其數值,來變更將要播放的Cue Index從而更改和播放相關Cue。

為了能夠通過介面進行Cue的切換,我們需要對下面的按鈕進行點選事件的邏輯建立:

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

建立的邏輯如下:

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

上圖中顯示通過CueIndex的值,當點選按鈕時選擇增加或減少1,當超過或小於某個值時回到正確的索引範圍內,當然筆者這裡使用的是具體的數字,更加常規的做法是需要在模型藍圖類中獲取CueList的Length,將其值傳入進行控制。

當點選按鈕後,會執行CueChoice的自定事件,同時將相關的Cue Index傳入模型藍圖類中用於選擇CueList中的Cue內容,並進行播放。

如此一來就完成了我們的Cue的切換,我們可以實時的切換Cue,並實時的更改模式來測試我們建立的原型效果。

我們還可以再在模型藍圖中和UI控制元件藍圖中建立如下邏輯,使得單機按鈕時進行當前Cue的再次播放,以此來方便觀察同一個語音在不同的模式下口型表現效果:

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

完成上述內容後,我們就能夠完整的完成一套原型,該原型能夠通過點選按鈕切換口型分析模式,同樣可以更改播放的Cue內容來測試同一模式下不同語種的口型效果以及同一語種下,不同模式的口型效果。

最終完成效果參考:


可以看到,在不同的模式下的表現效果不同,Volume形式下表現較差,高度寬度模式可以較好匹配語音,而音素混合量模式則表現更加。通過語音內容的分析,我們得到口型資料,從而驅動模型的變形引數使之與語音同步匹配。

這種方式下效果較好,且成本很低非常適合大量語音口型同步製作。

2.5 Unity應用

與Unreal中類似,我們通過匯入資原始檔,在Unity中建立相關Object,並新增相關程式碼,最終做出口型同步效果。同樣我們也將製作支援介面按鈕點選切換模式與切換播放的語音。

①模型資源

當匯入我們包含了變形引數的模型後,我們需要將其拖拽到場景中,並調節相關位置使之能夠在攝像機中被看到。

遊戲角色口型老是對不上?這裡有一個高效解決方案

模型放在Hierarchy中以備後續呼叫。

②介面按鈕建立

我們希望能夠在執行時實時調整口型的分析模式,且實時更換播放的語音內容,因此我們可以通過建立按鈕來實現這些功能:

遊戲角色口型老是對不上?這裡有一個高效解決方案

如上圖,建立6個介面按鈕,從上至下功能依次為:

  • "Button_play":播放Cue(事件)按鈕,當按下按鈕時播放相關語音。
  • "Button_stop":停止Cue(事件)按鈕,當按下按鈕時停止播放相關語音。
  • "Button_>":切換Cue(事件)按鈕,當按下按鈕時將會切換播放的語音。
  • "Button_<":切換Cue(事件)按鈕,當按下按鈕時將會切換播放的語音。
  • "Button_textshowcue":顯示Cue(事件)名稱,當更換Cue時,顯示文字跟隨Cue名稱變化。
  • "Button_modleselector":顯示當前口型模式,按下按鈕時將會切換模式且按鈕上文字將會跟隨轉變。


其中前四個按鈕為功能性質按鈕,按鈕點選後會對應上述功能,但顯示的文字不變,第五個單純顯示Cue名稱,第六個是顯示和功能性按鈕,按下後將會對應功能且文字顯示也將發聲變化。

③聲音資源處理

準備好我們的模型和按鈕後,我們需要處理聲音資源,所有的聲音素材都經過中介軟體ADX2進行打包後倒入到Unity中,此時我們建立一個空的Object於Hierarchy中,並重新命名為CRIWARE,同時點選Add Component按鈕,建立Cri Atom元件指令碼:

遊戲角色口型老是對不上?這裡有一個高效解決方案

新增完成後,在Cri Atom中填寫相關acf,acb以及awb檔案路徑,以備使用。

④口型模式

在接下來的步驟中,我們將處理口型模式與模型變形引數,使之能夠被我們正常的呼叫。

我們繼續在Hierarchy中建立一個空的Object並命名為:ModelControl,而後在其上點選Add Component按鈕新增指令碼元件:"Cri Lips Shape For Atom Source":

遊戲角色口型老是對不上?這裡有一個高效解決方案

可以看到在該指令碼中我們需要新增兩個內容:Cri Atom Source以及SkinnedMeshRenderer。

Cri Atom Source我們稍後新增,首先將SkinnedMeshRenderer新增完成,我們在Hierarchy中選擇之前的模型元件,將其拖拽到SkinnedMeshRenderer上完成新增:

遊戲角色口型老是對不上?這裡有一個高效解決方案

新增完成後將會出現下面的內容:

遊戲角色口型老是對不上?這裡有一個高效解決方案

此時點選BlendShapeType就可以選擇寬度高度模式還是音素混合量分析模式,而在其下方的內容中點選就可以選擇 我們之前在模型中建立的變形引數名稱。

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

將我們建立好的模型變形資料依次填寫完成,這樣就完成了不同口型模式下各個音訊分析的口型資料對應的模型變形引數的匹配。

我們再在ModelControl中通過Add Component新增一個Cri Atom Source元件。

遊戲角色口型老是對不上?這裡有一個高效解決方案

完成後,我們選擇Hierarcgy中的ModelControl將其拖拽到之前建立的"Cri Lips Shape For Atom Source"指令碼元件中的CriAtomSource中:

遊戲角色口型老是對不上?這裡有一個高效解決方案

這樣一來,我們"Cri Lips Shape For Atom Source"需要的相關資訊就已經新增完成,下面需要進行指令碼處理。

⑤處理指令碼

我們最終希望的效果是能夠在執行過程中切換口型模式,同時也能切換播放的語音內容,而Play和Stop按鈕也可以控制Cue的播放和停止,因此我們需要建立一個自定義指令碼指令碼內容如下:

  1.   using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;

  5. public class ADXLipsync : MonoBehaviour
  6. {  
  7.    #region
  8.    public CriAtomSource atomsource = null;
  9.    public string cueSheetName = "";
  10.    public CriLipsShapeForAtomSource shapeForAtomSource = null;
  11.    private CriAtomExAcb acb = null;
  12.    private CriAtomEx.CueInfo[] cueInfos;
  13.    private int selectedCueIndex = 0;
  14.    public Text textname;
  15.    public Text modelName;
  16.    #endregion
  17.    // 初始化相關資訊/顯示當前口型模式/設定當前口型模式
  18.    #region
  19.    void Start()
  20.    {  
  21.        if (!string.IsNullOrEmpty(cueSheetName))
  22.        {  
  23.            acb = CriAtom.GetAcb(cueSheetName);
  24.            cueInfos = acb.GetCueInfoList();
  25.            modelName.text = "JapaneseAIUEO";
  26.            shapeForAtomSource.blendShapeType = CriLipsShape.BlendShapeType.JapaneseAIUEO;
  27.         }
  28.     }
  29.    // 更新顯示語音名稱
  30.    void Update()
  31.    {  
  32.        textname.text = cueInfos[selectedCueIndex].name;

  33.     }
  34.    // play按鈕點選播放音訊
  35.    public void PlayCue()
  36.    {  
  37.        atomsource.player.SetCue(acb, cueInfos[selectedCueIndex].name);
  38.        if (atomsource.status == CriAtomSource.Status.Playing)
  39.        {  
  40.            atomsource.player.Stop();
  41.         }
  42.        atomsource.player.Start();
  43.     }
  44.    // stop按鈕點選停止音訊
  45.    public void StopCue()
  46.    {  
  47.        atomsource.player.Stop();
  48.     }
  49.    // 修改播放音訊的index
  50.    public void SetSelectedCueIndexIncrease()
  51.    {  
  52.        if (selectedCueIndex >= cueInfos.Length - 1)
  53.        {  
  54.            selectedCueIndex = 0;
  55.         }
  56.        else
  57.        {  
  58.            selectedCueIndex++;
  59.         }
  60.     }

  61.    public void SetSlectedCueIndexDecrease()
  62.    {  
  63.        if (selectedCueIndex <= 0)
  64.        {  
  65.            selectedCueIndex = cueInfos.Length - 1;
  66.         }
  67.        else
  68.        {  
  69.            selectedCueIndex--;
  70.         }
  71.     }
  72.    // 更改口型模式
  73.    public void changeModle()
  74.    {  
  75.        switch (shapeForAtomSource.blendShapeType)
  76.        {  
  77.            case CriLipsShape.BlendShapeType.WidthHeight:
  78.                shapeForAtomSource.blendShapeType = CriLipsShape.BlendShapeType.JapaneseAIUEO;
  79.                SetBlendShapeWidthHeightAtSilence(shapeForAtomSource);
  80.                modelName.text = "JapaneseAIUEO";
  81.                break;
  82.            case CriLipsShape.BlendShapeType.JapaneseAIUEO:
  83.                shapeForAtomSource.blendShapeType = CriLipsShape.BlendShapeType.WidthHeight;
  84.                setSetBlendShapeJapaneseAIUEOAtSilence(shapeForAtomSource);
  85.                modelName.text = "WidthHeight";
  86.                break;
  87.            default:
  88.                break;
  89.         }
  90.     }

  91.    // 模式切換還原資料
  92.    /// <summary>
  93.    /// 還原寬度高度資料
  94.    /// </summary>
  95.    private void SetBlendShapeWidthHeightAtSilence(CriLipsShapeForAtomSource lipsShapeForAtomSurce)
  96.    {  
  97.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.WidthHeightName.lipHeightOpenName, 0.0f);
  98.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.WidthHeightName.lipWidthCloseName, 0.0f);
  99.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.WidthHeightName.lipWidthOpenName, 0.0f);
  100.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.WidthHeightName.tonguePosition, 0.0f);
  101.     }
  102.    /// <summary>
  103.    /// 還原母音資料
  104.    /// </summary>
  105.    private void setSetBlendShapeJapaneseAIUEOAtSilence(CriLipsShapeForAtomSource lipsShapeForAtomSurce)
  106.    {  
  107.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.japaneseAIUEOName.a, 0.0f);
  108.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.japaneseAIUEOName.e, 0.0f);
  109.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.japaneseAIUEOName.i, 0.0f);
  110.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.japaneseAIUEOName.o, 0.0f);
  111.        BlendShapeWeighString(lipsShapeForAtomSurce.skinnedMeshRenderer, lipsShapeForAtomSurce.nameMapping.japaneseAIUEOName.u, 0.0f);
  112.     }

  113.    private void BlendShapeWeighString(SkinnedMeshRenderer skinnedMeshRenderer,string blendShapeName,float weight)
  114.    {  
  115.        if (string.IsNullOrEmpty(blendShapeName))
  116.        {  
  117.            return;
  118.         }
  119.        int index = skinnedMeshRenderer.sharedMesh.GetBlendShapeIndex(blendShapeName);
  120.        if (index < 0)
  121.        {  
  122.            return;
  123.         }
  124.        skinnedMeshRenderer.SetBlendShapeWeight(index, weight);
  125.     }
  126.    #endregion
  127. }
複製程式碼


在上面的指令碼中我們主要進行了以下內容:

  • 首先定義了acb,acb是聲音的資原始檔,其中儲存了需要播放的所有Cue內容。
  • 同時通過acb獲取了其中的cue內容,將其顯示在介面上。
  • 建立了自定義的PlayCue,StopCue,SetSelectedCueIndexIncrease以及SetSlectedCueIndexDecrease方法,分別用於播放Cue,停止Cue以及選擇Cue內容,使用的是從acb中獲取的CueIndex。
  • 建立了自定義的changeModle方法,用於點選按鈕時能夠切換口型模式(寬度高度模式以及音素混合量分析模式)。
  • 在changeModle方法中使用了SetBlendShapeWidthHeightAtSilence和setSetBlendShapeJapaneseAIUEOAtSilence方法,這兩個方法主要用於在切換模式時將相關的資料還原,使得口型變回基礎狀態。


完成指令碼內容後,我們需要將建立的指令碼方法與按鈕的點選以及各個文字顯示相連線:

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

遊戲角色口型老是對不上?這裡有一個高效解決方案

如此一來,我們的指令碼內容和按鈕就可以進行使用了,執行場景來測試一下效果吧。


ADX LipSync使用簡便,又能夠和音訊中介軟體ADX2聯合使用,完美解決了當想使用音訊進行口型實時分析時,不能將音訊進入音訊中介軟體管理的問題。

同時分析的效果也相對較好,快速的幫助遊戲專案完成非常好的口型效果表現。

作者:超級無敵土豪狗
來源:騰訊GWB遊戲無界
原地址:https://mp.weixin.qq.com/s/aVwiImNgcNs_q_OShyLvtw

相關文章