拓展Project视图

拓展右键菜单

要将拓展Unity编辑器的脚本放在Editor文件夹下,这个文件夹可以有多个,放在不同目录的子文件夹中,这样开发者就可以按照功能来划分

在editor文件夹中创建ExtendingRightMenu脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using UnityEditor;//引入命名空间

public class ExtendingRightMenu //不用继承Mono
{
[MenuItem("Assets/My Tools/Tools 1",false,2)]//按照标题栏的菜单顺序拓展

static void MyTools1()
{
Debug.Log(Selection.activeObject.name);
}

[MenuItem("Assets/My Tools/Tools 2", false, 1)]//特性中最后一个数字表示排列优先级,数字小的在前面

static void MyTools2()
{
Debug.Log(Selection.activeObject.name);
}
}

拓展创建菜单

在editor文件夹中创建ExtendingCreatMenu脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using UnityEditor;

public class ExtendingCreatMenu
{
[MenuItem("Assets/Create/MyCreate/Sphere", false, 2)]

static void MyCreateSphere()
{
GameObject.CreatePrimitive(PrimitiveType.Sphere);//创建预制体-圆球
}

[MenuItem("Assets/Create/MyCreate/Cube", false, 1)]

static void MyCreateCube()
{
GameObject.CreatePrimitive(PrimitiveType.Cube);//创建预制体-方块
}
}

拓展选中按钮布局

用鼠标在Project窗口中选中一个资源后,右侧会出现一个click按钮,点击后在console窗口中打印选中的资源名

在editor文件夹中创建ExtendingLayout脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;
using UnityEditor;

public class ExtendingLayout
{
[InitializeOnLoadMethod]//表示此方法会在每次C#编译完后首先调用,即在编辑器中刷新
static void InitializeOnLoadMethod()
{
EditorApplication.projectWindowItemOnGUI = delegate (string guid, Rect selectionRect)//使用匿名方法,监听projectWindowItemOnGUI
{
if (Selection.activeObject && guid == AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(Selection.activeObject)))
{
float width = 50f;
selectionRect.x += selectionRect.width - width;
selectionRect.y += 2f;
selectionRect.width = width;
GUI.color = Color.red;

if (GUI.Button(selectionRect, "click"))//使用GUI绘制按钮
{
Debug.LogFormat("click:{0}", Selection.activeObject.name);
}

GUI.color = Color.white;
}
};
}
}

监听资源事件

监听Project内资源的创建、删除、移动和保存。重写UnityEditor.AssetModificationProcessor中的方法

在editor文件夹中创建AssetEventListeners脚本,并且继承UnityEditor.AssetModificationProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

public class AssetEventListeners : UnityEditor.AssetModificationProcessor
{
[InitializeOnLoadMethod]

static void InitializeOnLoadMethod()
{
//全局监听Project视图下的资源是否发生变化(添加、删除和移动)
EditorApplication.projectWindowChanged = delegate ()
{
Debug.Log("Change");
};
}

//监听“双击鼠标左键,打开资源”事件
public static bool IsOpenForEdit(string assetPath, out string message)
{
message = null;
Debug.LogFormat("assetPath:{0}", assetPath);

return true;
}

//监听“资源即将被创建”事件
public static void OnWillCreateAsset(string path)
{
Debug.LogFormat("path:{0}", path);
}

//监听“资源即将被保存”事件
public static string[] OnWillSaveAssets(string[] paths)
{
if (paths != null)
{
Debug.LogFormat("savePaths{0}", string.Join(",", paths));//将整个数组用逗号串联起来
}

return paths;
}

//监听“资源即将被移动”事件
public static AssetMoveResult OnWillMoveAsset(string oldPath, string newPath)
{
Debug.LogFormat("from:{0} to {1}", oldPath, newPath);

//AssetMoveResult.DidNotMove 表示该资源可以移动
return AssetMoveResult.DidMove;
}

//监听“资源即将被删除”事件
public static AssetDeleteResult OnWollDeleteAsset(string assetPath, RemoveAssetOptions option)
{
Debug.LogFormat("delete:{0}", assetPath);

//AssetDeleteResult.DidNotMove 表示该资源可以删除
return AssetDeleteResult.DidDelete;
}
}

拓展Hierarchy视图

这个视图对应的是菜单栏GameObject菜单。

拓展GameObject菜单

在editor文件夹中创建ExtendingHierarchyMenu脚本

然后自定义的菜单就会出现在GameObject菜单里

