Python 实现 Unity 资源检测

Python 实现 Unity 资源检测

问题背景

需求:需要在项目中进行诸如判断 ScriptableObject 文件引用的材质或者贴图有没有丢失的工作

问题:直接调用 Unity 接口,需要执行 Unity 命令行,Unity 启动耗时太大,会导致检查脚本执行过久。

解决方式:使用 python 直接解析 AssetDataBase

知识背景

Unity 资源导入流程

放入 Unity 的美术资源(称之为 源资源 )的格式往往不是最终能使用的格式,比如常用来保存贴图的无损贴图格式 png,并不能直接提交给 GPU 采样,最后要转换成平台兼容的格式,比如 ASTC,这个转换之后的最终文件,依赖于源资源(可能不止一个,比如考虑图集的情况),以及资源的 导入设置 ,具体而言就是 源资源 对应的 meta 文件存储的设置,对于每个 源资源,Unity 会分配一个 GUID

ScriptableObject

Unity 提供了名为 ScriptableObject 的基类来存储各类自定义序列化对象,该类生成的文件也是一种 源资源,也有对应的 GUID ,它具体而言是一种名为 yaml 的序列化格式文件(meta 文件也是相同的格式)。ScriptableObject 上引用的各类源资源,最后会被记录成 GUID 保存在 yaml 格式的文本中。

SourceAssetDB

上文提过Unity 会为源资源分配 GUID,除此之外还会记录一些资源导入的设置等,该结果会被 Unity 保存到 Library\SourceAssetDB 下,此外其中还保存着源资源注入最后修改时间、hash等信息。(注:本文说的都是使用了 Asset pipeline v2 的情况)

LMDB

Unity 将 SourceAssetDB 保存成了名为 LMDB 的数据库文件,这是一种 key-value 型的数据库。

工程实现

解析 ScriptableObject

ScriptableObject 是 yaml 格式的文件,python 下直接使用 yaml 库即可解析,但要注意 Unity 添加了一些自定义 tag,这里简单处理跳过头三行字符串。

def read_ymal(file_path):  
    file = None  
try:  
    file = open(file_path, 'r')  
    lines = file.readlines()  
    #跳过开头三行的 unity 的自定义 Tag 与 handler 
    return yaml.safe_load("".join(lines[3:]))  
except Exception as e:  
    print("read ymal exception : {} ".format(e))  
    return None  
finally:  
    if file:  
        file.close() 

读取 LMDB

可以使用 lmdb 的 python 实现功能, 它提供的 lmdb.open 接口要求数据库目录下有两个文件, data.mdb lock.mdb ,可直接将 Library 下的 SourceAssetDB 与 SourceAssetDB-lock 拷贝成这两个文件(其实有另外的法子,但为了安全不直接使用原 DB 文件,就这样处理了,可以优化一下减少拷贝开销),然后就能直接读取数据库了,下面以检查 GUID 是否存在为例

#拷贝 DB 文件 
shutil.copyfile(DB_path, DB_new_path)  
shutil.copyfile(DB_lock_path, DB_new_lock_path)  
def is_exist_guid(guid):  
    env = None  
    db = None  
try:  
    env = lmdb.open(os.path.join(get_imdb_path()), map_size=1024*1024*30, subdir = True, readonly = True, max_dbs = 1)  
    db = env.open_db("GuidToPath".encode())  
    with env.begin(db) as txn:  
        return txn.get(guid_str_to_lmdb_bytes(guid)) != None  
except Exception as e:  
    print("is_exist_guid exception : {}".format(e))  
    return True  
finally:  
    if env: