如果沒預算請到各地專業的配音,請不要忘記Google小姐!
前言
相信讀者在專案過程中都遇過多語系的切換功能,多語系在遊戲中是很常見且必要的功能。但…這麽多語音檔整理下來肯定非常頭痛吧?尤其數量一多的時候Bank裏面包含的Event肯定又臭又長,讓人眼花繚亂。
瞭解
FMOD中要播放語音有兩種方式,不同模式下的腳本讀取會長的不太一樣,這點後面會再提到。
- 透過StreamingAsset方式讀取Wav檔案
- 透過FMOD Bank讀取Wav檔案

我們在Asset裏面新增Languages資料夾(名稱隨意),下面放入我們等會要在STUDIO裏面添加的Locale Name Folder,接下來把會用到的語音放入資料夾下面。(語音名稱可以相同/不同)
要注意的地方是這邊檔案名稱的主檔名將會成為”Key Name”,副檔名的功能目前用不到,後續的進階操作會再提到這一塊。
新增Locale Code
每個語系都有屬於自己的Locale Code,像中文就是ZHCN,英文是ENUS(基本上就是4碼組合)。

打開Preference裏面的Asset標籤,在下方空白處右鍵新增我們要的語系。
新增Audio Table
AudioTable,基本上所有的聲音都要透過這份表單來讀取。跟以前比較大的差別就是Table跟Bank下面存的資訊不同。Bank下面存放的是Event,Table裏面存放的是音效檔實際的路徑和Key Name,然而透過讀取Table表就可以知道現在播放的是哪些檔案,就可以減少把語音拉到Event裏面容易產生名稱混亂的狀況。

找到BANK頁籤下面的對話bank,裏面新增一份Localized Table,透過Browser選取之前存放語音檔案的根目録。
新建Programmer Sound腳本
那在Unity裡面播放我們前面在STUDIO做好的Event要怎麽做呢?回到Unity裡面我們新建一個FMOD_ProgrammerSound的Script。
以下的腳本是從API使用手冊摳過來的官方寫法,具體可以理解成:
#1:設定callback
#2:初始化需要播放的Event路徑
#3:選擇Unity尋找Audio播放的位置(從SteamingAsset / Banks)
#4:Play();
public class FMOD_ProgrammerSound : MonoBehaviour
{
FMOD.Studio.EVENT_CALLBACK dialogueCallback;
public FMODUnity.EventReference eventName;
void Start()
{
dialogueCallback = new FMOD.Studio.EVENT_CALLBACK(DialogueEventCallback);
}
public void Play(string keyName)
{
GCHandle stringHandle = GCHandle.Alloc(keyName);
VoiceEvent.setUserData(GCHandle.ToIntPtr(stringHandle));
VoiceEvent.setCallback(dialogueCallback);
VoiceEvent.start();
VoiceEvent.release();
}
[AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
{
FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);
IntPtr stringPtr;
instance.getUserData(out stringPtr);
GCHandle stringHandle = GCHandle.FromIntPtr(stringPtr);
String keyName = stringHandle.Target as string;
switch(type)
{
case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
{
FMOD.MODE soundMode = FMOD.MODE._3D;
var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
if(keyName.Contains(".")) //播放Unity StreamingAssets下面的音效
{
// FMOD.Sound fmodSound;
// var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(Application.streamingAssetsPath+"/"+keyName,soundMode, out fmodSound);
// if(soundResult==RESULT.OK)
// {
// fmodSound.setMode(MODE._3D_LINEARROLLOFF);
// parameter.sound = fmodSound.handle;
// parameter.subsoundIndex = -1;
// Marshal.StructureToPtr(parameter,parameterPtr,false);
// }
}
else //播放bank裏面的事件
{
FMOD.Studio.SOUND_INFO soundInfo;
var keyResult = FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(keyName, out soundInfo);
if(keyResult!=RESULT.OK)
{
break;
}
FMOD.Sound fmodSound;
var soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(soundInfo.name_or_data,soundMode | soundInfo.mode, ref soundInfo.exinfo, out fmodSound);
if(soundResult==RESULT.OK)
{
parameter.sound = fmodSound.handle;
parameter.subsoundIndex = soundInfo.subsoundindex;
Marshal.StructureToPtr(parameter,parameterPtr,false);
}
}
break;
}
case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
{
var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr,typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
var sound = new FMOD.Sound(parameter.sound);
sound.release();
break;
}
case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED:
{
stringHandle.Free();
break;
}
}
return FMOD.RESULT.OK;
}
}
(註解的部分是在StreamingAsset下播放音效檔案,此種方式播放的音效在Keyname需要添加”副檔名”,EX:Play(“Hello.wav”);)。
public void Play(string keyName)
{
keyName = audioNum[audiotableNum];
var VoiceEvent = FMODUnity.RuntimeManager.CreateInstance(eventName); //Assign FMOD.sound to an container
var audioOBJ = GetComponent<Select_Object>().Selected.gameObject; //Get Current Audio From Script
GCHandle stringHandle = GCHandle.Alloc(keyName);
VoiceEvent.setUserData(GCHandle.ToIntPtr(stringHandle));
VoiceEvent.setCallback(dialogueCallback);
VoiceEvent.set3DAttributes(audioOBJ.To3DAttributes()); //Set Audio position
VoiceEvent.start();
VoiceEvent.release();
記得分配播放的音效位置到想要的Object上,如果沒有set3DAttributes會導致聲音播放的位置不正確。