利用 Wwise Time Stretch 插件驱动游戏中的过场动画

音频编程 / 游戏音频

简介

这是徐若昊撰写的三篇技术系列博文的第一篇。该系列博文主要分享《逆向坍塌:面包房行动》背后的声音设计。在本文中,他深入探讨了如何利用 Wwise Time Stretch 插件驱动游戏中的过场动画。第 2、3 部分很快就会发布,敬请期待!

徐若昊最近在 Audiokinetic 博客上发表了另一篇题为《逆向坍塌:面包房行动 | Wwise 在远程协作中的重要作用》的博文。他和保罗•鲁斯卡 (Paul Ruskay) 在文中分享了自己如何管理角色及定制动画声音设计、为游戏构建互动音乐系统… 

 

利用 Wwise Time Stretch 插件驱动游戏中的过场动画

技术系列博文 | 第 1 部分

为了解决因为使用 Alt+Tab 在程序窗口之间快速切换或由于性能限制导致帧率卡顿之类情况可能造成的音视频同步问题,我们在《逆向坍塌》中基于 Wwise 时钟而非单纯依赖游戏内时钟编写了音视频同步代码。另外,还专门为快慢动作效果提供了支持,以便更好地呈现游戏中的过场时刻。为此,我们充分运用了功能强大且灵活多用的 Wwise Time Stretch 插件。这些举措让我们得以解决前面所说的难题。注意,“过场动画”一词涵盖各种游戏引擎(如 Unity Timeline 和 Unreal Sequencer)。不过,我们的示例将在 Unity 环境下展示,因为《逆向坍塌》是用 Unity 构建的。

基本原理

那么,我们先从基本原理说起。背后的理念其实很简单,对各种游戏引擎都适用。

若当前音频播放时间慢于当前视频播放时间,则暂停视频。
若当前音频播放时间快于视频播放时间,则将视频播放进度调到跟音频播放时间同步。
若两个时间值相等,则不执行任何操作,因为这时候系统已经处于完美同步状态。

该游戏是使用 Unity 构建的。所以,下面就用 C# 伪代码演示一下如何实现上述概念:

private void AudioVideoSyncLogic()
{
    if (audioTime < videoTime)
    {
        video.Pause();
    }
    else if (audioTime > videoTime)
    {
        video.Play();
    }
    else
    {
        continue;
    }
}

通过以上代码段,我们可以将相应功能集成到 Unity 默认的 Update() 函数中,确保在播放过场动画时每帧执行一次该函数。不过,要注意将时间线的 Update Method 由默认的 Game Time 改为 Manual 以确保其生效。

img1

上图展示了 RCAudioClockSync.cs 脚本的具体设置。在此,我们还可以把要播放的 Event 或环境声作为字符串输入来传递。

解决问题

在将上述代码集成到脚本中时会遇到诸多问题,我们需要就此做出修改和优化,以确保在游戏的各种互动场景中过场动画都能正常播放。其中最先要解决的问题包括:

  1. 获取当前音频播放时间:确保脚本能够准确追踪音频播放进度。
  2. 在过场动画停止时将视频与音频同步:确保在过场动画结束时视频与音频同步停止。
  3. 提供相应的保障措施:在未正确设置过场动画的情况下提供相应保障。
  4. 开发与音频同步相关的 QA 功能:为测试团队开发可基于非同步默认设置来验证音频同步的辅助功能。

通过解决这些问题,我们可以提升脚本在处理游戏内过场序列时的稳定性和可靠性。

获取当前音频播放时间

Wwise 提供了一个对程序员非常有用的函数:GetSourcePlayPosition ()

GetSourcePlayPosition() 会返回与给定播放 ID 对应的当前音频播放时间。刚好,Wwise Unity Integration 中提供了相较于原生 C++ 调用更为简易的函数:

public static AKRESULT GetSourcePlayPosition(uint in_PlayingID, out int out_puPosition, bool in_bExtrapolate)

为此,我们首先通过给定播放 ID 调用该方法并获取返回结果。在返回 Ak_Success 时,会将当前音频播放时间传给参数修饰符 out_puPosition。若出于任何原因返回 Ak_Fail,则转而输出 -1。下面的伪代码展示了这一解决方案。注意,示例中省略了获取 playingID 变量的代码段。

