rustum 发表于 2021-11-22 07:43

Unity编辑器自定义资源导入

涉及类/属性

ScriptedImporter
自定义资源导入器的抽象基类。脚本化导入器是与特定文件扩展名关联的脚本。Unity 的资源管线调用它们来将关联文件的内容转换为资源。ScriptedImporterAttribute
用于向 Unity 的资源导入管线注册派生自 ScriptedImporter 的自定义资源导入器的类属性。使用 ScriptedImporterAttribute 类可向资源管线注册自定义导入器。AssetDatabase.SetImporterOverride<T>(string path) where T : ScriptedImporter
将特定导入器设置为用于资源。AssetPostprocessor
在资源导入或重新导入以后由unity调用。主要使用 OnPreprocessAsset() 或者OnPostprocessAllAssets
<hr/>分两种自定义的方式

自定义文件的后缀名没有被引擎实现(比如 .cube .md .abcdefg)using UnityEngine;
using UnityEditor.AssetImporters;
using System.IO;


public class CubeImporter : ScriptedImporter
{
    public float m_Scale = 1;

    public override void OnImportAsset(AssetImportContext ctx)
    {
      var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
      var position = JsonUtility.FromJson<Vector3>(File.ReadAllText(ctx.assetPath));

      cube.transform.position = position;
      cube.transform.localScale = new Vector3(m_Scale, m_Scale, m_Scale);

      // 'cube' is a a GameObject and will be automatically converted into a Prefab
      // (Only the 'Main Asset' is elligible to become a Prefab.)
      ctx.AddObjectToAsset("main obj", cube);
      ctx.SetMainObject(cube);

      var material = new Material(Shader.Find("Standard"));
      material.color = Color.red;

      // Assets must be assigned a unique identifier string consistent across imports
      ctx.AddObjectToAsset("my Material", material);

      // Assets that are not passed into the context as import outputs must be destroyed
      var tempMesh = new Mesh();
      DestroyImmediate(tempMesh);
    }
}
关键参数是 string[] exts ,只要是 .cube 后缀的文件导入编辑器,就会自动调用OnImportAsset 函数,在里面就可以对自定义的文件进行控制。
配合 ScriptedImporterEditor 类可在 Inspector 面板绘制,也可以直接继承Editor类,只是 ScriptedImporterEditor 类为你提供了 保存导入文件数据 的功能。
using System.IO;
using UnityEditor;
using UnityEditor.AssetImporters;
using UnityEngine;



public class TransformImporterEditor : ScriptedImporterEditor
{
    // Stored SerializedProperty to draw in OnInspectorGUI.
    SerializedProperty m_GenerateChild;

    public override void OnEnable()
    {
      base.OnEnable();
      // Once in OnEnable, retrieve the serializedObject property and store it.
      m_GenerateChild = serializedObject.FindProperty("generateChild");
    }

    public override void OnInspectorGUI()
    {
      // Update the serializedObject in case it has been changed outside the Inspector.
      serializedObject.Update();

      // Draw the boolean property.
      EditorGUILayout.PropertyField(m_GenerateChild);

      // Apply the changes so Undo/Redo is working
      serializedObject.ApplyModifiedProperties();

      // Call ApplyRevertGUI to show Apply and Revert buttons.
      ApplyRevertGUI();
    }
}


public class TransformImporter : ScriptedImporter
{
    public bool generateChild;