1
2
3
4
5
6
7
8
9
10
11
12
using UnityEngine;
using UnityEditor;

public class ExtendingHierarchyMenu
{
[MenuItem("GameObject/My Create/Cube",false,0)]

static void MyCreateCube()
{
GameObject.CreatePrimitive(PrimitiveType.Cube);
}
}

拓展选中按钮布局

在editor文件夹中创建ExtendingHierarchyLayout脚本

点击Hierarchy窗口内的对象,右侧会出现一个图标(这个图标自己导入),点击后输出选中对象的名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using UnityEngine;
using UnityEditor;

public class ExtendingHierarchyLayout
{
[InitializeOnLoadMethod]
static void InitializeOnLoadMethod()
{
EditorApplication.hierarchyWindowItemOnGUI = delegate (int instanceID, Rect selectionRect)
{
//在HIerarchy窗口中选择一个资源
if (Selection.activeObject && instanceID == Selection.activeObject.GetInstanceID())
{
//设置拓展按钮的坐标和大小
float width = 50f;
float height = 20f;
selectionRect.x += (selectionRect.width - width);
selectionRect.width = width;
selectionRect.height = height;

//GUI事件
if (GUI.Button(selectionRect, AssetDatabase.LoadAssetAtPath<Texture>("Assets/Unity.png")))
{
Debug.LogFormat("click:{0}", Selection.activeObject.name);
}
}
};
}
}

重写GameObject菜单

通过监听点击事件,打开一个新的菜单窗口,覆盖原有菜单

在editor文件夹中创建OverrideHierarchyMenu脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
using UnityEngine;
using UnityEditor;

public class OverrideHierarchyMenu
{
[MenuItem("Window/Test/ATAO1")]//拓展在Window菜单里

static void Test1() { }
[MenuItem("Window/Test/ATAO2")]

static void Test2() { }
[MenuItem("Window/Test/ATAO3")]

static void Test3() { }

[InitializeOnLoadMethod]

static void InitializeOnLoadMethod()
{
EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowGUI;
}

static void HierarchyWindowGUI(int instanceID, Rect selectionRect)
{
if (Event.current != null && selectionRect.Contains(Event.current.mousePosition) && Event.current.button == 1 && Event.current.type <= EventType.MouseUp)//使用Event.current来获取编辑器界面的操作事件
{
GameObject selectedGameObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;


if (selectedGameObject)
{
Vector2 mousePosition = Event.current.mousePosition;

EditorUtility.DisplayPopupMenu(new Rect(mousePosition.x, mousePosition.y, 0, 0), "Window/Test", null);//弹出自定义菜单的方法

Event.current.Use();//使用此方法表示不再执行原有事件,原来的右键菜单不会出现了
}
}
}
}

我们还可以重写原有的菜单

在editor文件夹中创建OverrideCreateImageMenu脚本,这样新建的ImageUI组件就不会自动勾选“RaycastTarget”(需要在选中Canvas下新建Image)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;

public class OverrideCreateImageMenu
{
[MenuItem("GameObject/UI/Image")]

static void CreateImage()
{
if (Selection.activeTransform)
{
if (Selection.activeTransform.GetComponentInParent<Canvas>())
{
Image image = new GameObject("image").AddComponent<Image>();

image.raycastTarget = false;

image.transform.SetParent(Selection.activeTransform, false);

Selection.activeTransform = image.transform;//设置新建的Image组件为选中状态
}
}
}
}

拓展Inspector视图

拓展原生组件

拓展原生Camera组件

在editor文件夹中创建ExtendingCameraScripts脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UnityEngine;
using UnityEditor;


[CustomEditor(typeof(Camera))]//在特性中指定需要自定义的Inspector组件
public class ExtendingCameraScripts : Editor //继承Editor
{
public override void OnInspectorGUI()//使用OnInspectorGUI方法重新绘制组件
{
if (GUILayout.Button("拓展组件")) { }//在上方添加“拓展组件”按钮

base.OnInspectorGUI();//使用base获得父级原有功能
}
}

拓展继承组件(使用反射)

继承组件是指Unity内部父级会对子级有影响的组件,比如说Transform,我们无法得到他们的绘制方法,先使用反射得到UnityEditor.TransformInspector,然后调用其内部的OnInspectorGUI(),会让我们保留系统原有的transform的属性布局,更加美观

在editor文件夹中创建OverrideTransformScripts脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using UnityEngine;
using UnityEditor;
using System.Reflection;//使用反射


