為什麽要使用聲音引擎?

如果沒預算請到各地專業的配音,請不要忘記Google小姐!

前言

相信讀者在專案過程中都遇過多語系的切換功能,多語系在遊戲中是很常見且必要的功能。但…這麽多語音檔整理下來肯定非常頭痛吧?尤其數量一多的時候Bank裏面包含的Event肯定又臭又長,讓人眼花繚亂。

瞭解

FMOD中要播放語音有兩種方式,不同模式下的腳本讀取會長的不太一樣,這點後面會再提到。

  • 透過StreamingAsset方式讀取Wav檔案
  • 透過FMOD Bank讀取Wav檔案
blank

我們在Asset裏面新增Languages資料夾(名稱隨意),下面放入我們等會要在STUDIO裏面添加的Locale Name Folder,接下來把會用到的語音放入資料夾下面。(語音名稱可以相同/不同)

要注意的地方是這邊檔案名稱的主檔名將會成為”Key Name”,副檔名的功能目前用不到,後續的進階操作會再提到這一塊。

新增Locale Code

每個語系都有屬於自己的Locale Code,像中文就是ZHCN,英文是ENUS(基本上就是4碼組合)。

blank

打開Preference裏面的Asset標籤,在下方空白處右鍵新增我們要的語系。

新增Audio Table

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

blank

找到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會導致聲音播放的位置不正確。