文章目录
更改Unity编辑器(一共就3步)
第一步
第二步
第三步
打开即可,over。
一、拓展Project视图
1.拓展右键菜单
编辑器使用的代码应该仅限于编辑模式下,正式的游戏包不应该包含这些代码。
Unity提供了一个规则:
- 如果属于编辑模式下的代码,需要放在Editor文件夹下;
- 如果属于执行时执行的代码,放在任意非Editor文件夹下;
注:Editor文件夹是在Create->Folder时重命名的,Editor文件夹可以放在任意位置
在Editor文件夹下创建一个C#脚本文件
using UnityEngine;
using UnityEditor;
public class Script_03_01 {
[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);
}
[MenuItem("Assets/My Tools/Tools 3", false, 3)]
static void MyTools3()
{
Debug.Log(Selection.activeObject.name);
}
}
2.创建菜单
与上一方法类似,直接在MenuItem方法中添加路径即可
using UnityEngine;
using UnityEditor;
public class Script_03_02 {
[MenuItem("Assets/Create/My Create/Cube", false, 2)]
static void CreateCube()
{
GameObject.CreatePrimitive(PrimitiveType.Cube);//创建立方体
}
[MenuItem("Assets/Create/My Create/Sphere", false, 1)]
static void CreateSphere()
{
GameObject.CreatePrimitive(PrimitiveType.Sphere);//创建球体
}
}
效果演示
3.拓展布局
当鼠标选中一个资源后,会出现拓展后的click按钮,带你加这个按钮,程序会自动在Console窗口中打印选中的资源名。
using UnityEngine;
using UnityEditor;
public class Script_03_03 {
[InitializeOnLoadMethod]
static void InitializeOnLoadMethod()
{
EditorApplication.projectWindowItemOnGUI = delegate (string guid, Rect selectionRect)
{
//在Project视图中选择一个资源
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.yellow;
//点击事件
if (GUI.Button(selectionRect, "click"))
{
Debug.LogFormat("click:{0}", Selection.activeObject.name);
}
GUI.color = Color.white;
}
}; //这有个分号
}
}
5.监听事件
Project视图中的资源比较多,如果不好好规划,资源就会很凌乱。所以我们需要借助程序来约束资源,这可以通过监听资源的创建、删除、移动和保存等事件来实现。
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class Script_03_04: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);
//true表示资源可以打开,false表示不允许在Unity中dak该资源
return true;
}
//监听"资源即将被创建"事件
public static void onWillCreateAsset(string path)
{
Debug.LogFormat("path:{0}", path);
}
//监听"资源即将被保存"事件
public static string[] onWillSaveAssets(string[] paths)
{
if(paths!=null)
{
Debug.LogFormat("path:{0}", string.Join(",", paths));
}
return paths;
}
//监听"资源即将被移动"事件
public static AssetMoveResult onWillMoveAsset(string oldPath,string newPath)
{
Debug.LogFormat("from:{0}to:{1}", oldPath, newPath);
//AssetMoveResult.DidMove表示该资源可以移动
return AssetMoveResult.DidMove;
}
//监听"资源即将被删除"事件
public static AssetDeleteResult onWillDeleteAsset(string assetPath,RemoveAssetOptions option)
{
Debug.LogFormat("delete:{0}", assetPath);
//AssetDeleteResult.DidNotDelete 表示该资源可以被删除
return AssetDeleteResult.DidDelete;
}
}
二、拓展Hierarchy视图
Hierarchy视图中出现的都是游戏对象,这些对象之间同样具有一定的关联关系。一般用树状结构来表示对象之间复杂的父子关系。Hierarchy视图中的游戏对象会通过摄像机最终投影在发布的游戏中。
1.拓展菜单
using UnityEngine;
using UnityEditor;
public class Script_03_05
{
[MenuItem("GameObject/My Create/Cube", false, 0)]
static void CreateCube()
{
GameObject.CreatePrimitive(PrimitiveType.Cube); //创建立方体
}
}
2.拓展布局
using UnityEngine;
using UnityEditor;
public class Script_03_06
{
[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;
//点击事件
if(GUI.Button(selectionRect,AssetDatabase.LoadAssetAtPath<Texture>("Assets/unity.png")))
{
Debug.LogFormat("click:{0}", Selection.activeObject.name);
}
}
};
}
}
3.重写菜单
可以将Hierarchy视图抛弃原有菜单,新建菜单。主要用来监听用。
using UnityEngine;
using UnityEditor;
public class Script_03_07
{
[MenuItem("Window/Test/fafa")]
static void Test()
{
}
[MenuItem("Window/Test/ai")]
static void Test1()
{
}
[MenuItem("Window/Test/小汤圆/芝麻馅")]
static void Test2()
{
}
[InitializeOnLoadMethod]
static void StartIntializeOnLoadMethod()
{
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyGUI;
}
static void OnHierarchyGUI(int instanceID,Rect selectionRect)
{
//用Event.current来获取当前操作
if(Event.current!=null&&selectionRect.Contains(Event.current.mousePosition)&&Event.current.button==1&&Event.current.type<=EventType.MouseUp)
{
GameObject selectedGsmeObject = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
//从这里可以判断selectedGsmeObject的条件
if(selectedGsmeObject)
{
Vector2 mousePosition = Event.current.mousePosition;
//用于弹出自定义菜单
EditorUtility.DisplayPopupMenu(new Rect(mousePosition.x, mousePosition.y, 0, 0), "Window/Test", null);
//不再执行原有的操作
Event.current.Use();
}
}
}
}
此外,Hierarchy视图还可以重写系统自带的菜单行为。
using UnityEngine;
using UnityEditor;
using UnityEngine.UI;
//以菜单中的UI->Image组件为例
//当我们创建组件时,会自动勾选RaycastTarget。如果图片不需要处理点击事件,这样就可以节省开销
public class Script_03_08
{
[MenuItem("GameObject/UI/Image")]
static void CreatImage()
{
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;
}
}
}
}
三、拓展Inspector视图
Inspector视图用来展示组件以及资源的详细信息面板,每个组件的面板信息是各不相同的。
1.拓展源生组件
摄像机就是一个典型的原生组件。可以在摄像机组件的最上或最下添加一个按钮,注意不能添加在中间。
using UnityEngine;
using UnityEditor;
//CustomEditor表示自定义哪个组件
[CustomEditor(typeof(Camera))]
public class Script_03_09:Editor
{
//OnInspectorGUI()可以对他进行重新绘制
public override void OnInspectorGUI()
{
if (GUILayout.Button("拓展按钮"))
{
}
//表示是否绘制父类原有元素
base.OnInspectorGUI();
}
}
2.拓展继承组件
有些系统组件可能在Unity内部已经重写了绘制方法,但是外部是访问不了内部代码,所以修改起来比较麻烦发。不过我们还是有办法。
Unity将大量的Editor绘制方法封装在内部的DLL文件里,开发者无法调用它的方法。如果想解决这个问题,可以使用C#反射的方式调用内部未公开的方法。
using UnityEngine;
using UnityEditor;
using System.Reflection;
[CustomEditor(typeof(Transform))]
public class Script_03_10:Editor
{
private Editor m_Editor;
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();
//base.OnInspectorGUI();
}
}
3.组件不可编辑
在Unity中,我们可以给组件设置状态,这样他就无法编辑了。将Transform组件的原始功能禁掉,而不影响我们上下拓展的两个按钮。
using UnityEngine;
using UnityEditor;
using System.Reflection;
[CustomEditor(typeof(Transform))]
public class Script_03_11:Editor
{
private Editor m_Editor;
void OnEnable()
{
m_Editor = Editor.CreateEditor(target, Assembly.GetAssembly(typeof(Editor)).GetType("UnityEditor.TransformInspector", true));
}
public override void OnInspectorGUI()
{
if (GUILayout.Button("拓展按钮上"))
{
}
//开始禁止
base.OnInspectorGUI();
m_Editor.OnInspectorGUI();
//禁止结束
GUI.enabled = true;
if (GUILayout.Button("拓展按钮下"))
{
}
}
}
如果想整体禁止组件,可以设置游戏对象的hideFlags
using UnityEngine;
using UnityEditor;
public class Script_03_12
{
[MenuItem("GameObject/3D Object/Lock/Lock",false,0)]
static void Lock()
{
if(Selection.gameObjects!=null)
{
foreach(var gameObject in Selection.gameObjects)
{
gameObject.hideFlags = HideFlags.NotEditable;
}
}
}
[MenuItem("GameObject/3D Object/Lock/UnLock", false, 1)]
static void UnLock()
{
if (Selection.gameObjects != null)
{
foreach (var gameObject in Selection.gameObjects)
{
gameObject.hideFlags = HideFlags.None;
}
}
}
}
HideFlags可以使用按位或(|)同时保持多个属性,
- HideFlags.None: 清除状态
- HideFlags.DontSave:设置对象不会被保存(仅编辑模式下使用,运行时剔除掉)
- HIdeFlags.DontSaveInBuild:设置对象构建后不会被保存。
- HideFlags.DontSaveInEditor:设置对象编辑模式下不会被保存。
- HideFlags.DontUnloadUnusedAsset:设置对象不会被 Resources.UnloadUnusedAssets()卸载无用资源时卸掉。
- HIdeFlags.HideAndDontSave:设置对象隐藏,并且不会被保存。
- HideFlags.HideInHierarchy:设置对象在层次视图中隐藏。
- HIdeFlags.HideInInspector:设置对象在控制面板视图中隐藏。
- HideFlags.NotEditable:设置对象不可被编辑。
Context菜单
using UnityEngine;
using UnityEditor;
public class Script_03_13
{
[MenuItem("CONTEXT/Transform/New Contect 1")]
public static void NewContext1(MenuCommand commannd)
{
//获取对象名
Debug.Log(commannd.context.name);
}
[MenuItem("CONTEXT/Transform/New Contect 2")]
public static void NewContext2(MenuCommand commannd)
{
Debug.Log(commannd.context.name);
}
}
四、拓展Scene视图
我们可以在Scene视图中绘制立方体、网格、贴图、射线和UI等
1.辅助元素
场景在编辑过程中,通常需要一些辅助元素,这样使用者可以更高效地完成编辑工作。
using UnityEngine;
public class Script_03_15 : MonoBehaviour {
void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
//画线
Gizmos.DrawLine(transform.position, Vector3.one);
//立方体
Gizmos.DrawCube(Vector3.one, Vector3.one);
}
}
2.辅助UI
在Scene视图中,我们可以添加EditorGUI,这样可以方便地在视图中处理一些操纵事件。
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Camera))]
public class Script_03_16 : Editor{
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();
GUI.backgroundColor = Color.red;
if (GUILayout.Button("click", GUILayout.Width(200f))){
Debug.LogFormat("click={0}", camera.name);
}
GUILayout.Label("Lable");
Handles.EndGUI();
}
}
}
3.常驻辅助UI
上面辅助UI 需要选择一个对象,而常驻辅助UI不需要
using UnityEditor;
using UnityEngine;
public class Script_03_18 {
[InitializeOnLoadMethod]
static void InitializeOnLoadMethod()
{
SceneView.onSceneGUIDelegate = delegate (SceneView sceneView)
{
Handles.BeginGUI();
GUI.Label(new Rect(0f, 0f, 50f, 15f), "标题");
GUI.Button(new Rect(0f, 20f, 50f, 50f), AssetDatabase.LoadAssetAtPath<Texture>("Assets/unity.png"));
Handles.EndGUI();
};
}
}
4.禁用选中对象
在Scene视图和Hierarchy视图中,都可以选择游戏对象。Scene视图中因为东西很多,而且很可能大量重叠,很容易选错对象。所在在开发过程中,如果不希望Scene视图中误操作别的对象,我们可以禁用选中对象地功能。
using UnityEditor;
using UnityEngine;
public class Script_03_19 {
[InitializeOnLoadMethod]
static void InitializeOnLoadMethod()
{
SceneView.onSceneGUIDelegate = delegate (SceneView sceneView)
{
Event e = Event.current;
if(e!=null)
{
int controlID = GUIUtility.GetControlID(FocusType.Passive);
//FocusType.Passive表示禁止接收控制焦点,获取它的controlID后即可禁止将点击事件穿透下去
if (e.type==EventType.Layout)
{
HandleUtility.AddDefaultControl(controlID);
}
}
};
}
}
还有一种办法,即以层为单位,设置某个层无法被选中。
五、拓展Game视图
Game视图输出的是最终的游戏画面,它的拓展主要有两部分:运行模式下以及非运行模式下。
脚本挂在游戏对象后,需要运行游戏才可以执行脚本的生命周期,不过非运行模式下也可以执行脚本。
using UnityEngine;
#if UNITY_EDITOR
[ExecuteInEditMode]
public class Script_03_20:MonoBehaviour
{
void OnGUI()
{
if(GUILayout.Button("Click"))
{
Debug.Log("click!!!");
}
GUILayout.Label("Hello World!!");
}
}
#endif
六、Menultem菜单
Unity编辑器使用的拓展菜单是Menultem。
1.自定义菜单
自定义菜单可以设置路径,排序,勾选框和禁止选中状态。
using UnityEngine;
using UnityEditor;
public class Script_03_21
{
[MenuItem("Root/Text1",false,1)]
static void Text1()
{
}
//菜单排序
[MenuItem("Root/Text0",false,0)]
static void Text0()
{
}
[MenuItem("Root/Text/2")]
static void Text2()
{
}
[MenuItem("Root/Text/2",true,20)]
//上一个菜单的优先级,一共预留了10个元素的位置,只要优先级+11即可,就会自动增加下划线效果
static bool Text2Validation()
{
//false表示Root/Text/2菜单将置灰,即不可点击
return false;
}
[MenuItem("Root/Text3",false,3)]
static void Text3()
{
//勾选框中的菜单
var menuPath = "Root/Text3";
bool mchecked = Menu.GetChecked(menuPath);
Menu.SetChecked(menuPath, !mchecked);
}
}
2.源生自定义菜单
MenuItem是依托于Unity编辑器的菜单栏,无法设置它的位置。如果希望菜单位置以及出现时机更加灵活的话,可以调用源生自定义菜单的方法。
using UnityEngine;
using UnityEditor;
public class Script_03_22
{
[InitializeOnLoadMethod]
static void InitializeOnLoadMethod()
{
SceneView.onSceneGUIDelegate = delegate (SceneView sceneView)
{
Event e = Event.current;
//鼠标右键抬起时
if (e != null && e.button == 1 && e.type == EventType.MouseUp)
{
Vector2 mousePosition = e.mousePosition;
//设置菜单项
var options = new GUIContent[]
{
new GUIContent("Test1"),
new GUIContent("Test2"),
new GUIContent(""),
new GUIContent("Test3"),
new GUIContent("Test4"),
};
//设置菜单显示区域
var selected = -1;
var userData = Selection.activeGameObject;
var width = 100;
var height = 100;
var position = new Rect(mousePosition.x, mousePosition.y - height, width, height);
//显示菜单
EditorUtility.DisplayCustomMenu(position, options, selected,
delegate (object data, string[] opt, int select)
{
Debug.Log(opt[select]);
}, userData);
e.Use();
}
};
}
}
拓展后,可以在Scene视图中点击鼠标右键,此时会弹出一组源生自定义菜单。
代码中,首先监听鼠标右键以获取鼠标位置,接着使用EditorUtility.DisplayCustomMenu()方法来弹出自定义菜单,以及监听菜单选择后的事件。
3.拓展全局自定义快捷键
Unity没有提供全局自定义快捷键,但可以利用MenuItem提供的快捷键来实现。
using UnityEngine;
using UnityEditor;
public class Script_03_15
{
[MenuItem("Assets/HotKey %#d",false,-1)]
private static void HotKey()
{
Debug.Log("Command Shift+D");
}
}
我们自定义了快捷键Command+Shift+D,使用者将需要执行的逻辑写在方法体内即可。
热键可以相互组合
- %:表示Windows下的Ctrl键和macOS下的Command键。
- #:表示Shift键
- &:表示Alt键
- LEFT / RIGHT / UP / DOWN:表示左,右,上,下4个方向键
- F1…F12:表示F1至F12菜单键
- HOME 、END、PGUP、和PGDN键
七、面板拓展
脚本挂在游戏对象上时,右侧会出现它的详细信息面板,这些信息是根据脚本中声明的public可序列化变量而来的。
1.Inspector面板
EditorGUI和GUI的用法几乎完全一致。
- EditorGUI多用于编辑器开发
- GUI多用于发布后调试编辑器
EditorGUI提供的组件非常丰富,常用的绘制元素包括文本、按钮、图片、和滚动框等。
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class Script_03_15 : MonoBehaviour
{
public Vector3 scrollPos;
public int myId;
public string myName;
public GameObject prefab;
public MyEnum myEnum = MyEnum.One;
public bool toole1;
public bool toole2;
public enum MyEnum
{
One = 1,
Two,
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(Script_03_15))]
public class ScriptEditor_03_15 : Editor
{
private bool m_EnableToogle;
public override void OnInspectorGUI()
{
//获取脚本对象
Script_03_15 script = target as Script_03_15;
//绘制滚动条
script.scrollPos = EditorGUILayout.BeginScrollView(script.scrollPos, false, true);
script.myName = EditorGUILayout.TextField("text", script.myName);
script.myId = EditorGUILayout.IntField("int", script.myId);
script.prefab = EditorGUILayout.ObjectField("GameObject", script.prefab, typeof(GameObject), true) as GameObject;
//绘制按钮
EditorGUILayout.BeginHorizontal();
GUILayout.Button("1");
GUILayout.Button("2");
script.myEnum = (Script_03_15.MyEnum)EditorGUILayout.EnumPopup("MyEnum:", script.myEnum);
EditorGUILayout.EndHorizontal();
//Toogle组件
m_EnableToogle = EditorGUILayout.BeginToggleGroup("EnableToogle", m_EnableToogle);
script.toole1 = EditorGUILayout.Toggle("toogle1", script.toole1);
script.toole2 = EditorGUILayout.Toggle("toogle2", script.toole2);
EditorGUILayout.EndToggleGroup();
EditorGUILayout.EndScrollView();
}
}
#endif
2.EditorWindow窗口
Unity提供编辑器窗口,开发者可以自由拓展自己的窗口。
using UnityEngine;
using UnityEditor;
public class Script_03_15 : EditorWindow
{
[MenuItem("Window/Open My Window")]
static void Init()
{
Script_03_15 window = (Script_03_15)EditorWindow.GetWindow(typeof(Script_03_15));
window.Show();
}
private Texture m_MyTexture = null;
private float m_MyFloat = 0.5f;
void Awake()
{
Debug.Log("窗口初始化时调用");
m_MyTexture = AssetDatabase.LoadAssetAtPath<Texture>("Assets/unity.png");
}
void OnGUI()
{
GUILayout.Label("Hello World!!", EditorStyles.boldLabel);
m_MyFloat = EditorGUILayout.Slider("Slider", m_MyFloat, -5, 5);
GUI.DrawTexture(new Rect(0, 30, 100, 100), m_MyTexture);
}
void OnDestroy()
{
Debug.LogFormat("窗口销毁时调用");
}
void OnFocus()
{
Debug.LogFormat("窗口拥有焦点时调用");
}
void OnHierachyChange()
{
Debug.LogFormat("Hierachy视图发生改变时调用");
}
void OnInspectorUpdate()
{
//Debug.LogFormat("Inspector每帧更新");
}
void OnLostFocus()
{
Debug.LogFormat("失去焦点");
}
void OnProjectChange()
{
Debug.LogFormat("Project视图发生改变时调用");
}
void OnSelectionChange()
{
Debug.LogFormat("在Hierachy或者Project视图中选择一个对象时调用");
}
void Update()
{
//Debug.LogFormat("每帧更新");
}
}
3.预览窗口
选择游戏对象或者游戏资源后,Inspector面板下方将会出现它的预览窗口,但有些资源是没有预览信息的,不过我们可以监听它的窗口方法来重新绘制它。
using UnityEngine;
using UnityEditor;
[CustomPreview(typeof(GameObject))]
public class Script_03_15 : ObjectPreview //继承ObjectPreview
{
public override bool HasPreviewGUI() //重写OnPreviewGUI()方法
{
return true;
}
public override void OnPreviewGUI(Rect r, GUIStyle background)
{
GUI.DrawTexture(r, AssetDatabase.LoadAssetAtPath<Texture>("Assest/unity.png"));
GUILayout.Label("Hello World!!");
}
}
4.获取预览信息
有些资源是有预览信息的,我们可以在预览窗口可以看到它的样式。如果需要在自定义窗口中显示它,就需要获取它的预览信息。
using UnityEngine;
using UnityEditor;
public class Script_03_15 : EditorWindow
{
private GameObject m_MyGo;
private Editor m_MyEditor;
[MenuItem("Window/Open My Window")]
static void Init()
{
Script_03_15 window = (Script_03_15)EditorWindow.GetWindow(typeof(Script_03_15));
window.Show();
}
void OnGUI()
{
//设置一个游戏对象
m_MyGo = (GameObject)EditorGUILayout.ObjectField(m_MyGo, typeof(GameObject), true);
if(m_MyGo!=null)
{
if(m_MyEditor==null)
{
//创建Editor实例
m_MyEditor = Editor.CreateEditor(m_MyGo);
}
//预览它
m_MyEditor.OnPreviewGUI(GUILayoutUtility.GetRect(500, 500), EditorStyles.whiteLabel);
}
}
}
总结于:
《Unity3D游戏开发》(第2版) 作者:宣雨松