    public override void OnImportAsset(AssetImportContext ctx)
    {
      GameObject root = ObjectFactory.CreateGameObject(Path.GetFileNameWithoutExtension(ctx.assetPath));
      if (generateChild)
      {
            GameObject child = ObjectFactory.CreateGameObject("child");
            child.transform.SetParent(root.transform);
      }
      ctx.AddObjectToAsset("main", root);
      ctx.SetMainObject(root);
    }
}
自定义的文件后缀名被引擎使用(霸占)了(比如: .asset .cs .unity)                         unity自然提供了让切换默认的导入管线的方法,但是自己实现的手段有多种,可按需去设计。unity没法知道你是要使用内置导入器还是自定义的导入器,所以需要你自己去调用函数去设置对应文件路径的对象使用自定义导入器。
需要注意的是,ScriptedImporter 类必须要实现 ScriptedImporterAttribute 属性,且参数
string[] overrideExts 为你切换导入器 的文件的后缀。(加不加 . (点)无所谓,unity会自动剔除)例如:
{ }, new string[] { "json" })]
public class MyScriptedImporter: ScriptedImporter注意:string[] exts 参数别写 unity实现了的后缀名,写了编译报错
可不填,也可以填未被实现的后缀,但是自定义的后缀名文件导入也会使用这个类作为导入器。
但是,光这样加属性并不会让文件导入引擎后就安装自定义流水线去导入,没有提供默认的切换操作。
需要配合下面的核心函数
AssetDatabase.SetImporterOverride<T>(string path)whereT:ScriptedImporter将特定导入器设置为用于资源。注意:ScriptedImporter 类必须单独写一个cs脚本,否则unity的导入器切换下拉菜单就不显示


你以为这样就结束了吗?
此时不得不吐槽下Unity,为什么不能加了 ScriptedImporterAttribute 属性就能显示这个Importer的下拉选项,非得脚本设置一次。当你用下拉菜单去还原了导入器以后,这个下拉菜单就没有了,所以需要下面的步骤。
可以这样实现 AssetPostprocessor 类的 OnPreprocessAsset 函数,在函数内设置文件导入器的覆盖
private void OnPreprocessAsset()
{
    string path = assetPath.ToLower();
    if (path.EndsWith("/package.json"))
    {
      if ((assetImporter is PackageImporter) == false)
      {
            AssetDatabase.SetImporterOverride<PackageImporter>(assetPath);
      }
    }
}
也可以这样

private void SetImporterOverride()
{
    var obj = Selection.activeObject;
    if (obj != null)
    {
      var path = AssetDatabase.GetAssetPath(obj);
      if (path.EndsWith("/package.json"))
      {
            AssetDatabase.SetImporterOverride<PackageImporter>(assetPath);
      }
    }
}还可以这样
重写全部文件的序列号,添加一个切换按钮,我懒所以不实现了。


<hr/>关于继承 ScriptedImporterEditor 类为文件写自定义导入的坑 后续在填。
参考资料:
PSD 插件的导入 PSD File Importer Override

下面是我自己为Packge.json文件写的导入。起因是因为我修改版本号还需要打开文件,就很痛苦。
Importer

public class Package
{
    public string name;
    public string version;
    public string displayName;
    public string description;
    public string unity;
}
using System.Diagnostics.CodeAnalysis;
using UnityEditor;

internal sealed class PackageAssetPostprocessor : AssetPostprocessor
{
   
    private void OnPreprocessAsset()
    {
      string path = assetPath.ToLower();
      if (path.EndsWith("/package.json") && path.StartsWith("assets/"))
      {
            if ((assetImporter is PackageImporter) == false)
            {
                AssetDatabase.SetImporterOverride<PackageImporter>(assetPath);
            }
      }
    }
   
    private void SetImporterOverride()
    {
      var obj = Selection.activeObject;
      if (obj != null)
      {
            var path = AssetDatabase.GetAssetPath(obj);
            if (path.EndsWith("/package.json"))
            {
                AssetDatabase.SetImporterOverride<PackageImporter>(assetPath);
            }
      }
    }
}
using System.IO;
using UnityEditor;
using UnityEditor.AssetImporters;
using UnityEngine;

{ }, new string[] { "json" })]

public class PackageImporter : ScriptedImporter
{
    public override void OnImportAsset(AssetImportContext ctx)
    {
      string json = File.ReadAllText(ctx.assetPath);
      var package = JsonUtility.FromJson<Package>(json);

      var packageSO = ObjectFactory.CreateInstance<PackageSO>();
      packageSO.package = package;

      ctx.AddObjectToAsset("main", packageSO);
      ctx.SetMainObject(packageSO);
    }
}
using System.IO;
using UnityEditor;
using UnityEditor.AssetImporters;
using UnityEngine;


public class PackageImporterEditor : ScriptedImporterEditor, IHasCustomMenu
{
    public override bool showImportedObject => true;
    protected override bool needsApplyRevert => base.needsApplyRevert;
    protected override System.Type extraDataType => typeof(PackageSO);

