【Unity遊戲開發】升級Unity2019後,資源管線後處理採坑記錄

一、引子

  最近我們的項目由Unity2018升級到了Unity2019.4,但是突然間發現FBX資源導入時的後處理不生效了。經過一系列的實驗,發現了升級到Unity2019以後,資源管線後處理中的一些坑,今天馬三來和大家分享一下這個過程。

二、情況復現與原因排查

  在我們的項目還使用Unity2018開發的時候,便有一個資源後處理的Editor代碼,負責處理fbx類型文件導入時的一些自動化配置,比如:壓縮動畫曲線、優化模型的網格,關閉模型的Read/Write Enable選項等操作。同時這個自動化處理操作是隻在資源第一次被導入的時候纔會運行的,也就是說只有一個新的FBX剛進來的時候纔會自動配置,在這之後我們還可以手動去調整一些參數。因此有了一步判斷資源有沒有被自動化後處理過的過程,用的是判斷fbx對應的.meta文件存在與否,來指示這個fbx是否是被首次處理,代碼如下所示:

  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 這個接口去巧妙地兼容判斷資源文件是否是被第一次導入,解決了上述問題,希望可以幫到大家。

 

 

 

 

如果覺得本篇博客對您有幫助,可以掃碼小小地鼓勵下馬三,馬三會寫出更多的好文章,支持微信和支付寶喲!

       

 

作者:馬三小夥兒
出處:https://www.cnblogs.com/msxh/p/13805008.html
請尊重別人的勞動成果,讓分享成爲一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章