public int GetSourcePlaybackPositionInMilliseconds(uint playingID, bool extrapolated)
{
    int returnPos = 0;

    AKRESULT returnResult = AkSoundEngine.GetSourcePlayPosition(playingID, out returnPos, extrapolated);
        
    if (returnResult == AKRESULT.AK_Success)
    {
        return returnPos;
    }
    else
    {
        return -1;
    }    
}

因此,通过调用 GetSourcePlaybackPositionInMilliseconds() 并将返回值指派给 audioTime 变量,我们可以实时获取当前音频播放时间。不过,要记得处理变量值为 -1 的情况并跳过音视频同步逻辑。

在过场动画停止时将视频与音频同步

前面说了 AudioVideoSyncLogic() 函数。在音频结束时,视频要无缝接续。也就是说,其会保持当前状态。理想情况下,若音频和视频同时结束,两者都应该停止。比方说,若视频在音频结束时暂停,其将保持暂停状态。这可能会导致游戏出现卡顿。相反,若音频文件在结尾有几秒无声内容,视频会继续播放。这可能会导致音频持续期间画面出现异常。为了避免引入破坏游戏体验的严重漏洞,我们需要在实现过程中解决这些问题。

有时可以通过销毁过场游戏对象本身来解决该问题,但是这种方法取决于很多超出音频部门控制的因素。事实上,我们可以制定一个简单、通用的解决方案,来在音频结束时明确停止播放视频。为此,我们只需将下面的伪代码添加到 AudioVideoSyncLogic() 函数的末尾。注意,示例中省略了获取 videoDuration 变量的代码段:

if (audioTime > videoDuration)
{
        video.Stop();
}

提供相应的保障措施

在开发当中,我们经常会遇到过场动画中有占位音频或完全没有音频的情况。在玩法上,个别情形下这种方法行不通,从而导致视频出现卡顿或游戏进程停滞。所以,我们不能单纯依赖前面说的方法来确保软件的稳定性。如果前面说的方法不管用,不管是在过场动画开发阶段,还是在玩家运行游戏的时候,都要提供相应的保障措施。这种方法与直接使用 Unity 的默认更新方法不同,尤其是在运行时,因为不应由玩家控制所用的更新方法。倘若在过场动画当间切换到默认更新方法,可能会引发比预期更多的已知问题。

以下函数展示了如何手动更新过场动画(本例中为备用方法)。注意,示例中省略了获取 videoTime 变量的代码段:

private void AudioVideoSyncFallbackLogic()
    if (videoTime < videoDuration)
    {
        videoTime += deltaTime
        video.Play();
    }
    else
    {
        video.Stop();
    }
}

在获取到有效的播放 ID 和音频时间时,会执行 AudioVideoSyncLogic()。对于其他情况,会执行 AudioVideoSyncFallbackLogic()

简而言之,在 update 或 tick 方法中,我们要包含以下代码段和函数调用(使用伪代码和示例):

private void LateUpdate()
{
    int audioTime = GetSourcePlaybackPositionInMilliseconds((uint)playingID, true);

    if (playingID != -1 && audioTime != -1)
    {
        AudioVideoSyncLogic();
    }
    else
    {
        AudioVideoSyncFallbackLogic();
    }
}

注意,我们在此处使用 LateUpdate() 确保在将音视频同步之前完全更新过场动画中的所有动画及其他元素(如 Unity Timeline)。这样在个别情况下即便没有正确更新某些内容也能获得较好的效果。当然,我们可以使用常规的 Update() 函数。但对于包含大量定制脚本和视觉元素的时间线,最好还是使用 LateUpdate()。具体实现方式可能因项目而异。有时还可在函数中添加自定义输入参数,来引用 Timeline 可播放内容或其他素材。

开发与音频同步相关的 QA 功能

为了便于排查过场动画相关问题,我们为 QA 团队添加了一个切换开关。藉此,可录制前后对比视频。该切换开关允许直接绕过音视频同步及其备用方法,并在录制视频前恢复为默认的 Game Time 更新方式。这样就可轻松确定问题究竟是由新增的音视频同步功能还是 Timeline 中的素材或脚本引起的。

