WWise是Audiokinetic的跨平台音频引擎,可以与游戏引擎很好地进行交互,负责音频的同事可以只在WWise中处理音频,把游戏业务和音频的制作与管理分离,提供事件和参数给游戏引擎使用,实现与业务的解耦和对音频更精确的控制。
本篇文章主要介绍WWise与UE4的集成、远程构建、资源分析、文档收录,WWise与UE的控制交互以及Bank生成的代码分析。
集成至UE4
WWise是全平台支持的,对Linux/Lumin/PS4/Switch/XboxOne/Windows/Android/iOS/Mac都支持。
但是多数游戏不需要支持这么多平台,WWise链接库很大,所以我在官方版本支持的多平台基础上做了裁剪,去掉了以下平台的支持:
- Linux
- Lumin
- PS4
- Switch
- XboxOne
对Android和Windows平台做了以下裁剪:
- 移除arm64-v8a和android_x86/x86_64的链接库支持
- 移除Win32的所有链接库/移除vc140/vc150的支持
对iOS做了以下裁剪:
- 移除所有的iphonesimulator,节省空间2.31G
对Mac的支持:
目前的项目是不需要支持Mac的(使用iOS远程出包),但是为了避免想要在Mac上跑工程编译不过的问题,保留了Mac链接库和模块支持,保留它不会对Android/iOS的打包有任何影响。
链接库
我在裁剪版本中支持以下平台:
- Android_armabi_v7a
- iOS
- Mac
- Win vc160
每个平台均支持Debug
/Profile
/Release
的支持,分别对应UE的Debug
/Development
/Shipping
的Configuration配置。
在打包Android Development的配置下,包含WWise的链接库,APK增大约30M.
WWise版本的问题
Wwise版本为Wwise 2019.1.9.7221
在AkAudio_Android.build.cs
中对Android的的链接库支持在UE_4_25_OR_LATER
下路径错误。
原始路径:
1 | Path.Combine(ThirdPartyFolder, "Android", "armeabi-v7a", akConfigurationDir, "lib") |
实际的路径:
1 | Path.Combine(ThirdPartyFolder, "Android_armeabi-v7a", akConfigurationDir, "lib") |
但是这样的修改会造成同时支持armv7和arm64时具有链接错误,解决方案看下节。
Android支持armv7和arm64
当在UE的项目设置中为Android同时支持arm64
和armb7
时,上面的修改会具有链接错误,需要变动WWise中链接库的路径,具体的方法可以看我这篇笔记:同时支持armv7和arm64 的链接库。
Android链接库的拷贝
WWise的SDK中同时使用UPL和RuntimeDependencies添加了链接库,造成了重复,所以可以把RuntimeDependencies中针对移动平台去掉:
1 | public AkAudio(ReadOnlyTargetRules Target) : base(Target) |
远程构建iOS
在我之前的笔记中写到过,远程构建iOS实际就是要把文件上传的Mac上执行编译,但是这就有一个问题,如果需要参与编译的文件没有被上传到Mac上,就会出现错误,很不巧在WWise中就会出现这个问题,解决的办法自然是要把编译WWise依赖的文件给上传到Mac上。
因为UE使用RSync
来同步构建机和本地的文件传输,在我之前的文章UE4开发笔记:Mac/iOS篇#配置远程构建有讲到,可以创建<ProjectDir>/Build/Rsync/RsyncProject.txt
文件,来写入RSync的文件同步规则,把需要的文件上传到Mac中。
WWise需要的规则如下:
1 | + /Plugins/Wwise/ThirdParty/include/** |
其实就是指定WWise的链接库和Include目录全上传到Mac上。
WWise资源
WWise在UE中有两种资源格式,一种是UAkAudioEvent
用来执行WWise中指定的Event,还有一种是UAkAudioBank
,用来记录Bank中包含哪些Event,用在生成时标记把属于相同Bank的Event打包到一起。
UAkAudioEvent
UAkAudioEvent
:主要作用是指定当前Event的Bank,它唯一的函数就是LoadBank
来加载当前Event所指定的Bank(看到有用到的地方就是获取它的名字),WWise集成到UE的插件也是通过拿到UAkAudioEvent
对象的名字来与UAkAudioBank
做绑定之后去WWise
端生成bnk等文件的,这也是要求UE中资源的命名要和WWise中Event的命名完全一致的原因。
1 | UCLASS(meta=(BlueprintSpawnableComponent)) |
通过获取Event的名字再传递给更深层次的PostEvent:
1 | int32 UAkGameplayStatics::PostEvent( |
UAkAudioBank
UAkAudioBank
的作用是用来调用AkAudioDevice
来加载Bank,如果在UE中开启了AutoLoad,则在UObejct的PostLoad
中就会去执行加载。
官方的对于SoundBank的介绍:SoundBank
1 | /** |
而且LoadBank我看到使用的也是和AkAudioEvent
类似,也是通过获取它的名字传递给FAkAudioDevices
。
Bank的生成分析
那么AkAudioEvent
是如何与SoundBank
进行关联起来的呢?因为我在代码里只看到使用之前需要LoadBank
,但是没有看到在UE资源里SounkBnak和Event进行关联起来的地方,而且,SoundBank的命名与在WWise中也没有关系。
答案就在通过AkSoundBank
生成的文件上,在对AkSoundBank
资源进行Generate Selected SoundBank
时:
会在项目设置中的WwiseSoundBankFolder
目录下创建出以下文件:
1 | D:\WWise\WwiseDemoGame\Content\WwiseAudio\Windows>tree /a /f |
其中Init.*
相关的四个文件是必备的Init的Bank的内容。每个Bank都会生成.bnk
/.json
/.txt
/.xml
四个文件,UE加载bank需要用到的就是.bnk
文件,经过测试,删掉其他的几个文件也没什么问题。
- bnk:数据文件,*.Bnk可以存储事件的详细信息、音频、其他插件所需要的数据结构。可以简单理解为是一个 数据存放的容器。该文件可以在运行中自由的控制加、卸载。
- json:描述文件,用于记录当前的bank中有哪些数据、哪些Event等等,以及对应的Event在WWise工程中的路径。
1 | { |
- xml:描述文件,与json表示的内容相同
1 |
|
- txt:也是描述文件,但和json和xml中的内容相比少了一些(不过基础的Event ID、EventName、)。
1 | Event ID Name Wwise Object Path Notes |
UAkGameplayStatics
中的类似Post*
传递Actor的作用是获取World,并且在从该Actor上获取AkComponent
(若没有就创建,并Attach到该Actor的RootComponent上):
1 | struct AkDeviceAndWorld |
通过Event来指定SoundBank,然后对SoundBank执行Generated Selected Bank
,流程如下:
- 根据所选择的SoundBank得到SoundBank的名字列表
- 获取引擎中所有的UAkAudioEvent对象,通过分析AkSoundEvent中
RequireBank
的名字与第一步中获取的SoundBank中的名字是否匹配,从而得到SoundBank中所有的AkAudioEvent
; - 根据以上两步分析的结果生成
SoundBankDefinitionFile
,存储在UE的项目目录下,名字为TempDifinitionFile.txt
1 | VelocityBank "PlayRederenceSoundTest" |
里面记录了UE里的SoundBank对象与其关联的Event
以及Bus
的名字,通过-ImportDefinitionFile
参数传递给WwiseCLI.exe
,从而让WWise端知道UE侧SoundBank和Event
之间的对应关系。
- WWise端根据传入进来的
ImportDefinitionFile
文件,根据WWise工程里的Event以及Bus等匹配,产生出来包含指定Event的SoundBank,并生成json
/xml
/txt
等描述文件。
UE通过LoadBank
的加载流程应该是:
- 通过Bank对象拿到Bank的名字
- 根据Bank名字去
WwiseSoundBankFolder
目录下查找同名的bnk文件 - 拿到bnk文件,进行加载
UE与WWise的交互
在UE中可以使用WWise的API来播放和控制声音的一些介绍。
PlaySoundAtLocation
API均在UAkGameplayStatics
:
1 | /** Posts a Wwise Event at the specified location. This is a fire and forget sound, created on a temporary Wwise Game Object. Replication is also not handled at this point. |
这两个API可以通过传递AkAudioEvent
的资源或者Event的名字来在指定的位置播放声音。PostEventAtLocationByName
是UAkGameplatStatics::PostEventAtLocation
的封装版本,AkAudioEvent为NULL。
PostEvent
发送给定 Actor 的根组件绑定和控制的 Wwise Event。如果传递进来的Actor上没有挂载AkComponent,会在PostEvent上创建出来一个组件,并默认Attach到该Actor的RootComponent。
在PostEvent之后,如果后续需要控制Event的值,如RTPC或者Switch等,要传入对应的Actor(因为PostEvent本质上是要通过AkComponent
):
1 | AkPlayingID FAkAudioDevice::PostEvent( |
目前我的理解为:传入进去的Actor(上的AkComponent)是WWise播放声音的上下文,通过这个上下文可以在另外的操作中去控制指定的Event.
而PostEvent还具有多种类型的回调函数,控制其的方式是传递进去的Mask值,它由EAkCallbackType枚举值控制(bitmask):
1 | static int32 PostEvent( class UAkAudioEvent* AkEvent, |
EAkCallbackType的可选值:
1 | /// Type of callback. Used as a bitfield in methods AK::SoundEngine::PostEvent() and AK::SoundEngine::DynamicSequence::Open(). |
- EndOfEvent:事件结束时触发回调;能够转换到AkEventCallbackInfo
- Marker:遇到标记时触发回调;能够转换到AkMarkerCallbackInfo
- Duration:当Sound Engine知道当前声音的持续时间时触发回调;能够转换到AkDurationCallbackInfo
- Starvation:当播放由于流不足而跳过下一帧时触发回调;能够转换到AkEventCallbackInfo
- MusicPlayStarted:当执行Play或者Seek时触发(从AK::SoundEngine::SeekOnEvent发出搜索命令),只适用于交互音乐层次结构的对象。能够转换到AkEventCallbackInfo
- MusicSyncBeat:在Music beat上启用通知;能够转换到AkMusicSyncCallbackInfo
- MusicSyncBar:在Music Bar上启用通知;能够转换到AkMusicSyncCallbackInfo
- MusicSyncEntry:在Music Entry上启用通知;能够转换到AkMusicSyncCallbackInfo
- MusicSyncExit:在Music Exit上启用通知;能够转换到AkMusicSyncCallbackInfo
- MusicSyncGrid:在Music Grid上启用通知;能够转换到AkMusicSyncCallbackInfo
- MusicSyncPoint:在Music switch transition synchronization point上启用通知;能够转换到AkMusicSyncCallbackInfo
- MIDIEvent:为MIDI事件启用通知;能够转换到AkMIDIEventCallbackInfo
Marker
在WWise编辑器中添加的Sound VFX
里添加的Audio可以通过编辑来添加Marker,Marker可以在UE调用UAkGameplayStatics::PostEvent
时将CallbakMask
参数设置为EAkCallbackType::Marker
,这样可以在回调函数中接收每次播放到WWise中添加Marker的位置,用于在游戏逻辑中做一些事情,比如显示音频对应的字幕。
UE中监听:
而且Identifier
也是从0开始的:
WWise中添加Marker:
在UE里PostEvent
时可以把CallbackMask
的值添加上Marker
,就可以在绑定的事件中接收播放过程中每次遇到的Marker了。可以用在播放的过程中监听显示字幕。
RTPC
Real-time Parameter Controls(实时参数控制,RTPC)用于根据游戏中发生的实时参数变化,实时控制各种 Wwise 对象(包括音效对象、容器、总线、效果器等)的特定属性。
RTPC可以由程序端传递值给WWiese端,WWise端可以根据 传入的值控制声音。
在WWise中RTPC:
Switch
使用Switch可以在引擎中切换不同的模式,比如玩家走在不同地面上的声音。
在PostEvent
一个AkEvent
之后,可以通过SetSwitch
传入SwitchGroup
和SwitchState
以及PostEvent时传递的Actor.
Sequencer
WWise提供了在Sequence中使用的支持,可以在Sequence中使用AkAudioEvenes
和AkAudioRTPC
,从而实现在Sequence中来播放和控制Event.
Animation
并且可以使用在动画通知中:
给该通知指定Event:
CommandLet
WWise提供了UE中的Commandlet,用于处理批量生成Bank的功能。
使用介绍:
1 | Commandlet allowing to generate Wwise SoundBanks. |
生成SoundBank的Commandle为:
1 | Engine/Binaries/Win64/UE4Editor.exe "D:/WwiseDemoGame.uproject" -run=GenerateSoundBanks -platforms=Windows,Android,iOS -wait |
-wait
参数是我加在CommandLet中的,可以用来控制执行完毕后等待用户输入,避免执行窗口一闪而过的情况。
当没有指定生成哪些SoundBank时,会把项目中所有定义的SoundBank生成,如果想要自己指定,则可以使用-banks=aaa,bbb,ccc
等形式来指定。
最终的执行命令为(可以看到-ImportDefinitionFile
参数):
1 | "C:\Program Files (x86)\Audiokinetic\Wwise 2019.1.9.7221\Authoring\x64\Release\bin\WwiseCLI.exe" "D:/WwiseDemoGame/UnrealWwiseDemo/UnrealDemo.wproj" -GenerateSoundBanks -Bank ExtSrcBnk -Bank AmbientBank -Bank ReverbBank -Bank VelocityBank -Bank MatineeBank -Bank SubtitleBank -Bank SwitchBank -ImportDefinitionFile "D:/WwiseDemoGame/TempDefinitionFile.txt" -Platform Windows -SoundBankPath Windows "D:\WwiseDemoGame\Content\WwiseAudio\Windows" -Platform Android -SoundBankPath Android "D:\WwiseDemoGame\Content\WwiseAudio\Android" -Platform iOS -SoundBankPath iOS "D:\WwiseDemoGame\Content\WwiseAudio\iOS" |
拆分命令来看:
- WWiseCLI.exe的路径
-GenerateSoundBanks
:传递给WWiseCLI.exe标识用于生成SoundBank-Bank
:指定Bank的名字-ImportDefinitionFile
:指定TempDefinitionFile.txt
文件-Platform
:指定平台-SoundBankPath
:指定生成平台保存到的目录
命令行参数
-nosound
:关闭WWise的声音。
1 | bool FAkAudioDevice::EnsureInitialized() |
全局暂停音乐
可以通过以下代码实现:
1 | // on enter backgraound |
热更WWise的Bank
根据上面的分析可以看到, WWise在生成Bank时以平台为单位生成,每个平台都会生成对应的文件夹。
这要求我们在热更时需要根据不同的平台包含不同的外部文件,HotPatcher已经支持了这个特性,可以实现特定平台包含特殊的文件。
WWise集成至UE的缺点
官方提供的Event导入和指定Bank的流程太繁琐了,每个Event都需要拖到Content Browser里才可以创建出UE的Event资源,然后还需要打开手动指定RequireBank,十分的麻烦。其实WWise编辑器中本身包含了Bank的编辑功能,但是在UE中官方没有提供方法可以批量地生成UE里的Event和Bank资源,这个可以作为业余扩展开发的点,自己搞一个批量导入的功能,不过是后话了,有时间再来搞。
文档
- WWise中的概念概述:Wwise 概念概述
- DLC 注意事项及限制