【Unity】Editor常用

持续更新中。。

Inspector面板

重写代码面板

复合元素 要显示在面板使用[serializable]
比如一个类 就是这么简单,但加了之后就不能为Null了

[Serializable]
public class TestClass
{
    public enum TestType
    {
        a,
        b,
    }
    public TestType testType;
    public bool testBool = false;
    public float testFloat = 1;
}

如果要重绘这个类...使用CustomPropertyDrawer,

[CustomPropertyDrawer(typeof(UScript))]
public class UScriptDrawer : PropertyDrawer
{
    public override void OnGUI(Rect pos, SerializedProperty property, GUIContent label)
    {
        var targetObj = property.serializedObject.targetObject;
        var asset = targetObj as 持有他的对象类型(如Asset);
    }
        
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        //如有需要返回Height
    }
}

用代码重新绘制了Inspector,并赋值,所以注意不能使用Ctrl+Z,要做Undo?...


如加个按钮

【Unity】Editor常用_第1张图片

[CustomEditor(typeof(UrScript))]
public class UrScriptEditor : Editor
{
    UrScript script;

    private void OnEnable()
    {
      script = (UrScript)target;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        serializedObject.Update();
        EditorGUI.BeginChangeCheck();
        if (script.boolValue)
        {
            EditorGUI.indentLevel++;
            EditorGUILayout.LabelField("Label面板提示1");
            EditorGUILayout.HelpBox("HelpBox面板提示2", MessageType.Warning);
            EditorGUILayout.PropertyField(任意变量);
            EditorGUI.indentLevel--;
        }
        else
        {
            EditorGUILayout.LabelField("面板提示");
            数组变量.arraySize = EditorGUILayout.IntField("Length", uscript.arraySize);
            for (int i = 0; i < 数组变量.arraySize; i++)
                EditorGUILayout.PropertyField(数组变量.GetArrayElementAtIndex(i));
        }

        if (GUILayout.Button("创建对象"))
        {
            TestScript myScript = (TestScript)target;
            Debug.LogError(myScript.self.gameObject.GetComponent());
        }
        if (EditorGUI.EndChangeCheck())
                UnityEditorInternal.InternalEditorUtility.RepaintAllViews();

        serializedObject.ApplyModifiedProperties();
    }
}

绘制套娃

Editor newEditor;
public override void OnInspectorGUI()
{
    serializedObject.Update();

    EditorGUI.BeginChangeCheck();
    EditorGUILayout.PropertyField(m_Prop, new GUIContent("套娃对象"));
    if (EditorGUI.EndChangeCheck() || assetEditor == null)
    {
        newEditor= CreateEditor(m_Prop.objectReferenceValue);
    }
    newEditor.OnInspectorGUI();

    serializedObject.ApplyModifiedProperties();
}

选择层

                FindProperty("xx1").FindPropertyRelative("xx2").intValue = UnityEditorInternal.InternalEditorUtility.ConcatenatedLayersMaskToLayerMask(EditorGUILayout.MaskField("", UnityEditorInternal.InternalEditorUtility.LayerMaskToConcatenatedLayersMask(xx2), UnityEditorInternal.InternalEditorUtility.layers));

自定义Attribute

发现了自定义更简洁的方法  参考Odin插件,
新增LabelText标签。可以放到属性上,显示出中文

public class LabelTextAttribute:PropertyAttribute
{
    public string label;
    public string showCondition;
    public string editorCondition;

    public LabelTextAttribute(string label, string showCondition = null, string editorCondition = null)
    {
        this.label = label;
        this.showCondition = showCondition;
        this.editorCondition = editorCondition;
    }
}

自定义Drawer

还可以针对一些非Unity类

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(LabelTextAttribute),false)]
public class LabelDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var attr = attribute as LabelTextAttribute;
        label.text = attr.label;

        // 如果字段为false则不显示
        if (!string.IsNullOrEmpty(attr.showCondition))
        {
            var field = property.serializedObject.FindProperty(attr.showCondition);
            if (!field.boolValue)
                return;
        }
        
        // 是否能编辑
        var notEdit = false;
        if (!string.IsNullOrEmpty(attr.editorCondition))
        {
            if (attr.editorCondition == "false")
                notEdit = true;
        }        
        if (notEdit)
            GUI.enabled = false;
        EditorGUI.PropertyField(position, property, label);
        
        if (notEdit)
            GUI.enabled = true;
    }

    //如果不显示返回没间隔
    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        LabelTextAttribute labelAttr = (LabelTextAttribute)attribute;
        var showCondition = property.serializedObject.FindProperty(labelAttr.showCondition);
        if (showCondition != null)
        {
            bool show = showCondition.boolValue;
            if (!show)
                return -EditorGUIUtility.standardVerticalSpacing;
        }
        return EditorGUI.GetPropertyHeight(property, label);
    }
}

