找回密码
 立即注册
查看: 193|回复: 6

【UE5】Wwise基础(一):创建为UE服务的Wwise工程

[复制链接]
发表于 2023-3-10 16:52 | 显示全部楼层 |阅读模式
游戏音频和电影音频是不一样的,游戏强调的是交互性的音频。电影中音频可以直接在时间轴上播放,但游戏音频与交互强相关,一个音频往往与一个玩家交互或是一个场景事件有关。而对音乐,游戏是无法预测玩家行为的,因此,游戏音乐在处理音乐过渡上会更谨慎,音乐的播放也与Gameplay强相关。
因此,游戏的音频如果想要带动玩家的情绪更困难,因为配乐师无法得知音乐播放时具体的画面,但设计精巧的交互音乐不仅同样能带动玩家的情绪,还能给玩家带来爽快的反馈感。
为了满足交互音频的设计,Wwise因应而生。Wwise是目前最广泛使用的游戏音频中间件之一,相比于引擎提供的解决方案,Wwise的工作流更独立,效果更高,而且Wwise收费并不贵。Wwise在你意想不到的任何一个地方都可能出现,经常和引擎与SpeedTree的logo一起登场,基本上就在宣告“我们是3A”。二月份哥们儿买的俩游戏(原子之心/霍格沃茨之遗)都有经典的UE+SpeedTree+Wwise三件套。
没有音频背景的朋友们不用害怕。Wwise关注的是音频如何播放,而非如何制作音频。


Wwise官方提供了101~301系列课程帮助大伙儿入门Wwise,但Wwise官方只提供了与Unity共同开发的系列课程(Wwise 301),本系列文章内容包含Wwise101, 201的所有内容在UE中的使用,相对而言,属于基础篇,适合从未使用过Wwise的朋友和只在Unity中使用过Wwise的朋友。也许会有讲优化和插件开发的进阶篇,但目前,我们只来关注基础内容。
一. 准备工作

首先,我们会使用Wwise 101的课程文件,下载链接在这里。
记得到官网去下载一个Wwise,然后注册一个账号。在UE中创建一个项目,随便命名即可。我使用的版本是5.0.3,Wwise暂时不支持5.1
官网:
我创建了一个第三人称模板项目,命名为WwiseTutorial。
接下来,我们通过在UE中播放一个随机音频来学习如何在UE中使用Wwise
二. 在引擎中导入Wwise插件并链接工程

Wwise中也有“工程”的概念,因此,我们需要将Wwise工程与UE工程链接起来,并且我们需要安装Wwise插件,如果这个东西由用户手动管理的话会很繁琐,好在Wwise为我们提供了自动化。


在Wwise Launcher->Unreal中可以看到你的所有工程,直接Integrate Wwise into Project就可以。
如果没有,可以在这里手动选择,而且也可以在这里建立一个DemoGame方便学习。


完事之后在面板上选择Open in Wwise就可以了。
在项目的Plugins中应该可以看到Wwise了。


三. 关注Wwise Project

我们项目的Wwise工程看起来像这样:


这个面板看起来很复杂,一些胆小的朋友看到就直接劝退了,不过没关系,我们一步一步的解锁它的功能,先从最简单的东西开始。
如果读者已经熟悉Wwise工程了,可以看一下我在文章末尾附上的何如组建3A级Wwise工程,也许会对你有帮助。
我们先关注Audio面板:


在这里,我们看到了很多不同的文件夹,AudioDevice,MasterMixer等等,他们各自对应不同的功能,比如Actor-Mixer Hierarchy一般对应SFX等,Interactive Music Hierarchy一般对应交互化的BGM。
每一个Hierarchy底下都有一个Default Work Unit,你可以把它理解成一个文件夹,文件夹里还有可以有别的文件夹,一个示例如下:
Actor-Mixer Hierarchy
    Default Work Unit
    Player Work Unit
        Footsteps Work Unit
            Footsteps Container..
            ...
            ...
        Battle Work Unit
            Weapon Work Unit
            ...
    NPC Work Unit
...我们的教程遵从简单原则,项目组往往有自己的组织规范,如果想学习一下组织规范,可以看文末参考资料内容。
四. 创建我们的第一个SFX

在Actor-Mixer Hierarchy中,对着Default Work Unit右键,New child建立一个Random Container。


一个Random Container包含着一堆SFX,他会随机播放Random Container中的音频。
我们将这个Random Container命名为Gem_breaking,他用来播放宝石魔法释放后宝石碎屑落在地上的声音。
对着我们创建出来的Gem_breaking右键,Import Audio Files,把lession 2中的音频文件Add Files过去




还有一种简单的创建Random Container的方法,直接右键Work Unit,然后Import Audio Files,Add Folder,并选择为Random Container


弄好了之后就可以按小三角播放试试了:


右上角一个Play Type,选择Shuffle的话,那么知道所有的音频都被随机过之前,已经随机到的音频不会再次播放。repeating last则保证最多连续播放一个音频几次。
五. 创建一个Event

我们创建好了一个音频,现在我们要学习一个Wwise的设计理念:音频的播放是基于Event的,一个Event触发一个播放音频的行为,具体播放了哪个,怎么播放,外界无需知道,只需知道触发哪个事件即可。
这种思维非常的游戏化:策划只知道要在挨打的时候播放一个挨打的音效,但音频设计师思考的会更全面:混响?随机?这种思维将两个工作流隔离开来,抽象为事件接口。对于策划,只需要提出播放挨打音频需求,程序根据event的名字调用一下,音频设计师完全不需要关注其他人的工作。
现在,我们试着在Wwise里创建一个Event,他所做的事情就是播放我们刚才的那玩意儿。


跑到Events面板里->Events->Defailt Work Unit创建一个Event,我将其命名为Play_Gem_Breaking