[CustomEditor(typeof(Transform))]
public class OverrideTransformScripts : Editor
{


private Editor m_Editor;

private void OnEnable()
{
m_Editor = Editor.CreateEditor(target, Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.TransformInspector", true));
}

public override void OnInspectorGUI()
{
if (GUILayout.Button("拓展按钮")) { }
//调用系统绘制方法
m_Editor.OnInspectorGUI();
}
}

组件不可编辑

使用GUI.enabled方法来让组件中部分区域不可编辑

还是使用OverrideTransformScripts脚本来示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;
using UnityEditor;
using System.Reflection;


[CustomEditor(typeof(Transform))]
public class OverriveTransformScripts : Editor
{


private Editor m_Editor;

private void OnEnable()
{
m_Editor = Editor.CreateEditor(target, Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.TransformInspector", true));
}

public override void OnInspectorGUI()
{
if (GUILayout.Button("拓展按钮上")) { }
GUI.enabled = false;//开始不可编辑
m_Editor.OnInspectorGUI();
GUI.enabled = true;//结束不可编辑
if (GUILayout.Button("拓展按钮下")) { }
}
}

通过设置hideFlags来让整个对象的内部组件全部不可编辑

我们在hierarchy窗口选择任意游戏对象,然后右键——3D Object——Lock来选择是否全部锁定该对象的Inspector内的组件

在editor文件夹中创建LockInspectorScripts脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using UnityEngine;
using UnityEditor;

public class LockInspectorScripts
{
[MenuItem("GameObject/3D Object/Lock/Lock")]

static void LockInspector()
{
if(Selection.gameObjects != null)
{
foreach (var gameObject in Selection.gameObjects)
{
gameObject.hideFlags = HideFlags.NotEditable;
}
}
}

[MenuItem("GameObject/3D Object/Lock/Unlock")]

static void UnlockInspector()
{
if (Selection.gameObjects != null)
{
foreach (var gameObject in Selection.gameObjects)
{
gameObject.hideFlags = HideFlags.None;
//gameObject.hideFlags = HideFlags.DontSave | HideFlags.DontUnloadUnusedAsset;可以使用“|”(按位或)设置多个属性
}
}
}
}

HideFlags可用属性列表

  • HideFlags.None: 清除状态
  • HideFlags.DontSave: 设置对象不会被保存(仅编辑模式下使用,运行时剔除掉)
  • HideFlags.DontSaveInBuild: 设置对象Build后不会被保存
  • HideFlags.DontSaveInEditor: 设置对象编辑模式下不会被保存
  • HideFlags.DontUnloadUnusedAsset: 设置对象不会被Resources.UnloadUnusedAssets()卸载无用资源时卸载掉。
  • HideFlags.HideAndDontSave: 设置对象隐藏,并且不会被保存
  • HideFlags.HideInHierarchy: 设置对象在HIerarchy视图中隐藏
  • HideFlags.HideInInspector: 设置对象在Inspector视图中隐藏
  • HideFlags.NotEditable: 设置对象不可被编辑

组件Context菜单

组件Context菜单是指组件右上角或者右键点击后弹出的菜单

在editor文件夹中创建ExtendingContextMenu脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEngine;
using UnityEditor;

public class ExtendingContextMenu
{
//MenuItem可以拓展主菜单和Context菜单
//想要拓展到别的组件上,将Transform改为对应的组件名即可
//想要所有的组件都包含自定义菜单,将Transform改为Component
[MenuItem("CONTEXT/Transform/New Context 1")]
public static void NewContext1(MenuCommand command)
{
//获取对象名
Debug.Log(command.context.name);
}
[MenuItem("CONTEXT/Transform/New Context 2")]
public static void NewContext2(MenuCommand command)
{
Debug.Log(command.context.name);
}
}

在Script文件夹中创建ExtendingOwnScriptContext脚本,我们为自己的脚本component自定义Context,注意这是组件,这个脚本要放在Script文件夹里并继承MonoBehaviour

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

public class ExtendingOwnScriptContext : MonoBehaviour
{
public string contextName;
#if UNITY_EDITOR
[MenuItem("CONTEXT/ExtendingOwnScriptContext/New Context1")]
public static void NewContext2(MenuCommand command)
{
ExtendingOwnScriptContext scriptContext = command.context as ExtendingOwnScriptContext;
scriptContext.contextName = "捏麻麻地";
}
#endif
}

除了MenuItem,创建自定义Context还有一个更简易的特性写法ContextMenu这里演示一个覆盖系统的Remove Component的脚本

1
2
3
4
5
6
7
8
[ContextMenu("Remove Component")]

void RemoveComponent()
{
//等一帧再删除自己
//编辑器模式下代码同步可能会有问题,使用以下方法来让DestroyImmediate(this)延迟调用来解决此问题
UnityEditor.EditorApplication.delayCall = delegate () { DestroyImmediate(this); };
}

拓展Scene视图

添加辅助元素

使用OnDrawGizmosSelected方法来添加辅助元素,各种添加辅助元素的方式都在Gizmos工具类里面。

绘制辅助元素需要挂载脚本,所以需要继承MonoBehaviour

在Script文件夹中创建GizmosForSelected脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using UnityEngine;

public class GizmosForSelected : MonoBehaviour
{
//选中对象后,画线,画立方体
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawLine(gameObject.transform.position, Vector3.one);
Gizmos.DrawCube(Vector3.one, Vector3.one);
}
//脚本挂载后一直绘制,不需要选中对象
private void OnDrawGizmos()
{
Gizmos.DrawSphere(transform.position, 1f);
}
}

辅助UI

可以在Scene视图中添加EditorGUI,就像拓展Inspector视图原生组件一样,重写OnSceneGUI方法来绘制

使用下面的脚本,在Scene视图中选中摄像机,会显示摄像机的坐标,并且左上角会出现一个按钮和一个Label,点击后显示摄像机名称

在Editor文件夹中创建ExtendingSceneWindow脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Camera))]
public class ExtendingSceneWindow : Editor
{
private void OnSceneGUI()
{
Camera camera = target as Camera;
if (camera != null)
{
Handles.color = Color.red;
Handles.Label(camera.transform.position, camera.transform.position.ToString());

Handles.BeginGUI();//在一个3D handle GUI中开始一块2D GUI区域,这是必要步骤
GUI.backgroundColor = Color.red;
if (GUILayout.Button("Click", GUILayout.Width(200f)))
{
Debug.LogFormat("Click{0}", camera.name);
}

GUILayout.Label("Label");
Handles.EndGUI();//在一个3D handle GUI中结束一块2D GUI区域
}
}
}

