欢迎光临散文网 会员登陆 & 注册

浅谈 Wwise WWU文件处理

2023-04-15 19:14 作者:kkxszz  | 我要投稿

什么是Wwise的WWU文件

Wwise工程中,音频结构和内容的数据存储都是以WorkUnit为单位。WorkUnit在硬盘中以.wwu文件的形式进行储存。

wwu的文件格式并不神秘,用记事本/notepad等软件进行预览,可以看到第一行就有它的格式声明:

由此可以看出它是一个遵循xml(可扩展标记语言)格式的文本文件。也就是说,只要掌握了xml的编辑方法我们可以直接在外部编辑wwise的workunit。

 

为什么需要编辑WWU

通常来说,针对Wwise结构的管线操作,大部分情况下使用WAAPI都能顺利解决(e.g. 定制规则的批量导入、批量修改属性、批量建立Wwise结构)。

但是Waapi还是有一定的局限性:

1. 必须开着Wwise工程才能使用Waapi功能。

2. Waapi的操作是较慢的。(根据你的流程和量级,可能会要数秒至十几秒。WWU的编辑则是毫秒级的)【更正一下,Wwise2022速度快了非常多!】

3. 并不是所有的“Wwise操作”都能通过waapi实现。


当遇到这些局限性时,直接编辑WWU便成为了一个可选的方向。(另外的方向是二次开发Wwise)


如何编辑xml的内容

1) 理解xml的结构

每个由 <tag></tag> 闭合的一个区域叫做一个元素(element),每个元素的完整结构如下

<tag attrib> text <若干子元素> </tag> tail

子元素中可以再嵌套子元素,构建出完整的文件。

 

为方便理解,下面以一个Events类目下的DefaultWorkUnit为例进行解释。


用NotePad打开它后,首先能看到第一行是xml的格式声明。

第2行至20行是<WwiseDocument>元素。它的attrib里记录了该WWU是个“WorkUnit”、它的guid等信息。

第3行至19行的<Events>则是WwiseDocument的子元素,这表示它在Events目录下。

第4行至18行,则是<WorkUnit>元素。它的attrib里面记录了workunit的具体名字、guid。 PersistMode为“Standalone”代表着它是在根目录下,从属于其他目录下的workunit这里会显示“Nested”。

接下来让我们展开第5行的ChildrenList元素:

此时可以看到,ChildrenList里面包含了一个Event元素,Event的attrib包含了它的命名和Guid。

再展开它的ChildrenList,可以看到该Event包含的Action信息。

此时笔者在同一个Event中再加入一个Action,可以看到Event的子元素里面多了相应的信息。

对比可以看出,“Play”这个Action所需的信息特别少,只有一条target引用。而“Stop”这个Action,不仅有引用,还有各类属性信息。此例中,Delay和FadeTime填写了非默认值,被记录在文本中,而Fade-out Curve是默认值,不被记录在文本里。


同理,想要掌握其他类型的Wwise对象的数据格式,只要按照类似的方法进行观察比对即可。

 

2) 编辑xml的方法

理解Wwise中WWU的格式规律之后,需要的时候就可以直接编辑WWU文本来修改Wwise工程内容。

要程序化这个操作,笔者推荐使用Python标准库中的xml.etree.ElementTree模块来进行xml的读写。

这个库较为常用并且有大量的中文教程可参考

参考资料推荐:

https://docs.python.org/zh-cn/3/library/xml.etree.elementtree.html

https://www.cnblogs.com/ifantastic/archive/2013/04/12/3017110.html


PS:如果使用XPath的话,要额外注意python版本。

 

3) 代码示范

例1:在workUnit内找到第一个名为in_MusicName的MusicSwitchContainer元素

因为上述同类操作(根据Attrib的某个值寻找元素)非常常见,可以封装一下:

如果想用XPath来实现也可以:

例2:在指定的音乐的WorkUnit下,生成一个新的WorkUnit

