【Unity】【Wwise】在Unity中获取某个Wwise事件的持续时间

【Unity】【Wwise】在Unity中获取某个Wwise事件的持续时间


有一次接到这么一个需求:要在界面上显示出一段人物语音的长度,并且在播放的时候进行倒计时。当时看到这个需求的时候,心想就直接调官方提供的函数获取事件长度呗,但在做的时候就发现,自己天真了。

在官方提供的函数接口中(AkSoundEngine类),我翻来覆去找过很多次,并没有获取某个事件的持续时间的函数,只能自己想方设法的获取该信息,那从哪里能拿到呢?

当时的情况是,我们项目会在Timeline中使用Wwise事件来配置音频,在Timeline中,事件是可以看到长度的。(事件名涂掉了,放置泄露项目信息,虽然我也不知道为什么我要这么谨慎。。)
在这里插入图片描述
在这里插入图片描述
顺藤摸瓜找对应脚本看看,怎么得到事件长度的。

在项目中搜索AkTimelineEventPlayable脚本,可以发现是通过AkUtilities.GetEventDurations委托来获取的,最终这个委托是:

在这里插入图片描述
找到了?不要急,看脚本文件的最上面,该脚本内容只可用于UnityEditor下,在最终打包后是无法调用的,包括AkWwiseProjectInfo脚本也是。

但既然在UnityEditor下可以获取,那我自己取来复制一份在项目中就可以了呗。

那么,解决方案就来了~

解决方案

直接上代码

  • WwiseInfoCollector.cs,编辑器下的菜单脚本
[MenuItem("WWISE/Collect Event Duration")]
private static void GenerateCharacterVoiceInfo()
{
    // NOTE 此处读取资源的类和方法是我们项目封装的,须自行替换。
    // 读取判断是否已有.asset资源,若没有,则创建
	ScriptWwiseInfo wwiseInfo = AssetLibrary.LoadAsset<ScriptWwiseInfo>(WwiseInfoHolder.WwiseInfoDataPath, true);
	if (wwiseInfo == null)
	{
		wwiseInfo = ScriptableObject.CreateInstance<ScriptWwiseInfo>();
		AssetDatabase.CreateAsset(wwiseInfo, WwiseInfoHolder.WwiseInfoDataPath);
	}

	var wwiseProjectData = AkWwiseProjectInfo.GetData();
	// 转存信息
	wwiseInfo.EventInfos = new List<WwiseEvent>();
	foreach (var wwu in wwiseProjectData.EventWwu)
	{
        // 此处是根据事件的路径规则,只对人物语音部分进行转存,自行替换规则或删去该判断以用于所有事件。
		if (wwu.ParentPath.Contains("Character"))
        {
			foreach (var wwuEvent in wwu.List)
			{
				wwiseInfo.EventInfos.Add(new WwiseEvent()
				{
					Id = wwuEvent.Id,
					Name = wwuEvent.Name,
					DurationMin = wwuEvent.minDuration,
					DurationMax = wwuEvent.maxDuration,
				});
			}
			break;
		}
	}
    // 保存.asset文件
	EditorUtility.SetDirty(wwiseInfo);
	AssetDatabase.SaveAssets();
}
  • WwiseInfoHolder.cs,运行时获取事件长度使用的脚本。其中有一个叫PlayNormalPrefix的变量,是我们项目里面播放音频事件的统一前缀,不是必须(其他可能还有Stop_前缀的事件等等)。实现分别使用字符串(事件名)和uint(事件id,可从Wwise_IDs脚本中获取,具体查看下方边边角角第1条)两种参数的查询方法。
public class WwiseInfoHolder
{
    public const string WwiseInfoDataName = "WwiseEventDurations.asset";
    public static readonly string WwiseInfoDataPath = "Assets/Wwise/ScriptableObjects/" + WwiseInfoDataName;

    private readonly ScriptWwiseInfo _wwiseInfo;

    private const string PlayNormalPrefix = "Play_";

    public WwiseInfoHolder()
    {
        // NOTE 此处读取资源的类和方法是我们项目封装的,须自行替换。
        _wwiseInfo = AssetLibrary.LoadAsset<ScriptWwiseInfo>(WwiseInfoDataPath, true);
        if (_wwiseInfo == null)
            WwiseLogger.LogMessage(WwiseLogger.LogLevel.Warning, "WwiseInfoData load failed.");
    }

    /// <summary>
    /// Get sound duration.Only for character voice currently.
    /// </summary>
    /// <param name="soundName"></param>
    /// <param name="durationMin"></param>
    /// <param name="durationMax"></param>
    public bool TryGetCharacterVoiceDuration(string soundName, out float durationMin, out float durationMax)
    {
        durationMin = -1;
        durationMax = -1;

        if (_wwiseInfo == null)
        {
            WwiseLogger.LogMessage(WwiseLogger.LogLevel.Warning, "WwiseInfoData was not loaded.");
            return false;
        }

        var eventName = PlayNormalPrefix + soundName;

        var result = _wwiseInfo.EventInfos.Find((wwiseEvent) => wwiseEvent.Name.ToLower() == eventName.ToLower());
        if (result == null)
        {
            WwiseLogger.LogMessage(WwiseLogger.LogLevel.Warning,
                                   string.Format("Didn't find duration of event named <{0}>", eventName));
            return false;
        }

        durationMin = result.DurationMin;
        durationMax = result.DurationMax;
        return true;
    }

