一、引子
最近我们的项目由Unity2018升级到了Unity2019.4,但是突然间发现FBX资源导入时的后处理不生效了。经过一系列的实验,发现了升级到Unity2019以后,资源管线后处理中的一些坑,今天马三来和大家分享一下这个过程。
二、情况复现与原因排查
在我们的项目还使用Unity2018开发的时候,便有一个资源后处理的Editor代码,负责处理fbx类型文件导入时的一些自动化配置,比如:压缩动画曲线、优化模型的网格,关闭模型的Read/Write Enable选项等操作。同时这个自动化处理操作是只在资源第一次被导入的时候才会运行的,也就是说只有一个新的FBX刚进来的时候才会自动配置,在这之后我们还可以手动去调整一些参数。因此有了一步判断资源有没有被自动化后处理过的过程,用的是判断fbx对应的.meta文件存在与否,来指示这个fbx是否是被首次处理,代码如下所示:
代码语言:javascript复制 1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4 using UnityEditor;
5 using System.IO;
6
7 public class FBXPostProcesser : AssetPostprocessor
8 {
9 #region 模型处理
10 /// <summary>
11 /// 模型导入之前调用
12 /// </summary>
13 public void OnPreprocessModel()
14 {
15
16 //判断资源是否是首次导入
17 #if UNITY_2019_3_OR_NEWER
18 if (!assetImporter.importSettingsMissing)
19 {
20 assetImporter.userData = "v_0.0.1";
21 return;
22 }
23 #else
24 var metaPath = this.assetPath ".meta";
25 if (File.Exists(metaPath))
26 {
27 return;
28 }
29 #endif
30
31 Debug.Log("==模型导入之前调用==" this.assetPath);
32 ModelImporter modelImporter = (ModelImporter)assetImporter;
33
34 //模型优化
35 modelImporter.optimizeMesh = true;
36 modelImporter.optimizeGameObjects = true;
37 modelImporter.animationCompression = ModelImporterAnimationCompression.Optimal;
38 modelImporter.animationRotationError = 1.0f;
39 modelImporter.animationPositionError = 1.0f;
40 modelImporter.animationScaleError = 1.0f;
41 }
42
43
44 /// <summary>
45 /// 模型导入之后调用
46 /// </summary>
47 /// <param name="go"></param>
48 public void OnPostprocessModel(GameObject go)
49 {
50 //判断资源是否是首次导入
51 #if UNITY_2019_3_OR_NEWER
52 if (!assetImporter.importSettingsMissing)
53 {
54 return;
55 }
56 #else
57 var metaPath = this.assetPath ".meta";
58 if (File.Exists(metaPath))
59 {
60 return;
61 }
62 #endif
63
64 // for skeleton animations.
65 Debug.Log("==模型导入之后调用==");
66 List<AnimationClip> animationClipList = new List<AnimationClip>(AnimationUtility.GetAnimationClips(go));
67 if (animationClipList.Count == 0)
68 {
69 AnimationClip[] objectList = Object.FindObjectsOfType(typeof(AnimationClip)) as AnimationClip[];
70 animationClipList.AddRange(objectList);
71 }
72
73 foreach (AnimationClip theAnimation in animationClipList)
74 {
75 try
76 {
77 // 去除scale曲线
78 //foreach (EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(theAnimation))
79 //{
80 // string name = theCurveBinding.propertyName.ToLower();
81 // if (name.Contains("scale"))
82 // {
83 // AnimationUtility.SetEditorCurve(theAnimation, theCurveBinding, null);
84 // }
85 //}
86
87 // 浮点数精度压缩到f3
88 AnimationClipCurveData[] curves = null;
89 curves = AnimationUtility.GetAllCurves(theAnimation);
90 Keyframe key;
91 Keyframe[] keyFrames;
92 for (int ii = 0; ii < curves.Length; ii)
93 {
94 AnimationClipCurveData curveDate = curves[ii];
95 if (curveDate.curve == null || curveDate.curve.keys == null)
96 {
97 //Debuger.LogWarning(string.Format("AnimationClipCurveData {0} don't have curve; Animation name {1} ", curveDate, animationPath));
98 continue;
99 }
100 keyFrames = curveDate.curve.keys;
101 for (int i = 0; i < keyFrames.Length; i )
102 {
103 key = keyFrames[i];
104 key.value = float.Parse(key.value.ToString("f3"));
105 key.inTangent = float.Parse(key.inTangent.ToString("f3"));
106 key.outTangent = float.Parse(key.outTangent.ToString("f3"));
107 keyFrames[i] = key;
108 }
109 curveDate.curve.keys = keyFrames;
110 theAnimation.SetCurve(curveDate.path, curveDate.type, curveDate.propertyName, curveDate.curve);
111 }
112 }
113 catch (System.Exception e)
114 {
115 Debug.LogError(string.Format("CompressAnimationClip Failed !!! animationPath : {0} error: {1}", assetPath, e));
116 }
117 }
118 }
119 #endregion
120
121 }
在Unity2018时,以上的判断方法可以完好地运行,但是升级到Unity2019以后,就不能再通过.meta文件存在与否来判断一个fbx是否是第一次被导入了。因为Unity2019.3以后资源后处理管线也由AssetPipline v1升级到了Asset Pipline v2,同时Unity生成.meta和调用资源后处理接口(比如 OnPreprocessModel()、 OnPostprocessModel(GameObject go)这些回调接口)的时序也发生了变化。在Unity2018中,一个资源被导入的时候,会先去调用资源后处理接口,然后再生成.meta文件,因此可以通过.meta文件存在与否来判断一个fbx是否是第一次被导入。但是在Unity2019中,这个时序变成了先生成一个.meta文件,然后再去调用资源后处理接口。如果此时还通过.meta文件来判断一个资源是否是被第一次导入的话,就会造成程序认为这个新导入的fbx之前是被处理过的,就不会走到资源自动处理的那部分代码了,因此也就会表现为资源后处理机制失效了。同时Unity2019中先于资源后处理回调接口生成的这个.meta文件,也并不是完整的,只是先生成一个文件用来占坑,里面只有两行基本信息,如下图所示;
只有在走完资源后处理接口以后,其内容才会被正式地补充完整并写入,如下图所示:
网上有一种比较流行的判断资源是否是第一次导入的方式,是通过首次导入的时候在 assetImporter.userData 中写入一个标记,然后后续通过判断是否可以读到这个标记来区分资源是否是首次导入,因为如果以前导入过一次了,那么一定会读取到userdata的,其代码类似于这样:assetImporter.userData = "v_0.0.1"; ,这个userdata数据会存在资源对应的.meta文件中。
但是我们的项目之前没有加写入userdata这一步,userdata是读取不到的。一种比较简单粗暴的解决办法就是写一个工具,遍历每个fbx资源,然后把userdata写入到他们的.meta文件中,这样就批量地完成了资源meta的升级,新旧资源就可以通过userdata来进行区分了。
但是这样做比较拙劣的问题是,项目中资源很多,批量处理起来可能会有一定的风险,那么有没有比较优雅且安全系数较高的解决方案呢?其实Unity为我们提供了一个 assetImporter.importSettingsMissing 的接口,他完全可以实现我们的需求。这个接口可以判断一个资源对应的配置是否存在,对于一个新导入的资源,其配置肯定是不存在的,因此要执行资源后处理代码;而对于一个已经导入过了的资源,其配置肯定是存在的,所以直接跳过不处理。经过马三的测验,它可以良好地在Unity2018和2019上工作,通过使用这个接口来判断资源是否是被第一次导入,就可以巧妙的解决上述问题了。同时不用批量的去处理每一个资源对应的meta,不但优雅也降低了风险系数,是一个比较好的兼容方式。
三、总结
在本篇博客中,马三跟大家分享了从Unity2018升级到Unity2019以后,资源导入管线遇到的小问题。一个是Unity2018和Unity2019生成.meta和调用资源后处理回调的时序发生了变化,因此不能再通过判断.meta文件是否存在来标记一个资源是否是被第一次导入;另一个是通过 assetImporter.importSettingsMissing 这个接口去巧妙地兼容判断资源文件是否是被第一次导入,解决了上述问题,希望可以帮到大家。