利用 Wwise Time Stretch 插件为快慢动作效果提供支持

按照以下步骤操作应当可以构建一个适用于很多游戏的音视频同步系统。不过,每个项目都可能存在特殊情况,因而需要添加额外的定制功能。比如,在《逆向坍塌》中,我们需要为同步系统添加快慢动作效果,以此来突出过场动画中的精彩瞬间。

为了实现这一目标并确保音视频同步系统正常运行,我们需要将这些新功能集成到现有架构中。这个时候就要用到 Wwise Time Stretch 插件了。该插件可在不改变音高的情况下在 Wwise 中调整语音的播放速度。这跟我们的应用场景非常契合。<

要设置 Time Stretch,请打开 Project Explorer 层级结构,并转到与相关 Mixer、容器或音频源对应的 Effects 选项卡。在本例中,我们要在过场动画 SFX Actor Mixer 上进行配置。此 Actor Mixer 负责管理所有过场动画 SFX 的播放音轨。也就是说,在 Time Stretch 中做的任何更改都将应用于其下包含的所有过场动画 SFX。此设置会影响游戏中的所有过场动画时间线。这些时间线会依次播放(所述音轨将用于驱动 Unity Timeline)。

img2

该插件的大部分属性都可保留默认设置。不过,我们要通过代码创建 RTPC,以此修改 Time Stretch 属性。

img3

img4

根据官方 Wwise 文档,Time Stretch 插件的 Y 轴取值范围为 25 ~ 1600。该值代表原始声音时长的百分比。其中 25 表示播放速度快 4 倍,1600 表示播放速度慢 16 倍。为了简化计算,我们创建了一个 RTPC,其 X 轴取值范围为 0.25 ~ 16。该值代表原始音频实际播放速度的倒数倍数。只需将 1 除以这个数,就可得到可用的倍数。比如,1 除以 1/4 等于 4,表示播放速度快 4 倍;1 除以 16 等于 0.0625,表示播放速度慢 16 倍。

该设置的唯一缺点是只能在 0.25 ~ 16 之间设定时间拉伸倍数。在达到上限或下限之后,就没法再加快或减慢播放速度了。不过,对于我们的特定应用场景以及很多其他游戏,这样的取值范围已经绰绰有余,足以应对各种快慢动作情形。在《逆向坍塌》中,根据游戏设计和动画团队的要求,我们只设置了 0.25 ~ 4 的取值。

在本例中,我们要创建一个小型封装函数,以便提取参数修饰符的输出值,并根据需要在相应区域应用该值。

public float GetGlobalRTPC(string rtpcName)
{
    int rtpcType = 1;
    float acquiredRtpcValue = float.MaxValue;
    AkSoundEngine.GetRTPCValue(rtpcName, null, 0, out acquiredRtpcValue, ref rtpcType);

    if(acquiredRtpcValue >= 0.25f && acquiredRtpcValue <= 16.0f)
    {
        return acquiredRtpcValue;
    }
    else
    {
        return 1.0f;
    }
}

除了对 RTPC 做全局设定,上述函数还会确保在检测到错误的值时忽略要设置的 RTPC 并重置为默认值 1.0f。

最后,我们要将此函数添加到负责音视频同步逻辑的代码段中。现在,在执行播放过场动画的逻辑和视频结束后停止过场动画的逻辑之间插入以下代码行,并将计算得出的值指派给 Unity 的 timeScale 变量。

Time.timeScale = 1.0f / GetGlobalRTPC(“TimelineTimeDilation”);

通过上面说的各项措施,我们成功运用 Wwise 驱动了游戏内的过场动画时间线。另外,我们还实现了慢动作(慢至 1/16 倍速)和快动作(快至 4 倍速)等功能。并且,可根据需要自由地运用,来满足各种时长和帧率的需要。接下来,便可在 Unity Timeline 窗口中自由设置 RTPC。

img5