    /// <summary>
    /// Get sound duration.Only for character voice currently.
    /// </summary>
    /// <param name="eventId">Wwise event id, defined in <see cref="AK.EVENTS"/></param>
    /// <param name="durationMin"></param>
    /// <param name="durationMax"></param>
    public bool TryGetCharacterVoiceDuration(uint eventId, out float durationMin, out float durationMax)
    {
        durationMin = -1;
        durationMax = -1;

        if (_wwiseInfo == null)
        {
            WwiseLogger.LogMessage(WwiseLogger.LogLevel.Warning, "WwiseInfoData was not loaded.");
            return false;
        }

        var result = _wwiseInfo.EventInfos.Find((wwiseEvent) => wwiseEvent.Id == eventId);
        if (result == null)
        {
            WwiseLogger.LogMessage(WwiseLogger.LogLevel.Warning,
                                   string.Format("Didn't find event duration with id <{0}>", eventId));
            return false;
        }

        durationMin = result.DurationMin;
        durationMax = result.DurationMax;
        return true;
    }
}
  • ScriptWwiseInfo.cs,用于生成.asset文件的数据结构脚本。
[Serializable]
public class ScriptWwiseInfo : ScriptableObject
{
    public List<WwiseEvent> EventInfos;
}

[Serializable]
public class WwiseEvent
{
    public uint Id;
    public string Name;
    public float DurationMin;
    public float DurationMax;
}

注意事项

  • 有的事件是循环播放类型的,上述方法不可用于循环播放类型
  • 非播放类型的事件持续时长为0
  • 需要每隔一段时间或每次音频内容有更新或打包之前,调用一次上面的菜单脚本,保持最新的长度信息

边边角角

  1. uint型事件ID

    通过官方提供的方法来看(比如AkSoundEngine.Post()),有参数为uint类型的重载方法,标识事件的uint型ID是存于导出内容的Wwise_IDs.h脚本中的,可用于虚幻引擎。如果要用于Unity,需要进行转换。
    在这里插入图片描述
    在Unity菜单栏Assets>Wwise>Convert Wwise SoundBank IDs,但是这个方法总是需要去手选目录,然后将生成的脚本放入项目。所以直接在IDE中搜索他的菜单项名字,找到其转换的脚本AkWwiseIDConverter,将转换的函数抄过来,写一个直接根据路径读取.c文件转换到.cs并存于项目的函数。

private static readonly string s_converterScript = Path.Combine(
    Path.Combine(Path.Combine(Application.dataPath, "Wwise"), "Tools"),
    "WwiseIDConverter.py");

private static readonly string s_progTitle = "WwiseUnity: Converting SoundBank IDs";

[MenuItem("WWISE/Convert Wwise_Ids.h into C# and replace")]
public static void ConvertWwiseId()
{
    var wwiseAssetsPath = Path.Combine(Application.streamingAssetsPath, AkWwiseEditorSettings.Instance.SoundbankPath);
    var bankIdHeaderPath = Path.Combine(wwiseAssetsPath, "Wwise_IDs.h");
    if (string.IsNullOrEmpty(bankIdHeaderPath))
    {
        Debug.Log("WwiseUnity: User canceled the action.");
        return;
    }

    var start = new System.Diagnostics.ProcessStartInfo();
    start.FileName = "python";
    start.Arguments = string.Format("\"{0}\" \"{1}\"", s_converterScript, bankIdHeaderPath);
    start.UseShellExecute = false;
    start.RedirectStandardOutput = true;

    var progMsg = "WwiseUnity: Converting C++ SoundBank IDs into C# ...";
    EditorUtility.DisplayProgressBar(s_progTitle, progMsg, 0.5f);

    using (var process = System.Diagnostics.Process.Start(start))
    {
        process.WaitForExit();
        try
        {
            //ExitCode throws InvalidOperationException if the process is hanging
            if (process.ExitCode == 0)
            {
                EditorUtility.DisplayProgressBar(s_progTitle, progMsg, 1.0f);
                Debug.Log(string.Format(
                    "WwiseUnity: SoundBank ID conversion succeeded. Find generated Unity script under {0}.", bankIdHeaderPath));

                ReplaceOldWwiseIDsFile(wwiseAssetsPath);
            }
            else
                Debug.LogError("WwiseUnity: Conversion failed.");

            AssetDatabase.Refresh();
        }
        catch (Exception ex)
        {
            AssetDatabase.Refresh();

            EditorUtility.ClearProgressBar();
            Debug.LogError(string.Format(
                "WwiseUnity: SoundBank ID conversion process failed with exception: {0}. Check detailed logs under the folder: Assets/Wwise/Logs.",
                ex));
        }

        EditorUtility.ClearProgressBar();
    }
}

private static void ReplaceOldWwiseIDsFile(string wwiseAssetsPath)
{
    var bankIdCsharp = "Wwise_IDs.cs";
    var bankIdCSharpPath = Path.Combine(wwiseAssetsPath, bankIdCsharp);
    var wwisePackagePath = Path.Combine(Path.Combine(Application.dataPath, "Wwise"), bankIdCsharp);
    File.Replace(bankIdCSharpPath, wwisePackagePath, null);
}

小吐槽

Wwise资料真的少,官方文档倒是有,但真要解决个什么问题,网上都找不到方案,当然,也可能是我搜索的姿势不对。。

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值