回到Audio,把我们刚刚做的音频拖进中间的面板就好了,可以按下下面的小三角播放一下。
六. 创建SoundBank

SoundBank是Wwise最终到处给引擎的东西,这里面Wwise会做很多东西,比如把Events封装一下等。
此外,你可能在使用很多音频,但这些音频共用了一个音频文件的一部分波段,Wwise会做一些优化,做到只引用,不拷贝。也有可能,音频的某个波段始终没有被任何Audio引用(由于音频剪辑),Wwise会帮你删掉。


我们来到这里创建一个SoundBank,命名为SB_test
仿照之前的步骤,把我们的Event拖进去:


右键,Generate一下,我这里只Generate了Win平台。


一般的,生成的结果路径为GameDir/[WwiseProkectName]/[WwiseProjectName]_WwiseProject/GeneratedSoundBanks中


六. 在Unreal Engine中播放它

在UE中使用Wwise比Unity更简单:打开项目,在Project Setting中找到Wwise


将Auto connect To WAAPI勾开。
来到Integration Settings中,将Wwise AudioLink Enabled勾开,重启编辑器


把Intergration Settings中的Generated Sound Banks Folder设置为我们刚刚提到的Soundbank生成路径。


在UE的编辑器中选择->窗口->WWise Picker


我们已经看到我们的Play_Gem_Breaking事件了,你可以把它拖到Content Browser中:


然后,我们可以在蓝图中播放他,很简单,但这里我们皮一下,使用动画通知,WWise插件帮我们做了一个简单的通知,当然兄弟们也可以自己实现,反正实现AnimNotify很简单,在动画MF_Run_Forward中:




播放动画试试,在浏览窗口就可以听到音频了,听不到的话检查一下音响有没有插电。
我们来看一下AnimNotify_AkEvent的内容吧:


除去报错信息,AnimNotify_AkEvent是这样处理的:
音频需要跟随物体运动吗?
  否:在对应位置播放
  是:检查是否有Ak Component
    没有:摆烂,报错
    有:可用吗?
      不可用:摆烂,报错
      可用:播放音频,并且音频跟随组件他还有一种播放音频的方法是直接PostEvent,但是我们看它的源码可以知道,如果找不到AkComponent,他也会摆烂不播了:
AkPlayingID PostEvent(UObject * Object, FAkAudioDevice * AudioDevice, FWwiseEventTracker & EventTracker, float CurrentTime)
        {
                ensure(AudioDevice != nullptr);

                if (EventTracker.EventName.IsEmpty())
                {
                        UE_LOG(LogAkAudio, Warning, TEXT("Attempted to post an AkEvent from an empty Sequencer section."));
                        return AK_INVALID_PLAYING_ID;
                }

                if (Object && AudioDevice)
                {
                        auto AkComponent = Cast<UAkComponent>(Object);

                        if (!IsValid(AkComponent))
                        {
                                auto Actor = CastChecked<AActor>(Object);
                                if (IsValid(Actor))
                                {
                                        AkComponent = AudioDevice->GetAkComponent(Actor->GetRootComponent(), FName(), NULL, EAttachLocation::KeepRelativeOffset);
                                }
                        }

                        if (IsValid(AkComponent))
                        {
                                AkPlayingID PlayingID;
                                if (EventTracker.Event)
                                {
                                        PlayingID = EventTracker.Event->PostOnComponent(AkComponent, nullptr, &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker, (AkCallbackType)(AK_EndOfEvent | AK_Duration), nullptr, AkComponent->StopWhenOwnerDestroyed);
                                }
                                else
                                {
                                        const AkUInt32 ShortID = AudioDevice->GetShortID(nullptr, EventTracker.EventName);
                                        PlayingID = AudioDevice->PostEventOnAkComponent(ShortID, AkComponent,
                                                AkCallbackType::AK_EndOfEvent | AkCallbackType::AK_Duration,
                                                &FWwiseEventTracker::PostEventCallbackHandler, &EventTracker);
                                }

                                if (PlayingID != AK_INVALID_PLAYING_ID)
                                {
                                        AkComponent->SetStarted(true);
                                       
                                }
                                EventTracker.TryAddPlayingID(PlayingID);
                                if (EventTracker.IsDirty)
                                        LogDirtyPlaybackWarning();
                                return PlayingID;
                        }
                }
                return AK_INVALID_PLAYING_ID;
        }
可以看到如果AkComponent = AudioDevice->GetAkComponent(Actor->GetRootComponent(),FName(),NULL, EAttachLocation::KeepRelativeOffset);执行之后如果AkCompoennt还是为空就啥也不干了。
这种设计被Wwise官方称为基于GameObject的设计(这命名,难怪官方只有Unity教程),它的好处就是它可以实时计算播放音频的游戏物体的状态,使用它的信息,如位置,遮挡等。同时,它也可以用来处理一些边际条件,比如在说话的NPC被打死了之类的。
七. 推荐阅读

突然发现资料忘记贴了,补上:
如何创建一个3A级别的Wwise工程
Wwise 301
Wwise 201
Wwise 101

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-3-10 16:59 | 显示全部楼层
正好要开始搞wwise,感谢分享[干杯]
发表于 2023-3-10 17:02 | 显示全部楼层
很高兴帮到你[爱]
发表于 2023-3-10 17:07 | 显示全部楼层
想请教下,generate sound data并不会自己创建asset,需要从picker里手动拖到unreal里是吗?
发表于 2023-3-10 17:14 | 显示全部楼层
是的
发表于 2023-3-10 17:18 | 显示全部楼层
修改音量使用哪个api呢  set rtpc value吗
发表于 2023-3-10 17:20 | 显示全部楼层
用RTPC可以的
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-17 13:45 , Processed in 0.099744 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表