上图举例展示了第 1071 到 1078 帧的 1/10 倍速慢动作设定。在我们的实现中,要在慢动作尾帧之后(本例中为第 1079 帧)创建另一 RTPC 来将倍数手动重置为 1。

在 Wwise 的驱动下,声音和画面都能正确放慢速度,彻底解决了音画同步上的问题。藉此,可减少为快慢动作帧创建音频素材的需要,从而帮助声音设计师节省大量的开发时间。对动画设计师来说,则无需再反复对快慢动作曲线进行微调。

下面的视频片段展示了最终的效果。为了展示该功能的运作方式,视频中故意设置了多处帧卡顿。在片段的 0:23 处,还可听到由 Wwise Time Stretch 驱动的慢动作效果。

免责声明:本文中使用的代码段都是重构的通用版本,仅用于演示目的。其底层逻辑经验证可正常运行。不过为避开潜在的版权限制,示例中省略了特定于项目的 API 调用和函数。

徐若昊(Jater Xu)

音频程序员,技术音频

徐若昊(Jater Xu)

音频程序员,技术音频

徐若昊是一名经验丰富的音频程序员与技术音频,专门从事在Unreal和Unity中使用Wwise集成的交互式音频解决方案的相关工作,有着丰富的C++、虚幻蓝图和C#经验。他编写的音频系统是《家园3》、《The Chant》和《逆向坍塌:面包房行动》等备受赞誉的游戏中不可或缺的一部分。

评论

留下回复

您的电子邮件地址将不会被公布。

更多文章

简化 WAAPI

假如你之前从来没用过 Wwise Authoring API (WAAPI),我觉得不妨接着往下读,说不定对你有帮助呢。 我也知道,对于非编程人员,WAAPI...

21.8.2018 - 作者:亚当·克罗夫特(ADAM T. CROFT)

《权力的游戏·凛冬将至》手游:使用Wwise结合影视原声带和原创交互音乐

我叫刘子奇(Victor Liu),在游族网络(YooZoo Games)担任音频设计师与音频程序。我负责了《权力的游戏·凛冬将至》(以下统称为GOT)手游的音乐部分。...

22.7.2019 - 作者:刘子奇

多人游戏如何利用语音聊天大幅提升玩家体验

最近为了抗击新冠疫情,很多人不得不宅在家里,使得手机游戏和主机游戏异常火爆。据游戏网站 SteamDB 统计,在三月中旬光是 Steam 玩家就创下了 2030...

19.10.2020 - 作者:TechJVB

如何打造一款独具特色的节奏解谜游戏

一年多前,当我被叫去开发一款新的游戏时,没想到居然是大名鼎鼎的《Tetris®》系列游戏。我在 Amber 的同事罗德里戈•费祖利建议使用 Wwise...

28.3.2023 - 作者:尤利尔•奥罗斯科 (Uriel Orozco)

《Midas Merge》背后的声音设计

本文将介绍《Midas Merge》音频设计的心路历程以及 Wildlife 音频团队如何利用 Wwise 为这款合并类益智游戏打造音效。...

9.6.2025 - 作者:Wildlife Studios

《Headbangers: Rhythm Royale》背后的声音设计 | 让鸽子跟着节奏肆意舞动

《Headbangers: Rhythm Royale》是全球首款竞技类在线音乐游戏。在游戏中,您将与另外 29...

17.9.2025 - 作者:Charles Bardin

更多文章

简化 WAAPI

假如你之前从来没用过 Wwise Authoring API (WAAPI),我觉得不妨接着往下读,说不定对你有帮助呢。 我也知道,对于非编程人员,WAAPI...

《权力的游戏·凛冬将至》手游:使用Wwise结合影视原声带和原创交互音乐

我叫刘子奇(Victor Liu),在游族网络(YooZoo Games)担任音频设计师与音频程序。我负责了《权力的游戏·凛冬将至》(以下统称为GOT)手游的音乐部分。...

多人游戏如何利用语音聊天大幅提升玩家体验

最近为了抗击新冠疫情,很多人不得不宅在家里,使得手机游戏和主机游戏异常火爆。据游戏网站 SteamDB 统计,在三月中旬光是 Steam 玩家就创下了 2030...