常驻辅助UI

订阅SceneView.duringSceneGui事件,就可以实现常驻了

在Editor文件夹中创建RemainingSceneWindow脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using UnityEngine;
using UnityEditor;

public class RemainingSceneWindow
{
[InitializeOnLoadMethod]

static void InitializeOnLoadMethod()
{
SceneView.duringSceneGui += delegate (SceneView sceneView)
{
Handles.BeginGUI();

GUI.Label(new Rect(0, 0, 50f, 15f), "标题");
GUI.Button(new Rect(0, 20f, 50f, 50f), AssetDatabase.LoadAssetAtPath<Texture>("Assets/Unity.png"));

Handles.EndGUI();
};
}
}

禁用选中Scene内的对象

代码只做演示,新版本的Unity中,这个功能只需要点击HIerarchy窗口中对应物体左侧的小指头即可,这样的话Scene窗口中的物体就不会被选中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;
using UnityEditor;

public class UnselectableObjectOnScene
{
[InitializeOnLoadMethod]

static void InitializeOnLoadMethod()
{
SceneView.duringSceneGui += delegate (SceneView sceneView)
{
Event e = Event.current;
if(e != null)
{
int controlID = GUIUtility.GetControlID(FocusType.passive);
if(e.type == EventType.Layout)
{
HandleUtility.AddDefaultControl(controlID);
}
}
};
}
}

父节点锁定

为了防止在Scene视图中选择到子节点对象,对父节点使用SelectionBase特性脚本

代码演示如下

1
2
[SelectionBase]
public class RootScript : MonoBehaviour{}

拓展Game视图

在Game视图中拓展,最经典的就是使用OnGUI方法了,但是直接使用此方法需要挂载脚本,并且要运行才可以

使用ExecuteInEditMode特性就可以在编辑模式下运行,不过仍然需要挂载脚本

一般情况下此特性需要配合Unity_Editor预编译指令,发布后剥离掉只有在编辑器中应用的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using UnityEngine;

#if UNITY_EDITOR
[ExecuteInEditMode]
public class ExtendingGameWindow : MonoBehaviour
{
private void OnGUI()
{
if (GUILayout.Button("Click"))
{
Debug.Log("Click!!!");
}

GUILayout.Label("Hello World!");
}
}
#endif