资源面板重写

场景:[CustomEditor(typeof(UnityEditor.SceneAsset))]
txt/byte   :  TextAsset
模型  :  ?
文件夹和一些unity不支持的文件类型:[CustomEditor(typeof(UnityEditor.DefaultAsset))]

    public override void OnInspectorGUI()
    {
        string path = AssetDatabase.GetAssetPath(target);
        if (path.EndsWith(".bytes") && path.Contains("Animations"))
        {
            using (BinaryReader reader = new BinaryReader(File.Open(path, FileMode.Open)))
            {
                int length = reader.ReadInt32();
                EditorGUILayout.LabelField("长度:" + length);
                for (int i = 0; i < length; i++)
                {
                    EditorGUILayout.LabelField("X:" + (reader.ReadInt32() / 10000f).ToString() +
                       "    Y:" + (reader.ReadInt32() / 10000f).ToString() +
                       "     Z:" + (reader.ReadInt32() / 10000f).ToString());
                    if (i != 0 && (i + 1) % 10 == 0)
                        EditorGUILayout.LabelField("----" + (i + 1) + "----");
                }
            }
        }
        else
            base.OnInspectorGUI();
    }

自定义窗口

各种GUI:

 https://www.cnblogs.com/ppne/p/14745215.html

拓展选项编辑

【Unity】Editor常用_第2张图片

//继承IHasCustomMenu
public void AddItemsToMenu(GenericMenu menu)    {
    menu.AddItem(new GUIContent("A"), false, () => { Debug.Log("A"); });
}

使用unity内置样式和Icon

GUIStyle HelpStyle = new GUIStyle("IconButton");
HelpStyle.normal.background = EditorGUIUtility.FindTexture("d__Help");

添加一个小问号

【Unity】Editor常用_第3张图片

 void ShowButton(Rect position)
    {
        if (lockButtonStyle == null)
            lockButtonStyle = new GUIStyle();
        lockButtonStyle.normal.background = EditorGUIUtility.FindTexture("d__Help");
        if (GUI.Button(position, GUIContent.none, lockButtonStyle))
        {

        }
    }