def createWorkUnit_Music(self,in_parentFilePath,in_newWwuName):

        rootPath,fullname=os.path.split(in_parentFilePath)

        parentName,ext=os.path.splitext(fullname)

 

        # 1.1读取母级的wwu

        tree_parent=ET.parse(in_parentFilePath)

        root_parent=tree_parent.getroot()

       

        # 1.2找到新的WorkUnit应该写入的位置

        workUnit_parent=self.findElementByAttrib(root_parent,"WorkUnit","Name",parentName)

        if workUnit_parent==None:

            print("parent 里读取workunit失败!终止")

            return

       

        ChildrenList=workUnit_parent.find("ChildrenList")

        if ChildrenList==None:

            print("create ChildrenList")

            ChildrenList=ET.SubElement(workUnit_parent,"ChildrenList")

       

        # 1.3 在childrenList下写入新的元素(对新建的wwu文件的引用)。此时确定了新WorkUnit的GUID

        newID=self.generateGUID()

        _attrib_parent={

            "Name": in_newWwuName,

            "ID": newID,

            "PersistMode": "Reference"

        }    

        ET.SubElement(ChildrenList,"WorkUnit",_attrib_parent)

       

        # 1.4 存储母级的ID以备使用

        parentId=workUnit_parent.get("ID")

        if not parentId:

            print("获取parentId失败!终止")

            return

       

        # 1.5 美化格式并且覆盖母级WWU文件

        self._pretty_xml(root_parent,'\t','\n')

        tree_parent.write(in_parentFilePath,encoding="utf-8",method="xml",xml_declaration=True)

       

       

        # 2.1 准备新建wwu,先写入最外层的WwiseDocument元素的信息

        _attrib_WwiseDocument={

            "Type": "WorkUnit",

            "ID": newID,

            "SchemaVersion": "103",

            "RootDocumentID": parentId,

            "ParentDocumentID": parentId

        }

        newroot=ET.Element("WwiseDocument",_attrib_WwiseDocument)

       

        # 2.2 写入InteractMusic元素的信息

        InteractiveMusic=ET.SubElement(newroot,"InteractiveMusic")

        _attrib_newWorkUnit={

            "Name":in_newWwuName,

            "ID": newID,

            "OwnerID": parentId,

            "PersistMode": "Nested"

        }

        ET.SubElement(InteractiveMusic,"WorkUnit",_attrib_newWorkUnit)

       

        # 美化并且创建WWU文件

        self._pretty_xml(newroot,'\t','\n')

        newTree=ET.ElementTree(newroot)

        newTree.write(rootPath+"\\"+in_newWwuName+".wwu",encoding="utf-8",method="xml",xml_declaration=True)

        pass

 

创建完的WWU长这个样子:

4) 注意事项

1. Wwise的wwu文件格式是不断更新的,进行相关工具制作的时候一定要确认好每个写入步骤都和Wwise当前的wwu格式一致。本文的案例包括103版本(2021.1.5)和110版本(2022.1.1)


2. 因为不使用Waapi,所有版本控制必须额外完成

   比如上文例2,在一个workunit下级创建新的workunit,需要迁出它母级WWU才能操作。配套使用P4的模块进行操作即可。

    

如何制作出Wwise的WWU格式

缩进、换行

当你试着用ElementTree读取了wwise的某个现有的wwu文件,再原样导出,你会发现你生产出的wwu文件长这个样子:所有的信息都写在了一行。

虽然排版不影响Wwise的读取,但是对debug阅读还是不方便,因此我个人采用了如下文章里介绍的排版方法,排版出的结果和Wwise基本一致。

                         https://blog.csdn.net/u012692537/article/details/101395192

下面则是同一个xml进行美化排版后的结果:

 

Attrib排序问题

上面一个问题解决以后,你还会发现你的输出的Attrib和Wwise的格式略有不同:

我们使用ElementTree输出的xml文件,它的Attrib,会强制按照字母来排序(ID在Name的前面)。它不影响任何功能,只是变得不易读了。

只要在ElementTree的源码中修改 _serialize_xml() 中的一行代码即可解决:

以上是改完的结果,原文为“ for k,v in sorted(items): ”


参考文献:https://cxyzjd.com/article/weixin_42997255/100084091

最终,我们得到了和Wwise自行生成的xml一样的格式:


如何生成GUID

从Wwise自己生成的ID的规则可以看出:Wwise使用的是4代的GUID,并且转换成了纯大写。

因此生成的方式如下:

 

总结

本文分享了笔者在制作wwu编辑工具时的学习路径。扫清这些障碍之后,WWU文件的程序化修改就成为了可能。这种“土法炼金”方式,可以在不二次开发Wwise的情况下,辅助Waapi,拓展Waapi工具链。

 

 

 

 

 


浅谈 Wwise WWU文件处理的评论 (共 条)

分享到微博请遵守国家法律