    private PackageSO packageSO;

    private SerializedProperty nameSP;
    private SerializedProperty version;
    private SerializedProperty displayName;
    private SerializedProperty description;
    private SerializedProperty unity;

    private Styles styles;


    public void AddItemsToMenu(GenericMenu menu)
    {
      menu.AddItem(new GUIContent("Edit Script"), false, () =>
      {
            SerializedObject serializedObject = new SerializedObject(this);
            serializedObject.Update();
            SerializedProperty m_Script = serializedObject.FindProperty("m_Script");
            var obj = m_Script.objectReferenceValue;
            AssetDatabase.OpenAsset(obj.GetInstanceID());
      });
    }

    protected override void InitializeExtraDataInstance(Object extraData, int targetIndex)
    {
      var assetPath = ((AssetImporter)targets).assetPath;
      var json = File.ReadAllText(assetPath);
      var package = JsonUtility.FromJson<Package>(json);

      packageSO = (PackageSO)extraData;
      packageSO.package = package;
    }

    public override void OnEnable()
    {
      base.OnEnable();

      if (styles == null)
            styles = new Styles();

      nameSP = extraDataSerializedObject.FindProperty("package.name");
      version = extraDataSerializedObject.FindProperty("package.version");
      displayName = extraDataSerializedObject.FindProperty("package.displayName");
      description = extraDataSerializedObject.FindProperty("package.description");
      unity = extraDataSerializedObject.FindProperty("package.unity");
    }

    public override void OnInspectorGUI()
    {
      bool change;
      using (new LocalizationGroup(/*target*/this))
      {
            change = DoDrawDefaultInspector(serializedObject);

            static bool DoDrawDefaultInspector(SerializedObject obj)
            {
                EditorGUI.BeginChangeCheck();
                obj.UpdateIfRequiredOrScript();
                SerializedProperty iterator = obj.GetIterator();
                bool enterChildren = true;
                while (iterator.NextVisible(enterChildren))
                {
                  using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath))
                  {
                        EditorGUILayout.PropertyField(iterator, true);
                  }
                  enterChildren = false;
                }

                obj.ApplyModifiedProperties();
                return EditorGUI.EndChangeCheck();
            }
      }

      GUILayout.Label(styles.information, EditorStyles.boldLabel);

      extraDataSerializedObject.Update();

      // Package information
      using (new EditorGUILayout.VerticalScope(GUI.skin.box, GUILayout.ExpandWidth(true)))
      {
            EditorGUILayout.DelayedTextField(nameSP, styles.name);

            EditorGUILayout.DelayedTextField(version, styles.version);

            EditorGUILayout.DelayedTextField(displayName, styles.displayName);
            EditorGUILayout.DelayedTextField(description, styles.description);
            EditorGUILayout.DelayedTextField(unity, styles.unity);
      }


      if (GUILayout.Button("Add Small Version", GUILayout.ExpandWidth(false)))
            packageSO.AddSmallVersion();

      extraDataSerializedObject.ApplyModifiedProperties();

      ApplyRevertGUI();
    }

    protected override void Apply()
    {
      base.Apply();
      // After the Importer is applied, rewrite the file with the custom value.
      for (int i = 0; i < targets.Length; i++)
      {
            var package = JsonUtility.ToJson(((PackageSO)extraDataTargets).package, true);
            string path = ((AssetImporter)targets).assetPath;
            File.WriteAllText(path, package);
      }
    }



    private class Styles
    {
      public readonly GUIContent information = EditorGUIUtility.TrTextContent("Information");
      public readonly GUIContent name = EditorGUIUtility.TrTextContent("Name", "Package name. Must be lowercase");
      public readonly GUIContent displayName = EditorGUIUtility.TrTextContent("Display name", "Display name used in UI.");
      public readonly GUIContent version = EditorGUIUtility.TrTextContent("Version", "Package Version, much follow SemVer (ex: 1.0.0-preview.1).");
      public readonly GUIContent unity = EditorGUIUtility.TrTextContent("Minimal Unity Version");
      public readonly GUIContent description = EditorGUIUtility.TrTextContent("Brief Description");
    }
}
页: [1]
查看完整版本: Unity编辑器自定义资源导入