预制

  1. 新预制系统说明
     
    Scene Mode Prefab Mode
    Hierarchy 里操作的对象本质 Prefab Instance 实例 Assets里的UnityEngine.Object文件
    Apply 保存过程 从Instance写入到Asset里的Prefab文件 直接操作Assets里的Prefab文件
    SetDirty 脚本修改Prefab上的对象 EditorUtility.SetDirty(Object) 暂无(目前U3D问答上很多人在问)
    设置场景Dirty EditorSceneManager.GetActiveScene() PrefabStageUtility.GetCurrentPrefabStage().scene)
    Apply时的回调 PrefabUtility.prefabInstanceUpdated PrefabStage.prefabSaving

    此外Project面板也分为one/two column也许也要写不同的代码?

     
  2. 输出选择的资源路径

    Scene/Hierarchy   (prefab mode场景会get source失败,因为操作的是Instance不是prefab所以在)
    Object source = PrefabUtility.GetCorrespondingObjectFromSource (TObject componentOrGameObject);
    string path = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(source);
    Prefab Mode
    string path = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage().prefabAssetPath;

    Project      (本质操作Asset)
    string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);
    
    组件(例如Aniamtor)
    AssetDatabase.GetAssetPath(Object);
  3. 当前选择目标
    Selection.activeObject
     
  4. 修改prefab
    直接修改Asset
    使用AssetDatabase.LoadAssetAtPath后
    直接操作:
        ①删Object使用GameObject.DestroyImmediate(go,true)
        ②改变脚本属性记得SetDirty否则保存可能无效
        static void MakePrefab()
        {
            string selectPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);
            var files = Directory.GetFiles(selectPath, "*.prefab", SearchOption.AllDirectories);
            foreach (var path in files)
            {
                GameObject prefab = AssetDatabase.LoadAssetAtPath(path);
                GameObject delect = prefab.transform.Find("delect");
                if (delect != null)
                {
                    GameObject.DestroyImmediate(delect, true);
                    AssetDatabase.SaveAssets();
                    AssetDatabase.Refresh();
                }
            }
        }

    问题:使用Asset如果删除一些组件要使用DestroyImmediate,当目标是一个ParticleSystem时,Material不会一同被删除,使用实例化解决。

    prefab实例化不断链
    首先实例化要用↓

    GameObject prefab = AssetDatabase.LoadAssetAtPath(path);
    GameObject go = PrefabUtility.InstantiatePrefab(prefab);//如果使用Instantiate会丢失链接 因为算是clone

    判断是否有Missing代码
             go.GetComponentsInChildren().Any(mono => mono == null))     

    保存预制
                    bool succe = PrefabUtility.SaveAsPrefabAsset(inst, prefabPath);


    这个时候想要进行一些删除或者SetParent操作,会提示不允许。
    要断链root,使用PrefabUtility.UnpackPrefabInstanceAndReturnNewOutermostRoots(go, PrefabUnpackMode.OutermostRoot);



  5.  
  6. prefab监听改变
    如果想要添加代码  有人也遇到和我一样的问题 想使用SaveAsPrefabAsset改 但是发现不起效 链接
    根据我蹩脚的英语理解 官方技术人员的意思大概就是这个delegate不对SaveAsPrefabAsset起效 
    要用SaveAsPrefabAssetAndConnect
    【Unity】Editor常用_第4张图片
     
    using UnityEditor;
    using UnityEngine;
    
    public class ApplyPrefab
    {
    	[InitializeOnLoadMethod]
        static void StartInitializeOnLoadMethod()
    	{
    		PrefabUtility.prefabInstanceUpdated = delegate (GameObject instance)
    		{
    			//prefab保存的路径
    			Debug.Log(PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(instance));		
    		};
    	}
    }
    
  7. Prefab保存多个
    	[UnityEditor.MenuItem("Tools/Apply Selected Prefabs")]
    	static void ApplySelectedPrefabs(){
    		//获取选中的gameobject对象
    		GameObject [] selectedsGameobject= Selection.gameObjects;		
    		GameObject prefab = PrefabUtility.FindPrefabRoot (selectedsGameobject[0]);		
    		for (int i = 0; i < selectedsGameobject.Length; i++) {
    			GameObject obj=selectedsGameobject[i];			
    			UnityEngine.Object newsPref= PrefabUtility.GetPrefabObject(obj);			
    			//判断选择的物体,是否为预设
    			if(PrefabUtility.GetPrefabType(obj)  == PrefabType.PrefabInstance){				
    				UnityEngine.Object parentObject = PrefabUtility.GetPrefabParent(obj); 				
    				//string path = AssetDatabase.GetAssetPath(parentObject);//获取路径
    PrefabUtility.ReplacePrefab(obj,parentObject,ReplacePrefabOptions.ConnectToPrefab);//替换预设					
    				AssetDatabase.Refresh();//刷新
    			}	
    		}		
    	}

     
  8. 遍历所有文件包括子目录
    static void Scan()
    {
        ScanFolder(EditorUtil.GetSelectedFilePath());
    }
    static void ScanFolder(path)
    {
        DirectoryInfo folder = new DirectoryInfo(path);
        FileSystemInfo[] files = folder.GetFileSystemInfos();
        for(int i = 0;i(path);        
        }
    }
  9. 响应保存
     
    [InitializeOnLoad]
    internal class PrefabExtension
    {
        static PrefabExtension()
        {
            PrefabUtility.prefabInstanceUpdated += OnPrefabInstanceUpdate;
        }
    
        static void OnPrefabInstanceUpdate(GameObject instance)
        {
            //Debug.LogError("[Callback] Prefab.Apply on instance named :" + instance.name);
            EditorApplication.delayCall += delegate ()
            {
                var prefab = PrefabUtility.GetCorrespondingObjectFromSource(instance);
                if (prefab == null)
                    return;
                var tPath = AssetDatabase.GetAssetPath(prefab);
                //Do Something
            };
        }
    }
    
    public class Example : AssetPostprocessor
    {
        void OnPostprocessPrefab(GameObject g)
        {
            Debug.LogError(g.name);
        }
        [InitializeOnLoadMethod]
        static void StartInitializeOnLoadMethod()
        {
            PrefabUtility.prefabInstanceUpdated = delegate (GameObject instance)
            {
                //prefab保存的路径
                    var sourrce = PrefabUtility.GetCorrespondingObjectFromSource(instance);
                    var path = AssetDatabase.GetAssetPath(sourrce);
                    if (PrefabUtility.IsAnyPrefabInstanceRoot(instance))
                        PrefabUtility.UnpackPrefabInstance(instance, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
    //do something
            };
        }
    }
    [InitializeOnLoad]
    public class PrefabSaveListener : UnityEditor.AssetModificationProcessor
    {
        private static string[] OnWillSaveAssets(string[] paths)
        {
            // 在这里可以添加一些逻辑代码,比如检查预制的属性或组件等
    
            foreach (string path in paths)
            {
                // Example: 更新预制体的某个属性
                var asset = AssetDatabase.LoadAssetAtPath(path);
                if (asset != null)
                {
                    // 修改预制体的属性
                }
            }
            return paths;
        }
    }

     


  10.  

OnGUI

EditorGUI

需要Rect、带输入、有默认间隔

GUILayout

不需要Rect、自动排布、不带输入、所以没有很大间隔、自定义间隔
一般结合BeginArea、BeginScrollView、BeginHorizontal、BeginVertical使用

EditorGUILayout

带输入,EditorGUILayout会间隔比较大

【Unity】Editor常用_第5张图片

GUIStyle和Icon 查看器

https://github.com/756915370/UnityEditorExtension

包围框


            EditorGUILayout.BeginVertical(EditorStyles.helpBox);
            EditorGUILayout.EndVertical();

 菜单栏

  1. 菜单添加与快捷键 
    快捷键api参考:Unity - Scripting API: MenuItem

    public class testTools
    {
        [MenuItem("Tools/Hello %#a")] 
        static void Hello()
        {
            Debug.Log("hello");
        }
    
        [MenuItem("Assets/Hello")] //如果放在Assets下右键文件夹也会出现
        static void Hello2()
        {
            Debug.Log("hello2");
        }
    }
    
    快捷键
    % - CTRL
    # - Shift
    & - Alt
    LEFT/RIGHT/UP/DOWN - 光标键
    F1…F12
    HOME,END,PGUP,PDDN
    Example
  2. 清空控制台 
     
     public static void ClearConsole()
        {
            Assembly assembly = Assembly.GetAssembly(typeof(SceneView));
            Type logEntries = assembly.GetType("UnityEditor.LogEntries");
            MethodInfo clearConsoleMethod = logEntries.GetMethod("Clear");
            clearConsoleMethod.Invoke(new object(), null);
        }
  3.    复制粘贴

    //复制
    GUIUtility.systemCopyBuffer = "test";
    //粘贴 或者 Ctrl+V
    string str = GUIUtility.systemCopyBuffer;





  4.  

场景

  1. 监听场景变换
     
    public class SaveScene :UnityEditor.AssetModificationProcessor   { 
    	static public void OnWillSaveAssets(string[]names)
    	{ 
    		foreach (string name in names) 
    		{
    			if (name.EndsWith (".unity")) {
    				Scene scene = SceneManager.GetSceneByPath (name);
    				Debug.Log ("保存场景 :" + scene.name);
    			} 
    		}
    	} 
    }
  2. 刷新Scene视图
     
     SceneView.lastActiveSceneView.Repaint();
  3. editor 每秒/改变时绘制
    在Scene预览模型 例子:

    GameObject preview;
    void OnDrawGizmos()
    {
        if(!Application.isPlaying)
        {
            if(preview == null)
                Resources.Load("path");
            else
            {
                MeshFilter[] meshFilters = mPreview.GetComponentsInChildren();
                for(int i = 0; i < meshFilters.Length; i++)
                {
                    Gizmos.DrawMesh(meshFilter[i].sharedMesh,
                    transform.position + mPreview.transform.GetChild(i).position * scale,
                    mPreview.transform.GetChild(i).rotation,
                    transform.localScale)
                }
            }
        }
    }

其他

ScriptObject

using UnityEngine;

[CreateAssetMenu(fileName = "FileName", menuName = "ScriptableObjects/menuName", order = 1)]
public class YourScriptObject : ScriptableObject
{
    [Header("大小")]
    public int Size = 16;
}

反射调用


    public class ReflectionTools
    {
        public static void Call(string typeName, string methodName, params object[] args)
        {
            Call(typeName, methodName, args);
        }

        public static T Call(string typeName, string methodName, params object[] args)
        {
            System.Type type = System.Type.GetType(typeName);
            T defaultValue = default(T);
            if (null == type) return defaultValue;

            System.Type[] argTypes = new System.Type[args.Length];
            for (int i = 0, count = args.Length; i < count; ++i)
            {
                argTypes[i] = null != args[i] ? args[i].GetType() : null;
            }
            MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, argTypes, null);
            if (null == method)
            {
                Debug.LogError(string.Format("method {0} does not exist!", methodName));
                return defaultValue;
            }
            object result = method.Invoke(null, args);
            if (null == result)
                return defaultValue;
            if (!(result is T))
            {
                Debug.LogError(string.Format("method {0} cast failed!", methodName));
                return defaultValue;
            }
            return (T)result;
        }
    } 
  

还可以把asmdef命名为内部internal 例如Unity.InternalAPIEditorBridge.001
这样可以直接访问
InternalsVisibleToAttribute Class (System.Runtime.CompilerServices) | Microsoft Learn

你可能感兴趣的:(Unity,unity,c#)