概述

第2章: 知识点

第1节: 自定义菜单栏和窗口拓展

自定义菜单栏扩展

导入

和Unity的许多原生窗口和功能一样,我们触发Editor有关功能的时候经常需要一个入口按钮,我们经常会采取自定义菜单栏的扩展来触发。

知识点一 特殊文件夹Editor

我们之前在学习Unity相关知识点时学习过

Editor文件夹是Unity中的特殊文件夹

所有Unity中编辑器相关的脚本都需要放置在其中

我们在本套课程学习中,会频繁的使用到UnityEditor等命名空间

这些使用了Unity编辑器相关命名空间的脚本最终是不能被打包出去的

我们需要把这些脚本放置到Editor文件夹中,避免打包时报错

知识点二 在Unity菜单栏中添加自定义页签

命名空间:UnityEditor

特性:MenuItem

用法:

在静态函数前加上

[MenuItem("页签名/一级选项/二级选项/....")]

作用:

当在菜单栏点击该页签时

将执行静态函数中逻辑

这里经常会作为编辑器执行的入口

注意:

  1. 不用在意继承对象

  2. 斜杠必须是/

知识点三 在Hierarchy窗口中添加自定义页签

命名空间:UnityEditor

特性:MenuItem

用法:

在静态函数前加上,并且页签命名放入GameObject路径

[MenuItem("GameObject/页签/一级选项/二级选项/....")]

注意:

  1. 不用在意继承对象

  2. 斜杠必须是/

  3. 在菜单栏的GameObject中也会出现对应选项

知识点四 在Project窗口中添加自定义页签

命名空间:UnityEditor

特性:MenuItem

用法:

在静态函数前加上,并且页签命名放入Assets路径

[MenuItem("Assets/页签/一级选项/二级选项/....")]

注意:

  1. 不用在意继承对象

  2. 斜杠必须是/

  3. 在菜单栏的Assets中也会出现对应选项

知识点五 在菜单栏的Component菜单添加脚本

命名空间:UnityEngine

特性:AddComponentMenu

用法:

在想要通过Component菜单添加的脚本前加上

[AddComponentMenu("一级选项/二级选项/....")]

即可在Compoment找到并进行添加

注意:

  1. 脚本需要继承MonoBehaviour对象

  2. 斜杠必须是/

  3. 最后一级选项的名字可以和脚本名不一样,但是建议一致

知识点六 在Inspector为脚本右键添加菜单

命名空间:UnityEditor

特性:MenuItem

用法:

在静态函数前加上

[MenuItem("CONTEXT/脚本名/页签/一级选项/二级选项/....")]

注意:

  1. 不用在意继承对象

  2. 斜杠必须是/

知识点七 加入快捷键

[MenuItem("Unity编辑器拓展/Lesson1/TestFun %#&A")] //加入快捷键Ctrl+shift+alt+A
private static void TestFun()
{
    Debug.Log("TestFun");
}

单键

路径后 + 空格 + 下划线 + 想要的按键

组合键

下划线替换为

%表示ctrl

#表示shift

&表示alt

其他支持的按键:

LEFT、RIGHT:持类似#LEFT是左shift之类的按键

UP、DOWN、F1..F12、HOME、END、PGUP、PGDN

总结

  1. 使用UnityEditor命名空间的脚本必须放在Editor文件夹下,避免打包报错

  2. 添加自定义页签 需要用到特性 MenuItem

  3. 添加代码添加页签 需要用到特性 AddComponentMenu

自定义窗口拓展

导入

某些自定义功能可能只需要自定义按钮点击一下即可,但是又有许多的功能需要开发者每次使用的时候进行配置变量,然后进行交互和调试。

这时候,我们通常就会使用Unity的自定义窗口扩展来自己开发一个窗口进行交互,可以实现类似Unity的功能窗口。

知识点一 创建窗口类

命名空间:UnityEditor

类名:EditorWindow

当我们想要为Unity拓展一个自定义窗口时

我们只需要实现一个继承了EditorWindow的类即可

并且在该类的OnGUI函数中编写面板控件相关的逻辑

public class Lesson2 : EditorWindow
{
    [MenuItem("Unity编辑器拓展/Lesson2/显示自定义面板")]
    private static void ShowWindow()
    {
        Lesson2 win = EditorWindow.GetWindow<Lesson2>();
        win.titleContent = new GUIContent("我的窗口");
        win.Show();
    }
}

知识点二 显示窗口

用上节课学习的添加自定义页签的知识

添加一个自定义页签用于开启窗口

调用

EditorWindow.GetWindow

该方法有很多种重载

其中的主要参数一般有

  • Type或T:窗口类的类型

  • utility:为 true 可创建浮动实用程序窗口,设置为 false 可创建正常窗口。所谓浮动窗口就是可以自由拖动自由改变大小

  • title:窗口标题

  • focus:是否为窗口提供焦点(如果已存在)。(如果 GetWindow 创建新窗口,则将始终获得焦点)

  • desiredDockNextTo:窗口试图停靠到其上的 EditorWindow 类型的数组

创建窗口对象

之后调用对象的Show方法即可显示窗口

知识点三 窗口事件回调函数

继承EditorWindow的窗口类 自带一些事件回调函数

当触发对应事件时会自动进入

  • OnHierarchyChange():当场景中的层次结构(Hierarchy)发生变化时调用。 例如,当游戏对象被创建、删除或重命名时触发。

  • OnFocus():当窗口获得焦点时调用。在这个方法中,你可以执行一些在窗口获得焦点时需要进行的操作。

  • OnLostFocus():当窗口失去焦点时调用。通常在这个方法中执行一些在窗口失去焦点时需要进行的清理工作。

  • OnProjectChange():当项目资源发生变化时调用。例如,当添加、删除或修改项目中的文件时触发。

  • OnInspectorUpdate():在检视器(Inspector)面板更新时调用。可以在这个方法中执行需要在检视器面板刷新时进行的逻辑,比如更新显示的信息

  • OnSelectionChange():当选择的对象发生变化时调用。在这个方法中,你可以执行与所选对象相关的操作,以确保编辑器窗口的内容与当前选择保持同步。

知识点四 窗口中常用的生命周期函数

OnEnable():

当窗口被激活时调用,通常在窗口创建时会调用一次。

在这个方法中,你可以进行一些初始化工作,例如注册事件监听器或设置初始变量。

OnGUI():

每帧都会调用此方法,用于绘制编辑器窗口的 GUI。

在这个方法中,你可以使用 GUILayout 或 EditorGUILayout 等类创建界面元素,以便用户与窗口进行交互。

OnDestroy():

当窗口被销毁时调用,通常在关闭编辑器或切换场景时触发。在这里进行最终的清理工作,确保没有未释放的资源。

Update():

在编辑器窗口每帧更新时调用。通常在这里执行一些需要在每帧进行的逻辑

知识点五 编辑器窗口类中的常用成员

Unity官方文档有关于编辑器窗口类的API说明

https://docs.unity.cn/cn/2022.3/ScriptReference/EditorWindow.html

静态变量
  1. focusedWindow:当前已获得键盘焦点的 EditorWindow。(只读)

  2. mouseOverWindow:当前在鼠标光标下的 EditorWindow。(只读)

静态函数
  • CreateWindow: 创建窗口,如果允许一个窗口有多个可以用该API创建窗口

  • GetWindow: 通过它我们可以创建一个窗口对象

  • GetWindowWithRect:返回一个指定位置、大小的窗口

  • HasOpenInstances:检查编辑器窗口是否打开

成员变量
  • titleContent:窗口标题名

  • positon:窗口位置大小信息

  • wantsMouseEnterLeaveWindow:如果设置为 true,则每当鼠标进入或离开窗口时,该窗口都会收到一次 OnGUI 调用

成员函数
  • Show: 显示面板

  • Repaint:重绘窗口

  • Close: 关闭窗口

总结

编辑器拓展中想要实现自定义窗口

只需要继承EditorWindow类

主要在OnGUI中利用GUI相关API绘制控件处理功能逻辑

对应的事件回调函数,可以帮助我们监听到一些特殊情况用于处理对应逻辑

第2节: EditorGUI相关

导入——EditorGUI是什么

知识回顾

GUILayout 是一个GUI自动布局的公共类

它其中的方法和GUI基本一模一样,都是用来绘制、响应各种UI控件的

只不过它在GUI的基础上加入了自动布局功能

我们无需过多的去关心UI控件的位置和大小

GUILayoutOption 布局选项

控件的固定宽高

GUILayout.Width(300);

GUILayout.Height(200);

允许控件的最小宽高

GUILayout.MinWidth(50);

GUILayout.MinHeight(50);

允许控件的最大宽高

GUILayout.MaxWidth(100);

GUILayout.MaxHeight(100);

允许或禁止水平拓展

GUILayout.ExpandWidth(true);//允许

GUILayout.ExpandHeight(false);//禁止

GUILayout.ExpandHeight(true);//允许

GUILayout.ExpandHeight(false);//禁止

知识点 EditorGUI是什么?

EditorGUI 类似 GUI

是一个主要用于绘制编辑器拓展 UI 的工具类

它提供了一些 GUI 中没有的API

主要是 编辑器功能中会用到的一些 特殊控件

而EditorGUILayout 类似于 GUILayout

是一个带有自动布局功能的 EditorGUI 绘制工具类

我们经常会将 EditorGUI 和 GUI 混合使用 来制作一些编辑器拓展功能

但是由于更多时候我们会用到自动布局功能

因此我们接下来着重讲解 EditorGUILayout 中的功能

EditorGUI和它的区别仅仅是需要自己设置位置而已

详细内容:https://docs.unity.cn/cn/2022.3/ScriptReference/EditorGUILayout.html

控件API

文本、层级和标签、颜色拾取 控件

EditorGUILayout中的文本控件

EditorGUILayout.LabelField("文本标题", "文本内容");

EditorGUILayout中的层级、标签选择

Layer

int变量 = EditorGUILayout.LayerField("层级选择", int变量);

Tag

string变量 = EditorGUILayout.TagField("标签选择", string变量);

EditorGUILayout中的颜色获取

color变量 = EditorGUILayout.ColorField(new GUIContent("标题"),color变量, 是否显示拾色器, 是否显示透明度通道, 是否支持HDR);

枚举、选择、按下按钮 控件

枚举选择控件
  • 枚举选择

枚举变量 = (枚举类型)EditorGUILayout.EnumPopup("枚举选择", 枚举变量);

  • 多选枚举

注意:多选枚举进行的是或运算,声明枚举时一定注意其中的赋值

枚举变量 = (枚举类型)EditorGUILayout.EnumFlagsField("枚举多选", 枚举变量);

拓展:

如果你希望在面板上,某些特殊的枚举组合情况下可以显示特殊的名字,那你需要为这个组合枚举准备一个单独的枚举。

如:

public enum E_TestType
{
    One = 1,
    Two = 2,
    Three = 4,
    组合枚举特殊显示名字 = 1 | 2//也可以用来装载1和2同时满足的枚举,
}

否则,多选枚举的情况下,枚举们会用逗号隔开显示。

整数选择控件

int变量 = EditorGUILayout.IntPopup("整数单选框", int变量, 字符串数组, int数组);

按下就触发的按钮控件

创建一个能够对鼠标按下做出反应的按钮,用于显示自己的下拉菜单内容。

此控件不会执行任何操作,只是在单击时针对鼠标按下返回 true;这与常规按钮相反,后者在鼠标松开时返回 true。

这可用于以下拉菜单形式打开 GenericMenu 或自己的自定义 EditorWindow 的按钮。

EditorGUILayout.DropdownButton(new GUIContent("按钮上文字"), FocusType.Passive)

FocusType枚举时告诉UI系统能够获得键盘焦点 当用户按Tab键时在控件之间进行切换,变量有:

  • Keyboard 该控件可接收键盘焦点。

  • Passive 该控件不能接收键盘焦点。

对象关联、各类型输入 控件

对象关联控件

对象变量 = EditorGUILayout.ObjectField(对象变量, typeof(对象类型), 是否允许关联场景上对象资源) as 对象类型;

各类型输入控件
int变量 = EditorGUILayout.IntField("Int输入框", int变量);
long变量 = EditorGUILayout.LongField("long输入框", long变量);
float变量 = EditorGUILayout.FloatField("Float 输入:", float变量);
double变量 = EditorGUILayout.DoubleField("double 输入:", double变量);

string变量 = EditorGUILayout.TextField("Text输入:", string变量);
vector2变量 = EditorGUILayout.Vector2Field("Vec2输入: ", vector2变量);
vector3变量 = EditorGUILayout.Vector3Field("Vec3输入: ", vector3变量);
vector4变量 = EditorGUILayout.Vector4Field("Vec4输入: ", vector4变量);
rect变量 = EditorGUILayout.RectField("rect输入: ", rect变量);
bounds变量 = EditorGUILayout.BoundsField("Bounds输入: ", bounds变量);
boundsInt变量 = EditorGUILayout.BoundsIntField("Bounds输入: ", boundsInt变量);

注意:EditorGUILayout中还有一些Delayed开头的输入控件

他们和普通输入控件最主要的区别是:在用户按 Enter 键或将焦点从字段移开之前,返回值不会更改

折叠、折叠组 控件

两个空间都是用来折叠显示的,但是用法上有所不同

折叠控件
private bool foldoutBool = false;
void OnGUI()
  {
      foldoutBool = EditorGUILayout.Foldout(foldoutBool, "标题名");//折叠控件
      if (foldoutBool)
      {
          EditorGUILayout.LabelField("这里是折叠区域内的内容");
      }
  }

折叠组控件
private bool foldoutHeaderBool = false;
void OnGUI()
{
    foldoutHeaderBool = EditorGUILayout.BeginFoldoutHeaderGroup(foldoutHeaderBool, "标题名");//折叠组空间
    if (foldoutHeaderBool)
    {
        EditorGUILayout.LabelField("这里是折叠头部组内的内容");
        // 可以添加更多的编辑器UI元素
    }
    EditorGUILayout.EndFoldoutHeaderGroup();
}

开关、开关组 控件

开关控件

bool变量 = EditorGUILayout.Toggle("普通开关", bool变量);

bool变量 = EditorGUILayout.ToggleLeft("开关在左侧", bool变量);

开关组控件

这种写法的开关,如果开关不为True,UI插件虽然会显示,但是会置灰无法交互

bool变量 = EditorGUILayout.BeginToggleGroup("开关组", bool变量);
//其他UI插件
EditorGUILayout.EndToggleGroup();

滑动条、双滑块滑动条 控件

滑动条控件

float变量 = EditorGUILayout.Slider("滑动条", float变量, 最小值, 最大值);

int变量 = EditorGUILayout.IntSlider("整数值滑动条", int变量, 最小值, 最大值);

双块滑动条控件

可以从两边开始拖动和设定数值。

EditorGUILayout.MinMaxSlider("双块滑动条", ref 左侧值, ref 右侧值, 最小值, 最大值);

帮助框、垂直间隔 控件

帮助框控件
EditorGUILayout.HelpBox("一般提示内容", MessageType.None);
EditorGUILayout.HelpBox("感叹号提示内容", MessageType.Info);
EditorGUILayout.HelpBox("警告符号提示内容", MessageType.Warning);
EditorGUILayout.HelpBox("错误符号提示内容", MessageType.Error);
间隔控件
//UI控件1
EditorGUILayout.Space(10); //进行10个单位的间隔
//UI控件2

动画曲线控件 和 布局API

动画曲线控件

设置一个UI,供开发者调控自定义曲线

AnimationCurve变量 = EditorGUILayout.CurveField("动画曲线:", AnimationCurve变量);

知识点二 布局相关API

可以使用布局API,实现按区域更自定义的布局设置。

EditorGUILayout.BeginHorizontal(); //开始水平布局
//一大堆控件
EditorGUILayout.EndHorizontal();//结束水平布局

EditorGUILayout.BeginVertical();//开始垂直布局
//一大堆控件
EditorGUILayout.EndVertical();//结束垂直布局

Vector2布局 = EditorGUILayout.BeginScrollView(Vector2布局); //开启滚动视图
//一大堆控件
EditorGUILayout.EndScrollView(); //结束滚动视图

总结

常用的EditorGUILayout基本总结完毕了。

EditorGUILayout中更多内容,可以去查阅:

EditorGUILayout - Unity 脚本 API

第3节: EditorGUIUtility公共类

EditorGUIUtility公共类的作用是什么

EditorGUIUtility公共类是用来做什么的?

Utility是实用的意思,EditorGUIUtility 是 EditorGUI 中的一个实用工具类

提供了一些 EditorGUI 相关的其他辅助API

我们只需要学习其中的相对常用的内容

官方文档:Unity - Scripting API: EditorGUIUtility (unity3d.com)

资源加载

知识点一 Editor Default Resources文件夹

Editor Default Resources 也是Unity当中的一个特殊文件夹

它的主要作用是放置提供给 EditorGUIUtility 加载的资源

想要使用EditorGUIUtility公共类来加载资源

我们需要将资源放置在 Editor Default Resources 文件夹中

可以实现在编辑器的面板或者场景下,加载并设置自己的图标等功能。

知识点二 加载资源(如果资源不存在返回null)

对应API:

EditorGUIUtility.Load

注意事项:

  1. 只能加载Assets/Editor Default Resources/文件夹下的资源

  2. 加载资源时,需要填写资源后缀名

知识点三 加载资源(如果资源不存在会直接报错)

对应API:

EditorGUIUtility.LoadRequired

注意事项:

  1. 只能加载Assets/Editor Default Resources/文件夹下的资源

  2. 加载资源时,需要填写资源后缀名

搜索框查询、对象选中提示

搜索框查询

主要作用:

弹出一个搜索窗口,用于选择自己想要的资源。

有些类似之前学到的 EditorGUILayout.ObjectField ,但是这里附带搜索功能,而且调用时机可以自定义,比较灵活。

主要API:

EditorGUIUtility.ShowObjectPicker<资源类型>(默认被选中的对象, 是否允许查找场景对象, "查找对象名称过滤", 0);

  • 参数1. 默认被选中的对象的引用

  • 参数2. 是否允许查找场景对象

  • 参数3. 查找对象名称过滤(比如这里的normal是指文件名称中有normal的会被搜索到)

  • 参数4. controlID, 默认写0

获取选择对象:

主要API:

EditorGUIUtility.GetObjectPickerObject()

弹出的搜索窗口会通过发送事件的形式

通知开启它的窗口对象信息的变化

通过Event公共类可以获取其它窗口发送给自己的事件

  • Event.current 获取当前事件

  • commandName 获取事件命令的名字

  • ObjectSelectorUpdated 对象选择发生变化时发送

  • ObjectSelectorClosed 对象选择窗口关闭时发送

高亮选中的对象,使用这个API,会跳转并高亮你当前选中的对象位置。

EditorGUIUtility.PingObject(想要提示选中的对象);

代码示例:

private Texture img3;    
void OnGUI()
    {
        if(GUILayout.Button("打开搜索框查询窗口"))
        {
            EditorGUIUtility.ShowObjectPicker<GameObject>(null, false, "Editor", 0);
        }

        if(Event.current.commandName == "ObjectSelectorUpdated")
        {
            img3 = EditorGUIUtility.GetObjectPickerObject() as GameObject;
            if(img3 != null)
                Debug.Log(img3.name);
        }
        else if(Event.current.commandName == "ObjectSelectorClosed")
        {
            img3 = EditorGUIUtility.GetObjectPickerObject() as GameObject;
            if (img3 != null)
                Debug.Log("窗口关闭 - " + img3.name);
        }
        
        if(GUILayout.Button("高亮选中对象"))
        {
            if (img3 != null)
                EditorGUIUtility.PingObject(img3);
        }
    }

窗口事件传递、GUI坐标转换

知识点一 窗口事件传递

Event e = EditorGUIUtility.CommandEvent("事件名");

获取到另一个窗口后,让该窗口调用SendEvent(e)

在另一个窗口中可以通过

Event.current.type == EventType.ExecuteCommand

Event.current.commandName == "事件名"

进行判断

在传递事件时 会自动将接受事件的窗口打开 不管对象是否有监听处理对应的内容

知识点二 GUI坐标转换

屏幕坐标系:原点为屏幕左上角

GUI坐标系:原点为当前窗口左上角

  • EditorGUIUtility.GUIToScreenPoint:将点从GUI位置转换为屏幕空间

  • EditorGUIUtility.GUIToScreenRect:将rect从GUI位置转换为屏幕空间

  • EditorGUIUtility.ScreenToGUIPoint:将点从屏幕空间转换为GUI位置

  • EditorGUIUtility.ScreenToGUIRect:将rect从屏幕空间转换为GUI位置

指定区域使用对应鼠标指针

知识点 指定区域使用对应鼠标指针

AddCursorRect(Rect position, MouseCursor mouse);

  • MouseCursor鼠标光标类型枚举

  • Arrow 普通指针箭头

  • Text 文本文本光标

  • ResizeVertical 调整大小垂直调整大小箭头

  • ResizeHorizontal 调整大小水平调整大小箭头

  • Link 带有链接徽章的链接箭头

  • SlideArrow 滑动箭头带有小箭头的箭头,用于指示在数字字段处滑动

  • ResizeUpRight 调整大小向上向右调整窗口边缘的大小

  • ResizeUpLeft 窗口边缘为左

  • MoveArrow 带有移动符号的箭头旁边用于场景视图

  • RotateArrow 旁边有用于场景视图的旋转符号

  • ScaleArrow 旁边有用于场景视图的缩放符号

  • ArrowPlus 旁边带有加号的箭头

  • ArrowMinus 旁边带有减号的箭头

  • Pan 用拖动的手拖动光标进行平移

  • Orbit 用眼睛观察轨道的光标

  • Zoom 使用放大镜进行缩放的光标

  • FPS 带眼睛的光标和用于FPS导航的样式化箭头键

  • CustomCursor 当前用户定义的光标

  • SplitResizeUpDown 向上-向下调整窗口拆分器的大小箭头

  • SplitResizeLeftRight窗口拆分器的左-右调整大小箭头

举例代码:

  void OnGUI()
    {
        EditorGUI.DrawRect(new Rect(0, 150, 100, 100), Color.green);
        EditorGUIUtility.AddCursorRect(new Rect(0, 150, 100, 100), MouseCursor.Text);
    }
}

绘制色板、绘制曲线

知识点一 绘制色板

在指定区域绘制一个色板矩形

EditorGUIUtility.DrawColorSwatch(Rect 绘制色板的矩形, Color 颜色);

主要配合 EditorGUILayout.ColorField 颜色输入控件使用,供开发者查看颜色效果

知识点二 绘制曲线

在指定区域绘制曲线

EditorGUIUtility.DrawCurveSwatch(Rect 绘制曲线的范围, AnimationCurve 曲线,SerializedProperty 要绘制为SerializedProperty的曲线,Color 绘制曲线的颜色,Color 绘制背景的颜色);

主要配合 EditorGUILayout.CurveField 曲线输入控件使用,供开发者查看曲线效果

第4节: Selection公共类

Selection中常用静态成员

导入 Selection公共类是用来做什么的

主要用来获取当前开发者在Unity编辑器中选择的对象

只能用于编辑器开发中

这个选择对象可以是场景里的也可以是资源里的。

知识点一 获取当前选择的Object

获取当前在面板上选择的游戏物体Object

未选择则返回Null

选择多个则返回第一个选择的游戏物体

Selection.activeObject

知识点二 获取当前选择的GameObject

获取当前在面板上选择的游戏物体GameObject

未选择则返回Null

选择多个则返回第一个选择的游戏物体

Selection.activeGameObject

知识点三 获取当前选择的Transform

获取当前在面板上选择的游戏物体的Transform

未选择则返回Null

选择多个则返回第一个选择的游戏物体

Selection.activeTransform

只能获取到场景中的对象的Transform

知识点四 获取当前选择的所有Object

获取当前在面板上选择的物体数组

未选择则返回Null

Selection.objects

知识点五 获取当前选择的所有GameObject

获取当前在面板上选择的游戏物体或Project中预设体 GameObject数组

未选择则返回Null

Selection.gameObjects

可以遍历获取所有信息

知识点六 获取当前选择的所有Transform

获取当前在面板上选择的游戏物体Transform数组

未选择则返回Null

Selection.transforms

可以遍历获取所有信息

Selection常用静态方法

知识点一 判断某个对象是否被选中

Contains 判断某个对象是否被选中

知识点二 筛选对象

从当前选择对象中,筛选出想要的内容

Selection.GetFiltered(类型, 筛选模式)

Selection.GetFiltered<类型>(筛选模式)

筛选模式:SelectionMode

Unfiltered: 不过滤

  • TopLevel: 只获取最上层对象,子对象不获取

  • Deep: 父对象、子对象都获取

  • ExcludePrefab: 排除预设体

  • Editable: 只选择可编辑的对象

  • OnlyUserModifiable: 仅用户可修改的内容

  • Assets: 只返回资源文件夹下的内容

  • DeepAssets: 如果存在子文件夹,其中的内容也获取

如果要混用 位或 | 即可

知识点三 当选中变化时会自动调用的委托

Selection.selectionChanged += 函数;//选择的物体变化时调用

总结

Selection公共类主要是帮助我们获取到选择的对象的

我们可以利用它对选中对象进行一些处理

Selection公共类 更多API可以查阅:Selection - Unity 脚本 API

第5节: Event公共类

Event公共类

知识点一 Event公共类是用来做什么的?

除了上文用来获取窗口执行事件的用法。
它还提供了许多属性和方法,允许你检查和处理用户输入


主要用于在Unity编辑器拓展开发中

因为Input相关内容需要在运行时才能监听输入
而Event专门提供给编辑模式下使用,可以帮助我们检测鼠标键盘输入等事件相关操作
在 OnGUI 和 OnSceneView 中都能使用


 

知识点二 重要API


1.获取当前输入事件
  Event.current

2.alt键是否按下
  Event.current.alt

3.shift键是否按下
  Event.current.shift

4.ctrl键是否按下
  Event.current.control

5.是否是鼠标事件
  Event.current.isMouse

6.判断鼠标左中右键
  Evnet.current.button (0,1,2 分别代表 左,右,中 如果大于2可能是其他鼠标按键)

7.鼠标位置
  Event.curretn.mousePosition

8.判断是否是键盘输入
  Event.current.isKey

9.获取键盘输入的字符
  Event.current.character

10.获取键盘输入对应的KeyCode
  Event.current.keyCode

11.判断输入类型
  Event.current.type
  EventType枚举和它比较即可
  EventType中有常用的 鼠标按下抬起拖拽,键盘按下抬起等等类型

12.是否锁定大写 对应键盘上caps键是否开启
  Event.current.capsLock

13.Windows键或Command键是否按下
  Event.current.command

14.键盘事件 字符串
  Event.current.commandName
  可以用来判断是否触发了对应的键盘事件
  返回值:
  Copy:拷贝
  Paste:粘贴
  Cut:剪切

15.鼠标间隔移动距离
  Event.current.delta

16.是否是功能键输入
  Event.current.functionKey
  功能键指小键盘中的 方向键, page up, page down, backspace等等

17.小键盘是否开启
  Event.current.numeric

18.避免组合键冲突
  Event.current.Use()
  在处理完对应输入事件后,调用该方法,可以阻止事件继续派发,放置和Unity其他编辑器事件逻辑冲突

知识点三 更多内容

Unity - Scripting API: Event (unity3d.com)

第6节: Inspector窗口拓展

Inspector窗口拓展 基础知识

知识点一 Inspector窗口自定义显示指什么?

我可以完全自定义某一个Mono脚本在Inspector的组件窗口的相关显示

知识点二 SerializedObject和SerializedProperty的作用

 SerializedObject 和 SerializedProperty
 主要用于在 Unity 编辑器中操作和修改序列化对象的属性。
 它们通常在自定义编辑器中使用,以创建更灵活、可定制的属性面板

 我们只需要记住简单的规则

  • SerializedObject 代表脚本对象

  •  SerializedProperty 代表脚本对象中的属性

public class MyScript : MonoBehaviour
{
    public int intValue;
    public float floatValue;
    public string stringValue;
}


[CustomEditor(typeof(MyScript))]
public class MyScriptEditor : Editor
{
    SerializedObject serializedObj;
    SerializedProperty intProp;
    SerializedProperty floatProp;
    SerializedProperty stringProp;

    void OnEnable()
    {
        // 初始化 SerializedObject
        serializedObj = new SerializedObject(target);

        // 获取 SerializedProperty 的属性们
        intProp = serializedObj.FindProperty("intValue");
        floatProp = serializedObj.FindProperty("floatValue");
        stringProp = serializedObj.FindProperty("stringValue");
    }

    public override void OnInspectorGUI()
    {
        serializedObj.Update();  // 开始属性检查更改

        // 使用 PropertyField 创建一个自定义的界面,允许用户编辑属性
        EditorGUILayout.PropertyField(intProp, new GUIContent("Integer Value"));
        EditorGUILayout.PropertyField(floatProp, new GUIContent("Float Value"));
        EditorGUILayout.PropertyField(stringProp, new GUIContent("String Value"));

        serializedObj.ApplyModifiedProperties();  // 应用属性修改到实际的游戏对象

        // 可以添加更多的编辑器控件和逻辑
    }
}

SerializedObject: SerializedObject - Unity 脚本 AP

SerializedProperty: SerializedProperty - Unity 脚本 API

 知识点三 自定义 脚本在Inspector窗口中显示的内容

 关键步骤:
 1.单独为某一个脚本实现一个自定义脚本,并且脚本需要继承Editor
   一般该脚本命名为 自定义脚本名 + Editor

2.在该脚本前加上特性
   命名空间:UnityEditor
   特性名:CustomEditor(想要自定义脚本类名的Type)

3.声明对应SerializedProperty序列化属性 对象
   主要通过它和自定义脚本中的成员进行关联
   可以利用继承Editor后的成员serializedObject中的FindProperty("成员变量名")方法关联对应成员;
   比如:SerializedProperty mySerializedProperty;
         mySerializedProperty = serializedObject.FindProperty("自定义脚本中的成员名");
   一般在OnEnable函数中初始化

4.重写OnInspectorGUI函数
   该函数控制了Inspector窗口中显示的内容
   只需要在其中重写内容便可以自定义窗口
   注意:其中的逻辑需要包裹在这两句代码之间:

serializedObject.Update();
//之间应用属性修改
serializedObject.ApplyModifiedProperties();


 

知识点四 获取脚本依附的对象

 Editor中的target成员变量即可获取。

总结

 我们为继承Editor的脚本 

添加[CustomEditor(typeof(想要自定义Inspector窗口的脚本))]特性

 在该脚本中按照一定的规则进行编写

 便可为Inspector窗口中的某个脚本自定义窗口布局
 

数组、List属性 自定义显示

知识点一 数组、List属性在Inspector窗口显示 基础方式

主要知识点:

EditorGUILayout.PropertyField(SerializedProperty对象, 标题名);

该API会按照属性类型自动去处理控件绘制的逻辑

知识点二 数组、List属性在Inspector窗口显示 自定义方式

如果我们不想要Unity默认的绘制方式去显示 数组、List相关内容

我们也可以完全自定义布局方式

我们可以利用SerializedProperty中数组相关的API来完成自定义:

  1. arraySize 获取数组或List容量

  2. InsertArrayElementAtIndex(索引) 为数组在指定索引插入默认元素(容量会变化)

  3. DeleteArrayElementAtIndex(索引) 为数组在指定索引删除元素(容量会变化)

  4. .GetArrayElementAtIndex(索引) 获取数组中指定索引位置的 SerializedProperty 对象

自定义属性 自定义显示

知识点一 回顾 自定义属性 在Inspector窗口显示 基础方式

EditorGUILayout.PropertyField(SerializedProperty对象, 标题);

该API会按照属性类型自己去自动处理控件绘制的逻辑

但是在很多情况下,我们可能会希望自己去处理有关显示逻辑。

知识点二 自定义属性 在Inspector窗口显示 自定义方式

如果我们不想要Unity默认的绘制方式去显示 自定义数据结构类 相关内容

我们也可以完全自定义布局方式

主要知识点:

  1. SerializedProperty.FindPropertyRelative(属性)//通过当前SerializedProperty根属性拿到其身上的子属性

  2. serializedObject.FindProperty(属性.子属性)//直接获取某个属性身上的子属性,也开源直接拿来获取根属性

举例代码:

/*第一种*/
// 先获取根属性
SerializedProperty myCustom = serializedObject.FindProperty("myCustom");

// 从根属性中获取子属性
SerializedProperty myCustomI = myCustom.FindPropertyRelative("i");

/*第二种*/
// 或者直接使用属性路径获取子属性
SerializedProperty myCustomI_Direct = serializedObject.FindProperty("myCustom.i");

/*自定义UI显示*/
// 使用EditorGUILayout.IntField来自定义属性UI的显示
myCustomI.intValue = EditorGUILayout.IntField("自定义属性中的I", myCustomI.intValue);

字典属性 自定义显示

知识回顾 SerizlizeField特性

让私有字段可以被序列化(能够在Unity的Inspector窗口被看到)

知识点一 如何在Inspector窗口编辑字典成员

Unity默认是不支持Dictionary在Inspector窗口被显示的

我们只有利用两个List(或数组)成员来间接设置Dictionary

知识点二 ISerializationCallbackReceiver接口

该接口是Unity提供的用于序列化和反序列化时执行自定义逻辑的接口

实现该接口的类能够在对象被序列化到磁盘或从磁盘反序列化时执行一些额外代码

接口中函数:

  • OnBeforeSerialize: 在对象被序列化之前调用

  • OnAfterDeserialize: 在对象从磁盘反序列化后调用

由于我们需要用两个List存储Dictionary的具体值

相当于字典中的真正内容是存储在两个List中的

所以我们需要在

  1. OnBeforeSerialize序列化之前:将Dictionary里的数据存入List中进行序列化

  2. OnAfterDeserialize反序列化之后:将List中反序列化出来的数据存储到Dictionary中

知识点三 利用两个List在Inspector窗口中自定义Dictionary显示

由于我们在Inspector窗口中显示的信息的数据来源是List

因此我们只需要利用List在Inspector窗口中自定义显示即可

演示代码

字典对象脚本

public class TheDic: MonoBehaviour, ISerializationCallbackReceiver
{


    public Dictionary<int, string> myDic = new Dictionary<int, string>() { { 1,"123"},{ 2,"234"} };

    [SerializeField]
    private List<int> keys = new List<int>();
    [SerializeField]
    private List<string> values = new List<string>();

    public void OnAfterDeserialize()
    {
        myDic.Clear();
        for (int i = 0; i < keys.Count; i++)
        {
            if (!myDic.ContainsKey(keys[i]))
                myDic.Add(keys[i], values[i]);
            else
                Debug.LogWarning("字典Dictionary容器中不允许有相同的键");
        }
    }

    public void OnBeforeSerialize()
    {
        keys.Clear();
        values.Clear();
        foreach (var item in myDic)
        {
            keys.Add(item.Key);
            values.Add(item.Value);
        }
    }


}

自定义面板编辑器对象脚本

//通过这个特性,我们就可以为TheDic脚本自定义Inspector窗口中的显示了
[CustomEditor(typeof(GridManger))]
public class TheDicEditor: Editor
{
    private SerializedProperty keys;
    private SerializedProperty values;
    private int dicCount;
    private int count;

    private void OnEnable()
    {
  

        keys = serializedObject.FindProperty("keys");
        values = serializedObject.FindProperty("values");

        //初始化当前容量 否则 每次一开始都是0
        dicCount = keys.arraySize;
    }

    //  该函数控制了Inspector窗口中显示的内容
    //  只需要在其中重写内容便可以自定义窗口
    public override void OnInspectorGUI()
    {
    

        dicCount = EditorGUILayout.IntField("字典容量", dicCount);
        //容量变少时 把多的删了
        for (int i = keys.arraySize - 1; i >= dicCount; i--)
        {
            keys.DeleteArrayElementAtIndex(i);
            values.DeleteArrayElementAtIndex(i);
        }

        for (int i = 0; i < dicCount; i++)
        {
            //如果容量不够 扩容
            if(keys.arraySize <= i)
            {
                keys.InsertArrayElementAtIndex(i);
                values.InsertArrayElementAtIndex(i);
            }
            //去真正的自定义键值对的修改
            SerializedProperty indexKey = keys.GetArrayElementAtIndex(i);
            SerializedProperty indexValue = values.GetArrayElementAtIndex(i);
            EditorGUILayout.BeginHorizontal();
            indexKey.intValue = EditorGUILayout.IntField("字典的键", indexKey.intValue);
            indexValue.stringValue = EditorGUILayout.TextField("字典的值", indexValue.stringValue);
            EditorGUILayout.EndHorizontal();
        }

        serializedObject.ApplyModifiedProperties();
    }
}

总结

由于Unity中不支持在Inspector窗口直接使用Dictionary

因此我们可以利用两个List(或数组)来间接的表达Dictionary成员

第7节: Scene窗口拓展

Handles类是什么及响应函数

知识点一 Handles公共类的作用

Handles类提供了很多API

让我们可以在Scene窗口中绘制我们的自定义内容

它和GUI、EditorGUI类似,只不过它专门提供给Scene窗口使用

想要在Scene窗口中显示自定义内容

我们需要在对应的响应函数中进行处理

知识点二 Scene窗口更新响应函数

关键点:

前两个步骤 和自定义Inspector窗口显示内容 一致

1.单独为某一个脚本实现一个自定义脚本,并且脚本需要继承Editor

一般该脚本命名为 自定义脚本名 + Editor

2.在该脚本前加上特性

命名空间:UnityEditor

特性名:CustomEditor(想要自定义脚本类名的Type)

3.在该脚本中实现void OnSceneGUI()方法

该方法会在我们选中挂载自定义脚本的对象时自动更新

注意:只有选中时才会执行,没有选中不执行

知识点三 自定义窗口中监听Scene窗口更新响应函数

可以在自定义窗口显示时

监听更新事件

SceneView.duringSceneGui += 事件函数

窗口隐藏或销毁时移除事件

SceneView.duringSceneGui -= 事件函数

它允许你附加自定义GUI元素和交互到所有的Scene视图,而不局限于特定对象的编辑器。这对于添加全局性的编辑功能特别有用,例如,工具栏或全局视图选项。

你可以订阅这个事件,并在任何Scene视图中绘制GUI,而不需要选择任何特定的游戏对象。这提供了一种更灵活的方式来扩展Unity编辑器的功能。

总结

Scene窗口拓展功能

主要是提供给自定义脚本和自定义窗口的

我们采用对应的规则进行处理

便可以在之后的教程中利用场景更新响应函数来自定义一些Scene窗口的显示内容

Handle类控件API

文本、线段、虚线控件

知识点回顾 Editor中的target成员

我们可以利用继承Editor基类中的target成员获取到拓展的组件对象

Handles中的颜色控制

在每次调用Handles中的绘制API之前 设置颜色即可让这次绘制变成对应颜色。

Handles.color = new Color(0, 1, 1, 0.3f);

文本控件

Handles.Label(显示位置, 文本内容);

线段控件

Handles.DrawLine(起点, 终点, 粗细);

虚线控件

Handles.DrawDottedLine(起点, 终点, 粗细);

弧线、圆、立方体,几何体

弧线(圆弧)

绘制线框弧线

Handles.DrawWireArc(圆心, 法线, 绘制朝向, 角度, 半径);

绘制填充弧线

Handles.DrawSolidArc(圆心, 法线, 绘制朝向, 角度, 半径);

绘制填充圆

Handles.DrawSolidDisc(圆心, 法线, 半径);

绘制线框圆

Handles.DrawWireDisc(圆心, 法线, 半径);

立方体线框

Handles.DrawWireCube(中心点, xyz大小);

几何体

这个API在Unity中绘制凸多边形时,会自动将提供的顶点数组中的点按顺序通过线段连接起来,形成多边形的边。此外,它还会自动将最后一个点与第一个点连接起来,闭合这个多边形。

Handles.DrawAAConvexPolygon(几何体各顶点);

移动、旋转、缩放

知识点一 Handles中的移动轴

可以在场景内自定义生成移动轴

Vector3 Handles.DoPositionHandle(位置, 角度);

Vector3 Handles.PositionHandle(位置, 角度);

知识点二 Handles中的旋转轴

可以在场景内自定义生成选择轴

Quaternion Handles.DoRotationHandle(角度, 位置);

Quaternion Handles.RotationHandle(角度, 位置);

知识点三 Handles中的缩放轴

Vector3 Handles.DoScaleHandle(缩放, 位置, 角度, HandleUtility.GetHandleSize(位置));

Vector3 Handles.ScaleHandle(缩放, 位置, 角度, HandleUtility.GetHandleSize(位置));

HandleUtility.GetHandleSize方法的作用是

获取给定位置的操纵器控制柄的世界空间大小

使用当前相机计算合适的大小

它决定了控制柄的缩放大小

自由移动、自由旋转

知识回顾

HandleUtility.GetHandleSize

用于获取在 Scene 窗口中的一个单位距离所对应的屏幕空间大小

这个方法主要用于根据物体的距离来动态调整控制手柄的大小

使其在不同距离下能够在视图中显示合适的大小

一般我们把对象位置传递进去,他会自动得到一个句柄大小

知识点一 Handles中的自由移动

一个不受约束的移动控制柄

这个把手可以在所有方向上自由移动

Vector3 Handles.FreeMoveHandle(位置, 句柄大小, 移动步进值(按住ctrl键时会按该单位移动), 渲染控制手柄的回调函数);

句柄大小一般配合HandleUtility.GetHandleSize函数使用

渲染控制手柄的常用回调函数:

  • Handles.RectangleHandleCap:一个矩形形状的控制手柄,通常用于表示一个平面的控制面

  • Handles.CircleHandleCap:一个圆形的控制手柄,通常用于表示一个球体的控制面

  • Handles.ArrowHandleCap:一个箭头形状的控制手柄,通常用于表示方向

知识点二 Handles中的自由旋转

性质同上,只不过换成了旋转。

Quaternion Handles.FreeRotateHandle(角度, 位置, 句柄大小);

显示GUI

知识点一 Scene中显示GUI
Handles.BeginGUI();
//GUI相关代码
Handles.EndGUI();

注意:不这么写会导致UI渲染在世界坐标系而不是屏幕坐标系,导致看起来不对劲

知识点二 获取Scene窗口大小

获取当前Scene窗口信息

SceneView.currentDrawingSceneView

它继承自EditorWindow,因此通过position就能得到它的大小

知识点三 Handles更多内容

Unity - Scripting API: Handles (unity3d.com)

HandleUtility公共类

知识回顾 获取窗口上鼠标位置

Event.current.mousePosition

知识点一 HandleUtility公共类的主要作用

HandleUtility是 Unity 中的一个工具类

用于处理场景中的编辑器句柄(Handles)以及其他一些与编辑器交互相关的功能

它提供了一系列静态方法,用于处理编辑器中的鼠标交互、坐标转换以及其他与Handles相关的功能

知识点二 HandleUtility类中的常用API

1.GetHandleSize(Vector3 position)

我们之前已经使用过的API

获取在场景中给定位置的句柄的合适尺寸

个方法通常用于根据场景中对象的距离来调整句柄的大小,以便在不同的缩放级别下保持合适的显示大小

2.WorldToGUIPoint(Vector3 worldPosition)

将世界坐标转换为 GUI 坐标

这个方法通常用于将场景中的某个点的位置转换为屏幕上的像素坐标

以便在 GUI 中绘制相关的信息

3.GUIPointToWorldRay(Vector2 position)

将屏幕上的像素坐标转换为射线

这个方法通常用于从屏幕坐标中获取一条射线,用于检测场景中的物体或进行射线投射

4.DistanceToLine(Vector3 lineStart, Vector3 lineEnd)

计算场景中一条线段与鼠标光标的最短距离

可以用来制作悬停变色等功能

5.PickGameObject(Vector2 position, bool isSelecting)

在编辑器中进行对象的拾取

这个方法通常用于根据鼠标光标位置获取场景中的对象,以实现对象的选择或交互操作

知识点三 更多内容

Unity - Scripting API: HandleUtility (unity3d.com)

Gizmos类是什么及响应函数

知识点一 Gizmos类是用来做什么的?

Gizmos和Handles一样

是用来让我们拓展Scene窗口的

而Gizmos相对Handles来说

它主要专注于绘制辅助线、图标、形状等

而Handles主要用来绘制编辑器控制手柄等

知识点二 Gizmos响应函数

在继承MonoBehaviour的脚本中实现以下函数

便可以在其中使用Gizmos来进行图形图像的绘制

1.OnDrawGizmos() 在每帧调用,绘制的内容随时可以在Scene窗口中看见

2.OnDrawGizmosSelected() 仅当脚本依附的GameObject被选中时才会每帧调用绘制相关内容

它们的执行类似生命周期函数,Unity会帮助我们自动执行

补充:差异详解

Unity 中的 Gizmos 和 Handles 都提供了绘制线条和其他形状的功能,它们在某些方面功能重叠,但各自的使用场景和目的有所不同。了解这两者的区别可以帮助你更好地决定在不同情况下应该使用哪一个。

Gizmos

Gizmos 主要用于在开发过程中在场景视图中绘制辅助图形,帮助开发者直观地看到游戏对象的一些非物理属性,如碰撞体的边界、光线投射、路径点等。Gizmos 绘制的内容只在编辑器中可见,不会出现在游戏的最终运行版本中。

用途:主要用于调试和可视化游戏组件的辅助信息。

特点:

在 OnDrawGizmos 和 OnDrawGizmosSelected 方法中使用。

不需要特别的用户交互。

可以快速地为游戏对象绘制例如边框、方向线等。

Handles

Handles 则更多地用于创建自定义的编辑器交互。与 Gizmos 不同,Handles 不仅允许绘制图形,还允许开发者通过这些图形与场景中的对象进行交互,如拖拽来调整对象的位置、旋转或者缩放等。

用途:主要用于自定义编辑器工具中,为对象操作提供可视化的控制手柄。

特点:

在自定义的 Editor 类中通过 OnSceneGUI 方法使用。

允许用户交互,如拖拽和点击。

适用于需要更细致控制场景对象属性的场景。

总结

功能重叠:绘制线条

确实,两者都可以用来在场景中绘制线条或者形状,例如:

使用 Gizmos.DrawLine 在两点之间绘制直线。

使用 Handles.DrawLine 实现相同的效果,但通常用在更交互性的环境中。

选择使用哪一个?

如果你只需要在编辑器中可视化某些信息,而这些信息不需要与用户的直接交互,那么使用 Gizmos 是更简单直接的方法。

如果你需要在自定义编辑器中提供与开发者的交互,如拖拽调整大小、位置或其他属性,那么使用 Handles 更合适。毕竟Handle的功能只有选中的时候才会执行。

通过这种方式,Unity 提供了两套工具,一套更偏向于视觉调试辅助,另一套则提供了强大的编辑器交互能力。

Gizmos类API

颜色、立方体、视锥、跟随旋转

知识点一 Gizmos修改颜色

类似Handle.color,使用后后续绘制Api会使用这个颜色

Gizmos.color = Color.green;

知识点二 Gizmos绘制立方体

Gizmos.DrawCube(中心点, 大小);

Gizmos.DrawWireCube(中心点, 大小);

知识点三 Gizmos绘制视锥

Gizmos.DrawFrustum(绘制中心, FOV(Field of View,视野)角度, 远裁切平面, 近裁切平面, 屏幕长宽比);

知识点四 如何改变绘制内容的角度

Gizmos类提供了一个非常有用的属性——Gizmos.matrix,它用于定义一个变换矩阵,这个矩阵会影响所有后续的Gizmos绘制操作。通过设置这个矩阵,你可以在绘制前修改Gizmos的位置、旋转和缩放,这样所有的Gizmos绘图命令都会在这个矩阵的上下文中进行变换。

相当于比起handle的直接设置,Gizmos拥有了基于矩阵绘制的功能,可以实现切换坐标系绘制等灵活的用法。

可以理解为,我们前面设置的中心点和大小等参数,都是基于该矩阵的局部坐标系。

修改Gizmos绘制前的矩阵

Gizmos.matrix = Matrix4x4.TRS(位置, 角度, 缩放);

当多次绘制的时候,可以还原矩阵防止造成别的绘制的干扰。

还原矩阵

Gizmos.matrix = Matrix4x4.identity

贴图、图标

知识点一 Gizmos绘制贴图

贴图会被绘制到屏幕坐标系

Gizmos.DrawGUITexture(new Rect(x, y, w, h), 图片信息);

知识点二 Gizmos绘制图标

图标需要放置在固定文件夹中

Assets/Gizmos/中

图标会被绘制到场景内,类似Unity摄像机图标那类效果。

Gizmos.DrawIcon(Vector3坐标, "图标名");

线段、网格、射线

知识点一 Gizmos绘制线段

Gizmos.DrawLine(起点, 终点);

知识点二 Gizmos绘制网格

Gizmos.DrawMesh(mesh, 位置, 角度);

知识点三 Gizmos绘制射线

Gizmos.DrawRay(起点, 方向);

球体、网格线

知识点一 Gizmos绘制球体

Gizmos.DrawSphere(中心点, 半径);

Gizmos.DrawWireSphere(中心点, 半径);

知识点二 Gizmos绘制网格线

Gizmos.DrawWireMesh(mesh, 位置, 角度);

知识点三 更多Gizmos相关

Unity - Scripting API: Gizmos (unity3d.com)

第8节: EditorUtility公共类

EditorUtility是什么

知识点一 EditorUtility公共类是用来做什么的?

它是 Unity 编辑器中的一个实用工具类

提供了一系列用于编辑器脚本和自定义编辑器的实用功能

知识点二 在哪里使用EditorUtility公共类中的相关内容

在编辑器相关处都可以使用EditorUtility公共类中的相关内容

它主要提供的是一些辅助功能,可以在编辑器拓展开发的任意地方使用

但一定注意,它属于编辑器功能,无法被打包出去,只能在Unity编辑器中使用

编辑器默认窗口相关

知识点一 显示提示窗口

EditorUtility.DisplayDialog("标题", "显示信息", "确定键名");

演示代码:

 private void OnGUI()
{
if(EditorUtility.DisplayDialog("测试窗口", "确定一定要做这件事情吗", "一定要做"))
{
    Debug.Log("确定要做,在这里去处理逻辑");
}
else
{
    Debug.Log("点击了叉叉,不去做");
}
}

注意:窗口显示会阻塞逻辑

知识点二 显示三键提示面板

int EditorUtility.DisplayDialogComplex("标题", "显示信息", "按钮1名字", "按钮3名字", "按钮2名字");

返回值 0-按钮1按下 1-按钮3按下 2-按钮2按下

演示代码:

private void OnGUI()
{
if(GUILayout.Button("显示三键提示窗口"))
{
    int result = EditorUtility.DisplayDialogComplex("三键提示", "显示信息", "选项1", "关闭", "选项2");
    switch (result)
    {
        case 0:
            Debug.Log("选项1按下了");
            break;
        case 1:
            Debug.Log("关闭键按下了");
            break;
        case 2:
            Debug.Log("选项2按下了");
            break;
        default:
            break;
    }
}

注意:窗口显示会阻塞逻辑

知识点三 进度条相关

显示进度条

EditorUtility.DisplayProgressBar("进度条", "显示信息", 进制值0~1);

关闭进度条

EditorUtility.ClearProgressBar();

样式就是我们编译代码时弹出来的样子。

进度和逻辑需要开发者自己控制

注意:进度条窗口不会卡逻辑,但是需要配合关闭进度条使用

文件面板相关

知识点一 显示 文件 存储面板

通常用于在编辑器中保存新创建的文件或选择文件的保存路径

string path = EditorUtility.SaveFilePanel("窗口标题", "打开的目录", "保存的文件的名称", "文后缀格式")

知识点二 显示 文件 存储面板(默认指定在Asset文件夹中)

与 SaveFilePanel 类似,但是它将保存路径限制在项目目录中,只允许用户选择项目内的文件夹作为保存路径

string path = EditorUtility.SaveFilePanelInProject("窗口标题", "保存的文件的名称", "后缀格式", "在对话框窗口中显示的文本摘要");

知识点三 显示 文件夹 存储面板

通常用于在编辑器中选择文件夹作为保存路径,用于保存文件或执行其他与文件夹相关的操作

string path = EditorUtility.SaveFolderPanel("窗口标题", "文件夹", "默认名称");

知识点四 显示打开 文件 面板

通常用于在编辑器中选择文件进行打开或执行其他与文件相关的操作

string path = EditorUtility.OpenFilePanel("窗口标题", "文件路径", "后缀格式");

知识点五 显示打开 文件夹 面板

通常用于在编辑器中选择文件夹进行打开或执行其他与文件夹相关的操作

string path = EditorUtility.OpenFolderPanel("窗口标题", "文件夹", "默认名称");

其他内容

知识点一 压缩纹理

void EditorUtility.CompressTexture(Texture2D texture, TextureFormat format, TextureCompressionQuality quality);

可以将纹理显式压缩为指定的格式

该知识点会配合之后的资源导入相关知识点使用

知识点二 查找对象依赖项

object[] EditorUtility.CollectDependencies(Object[] roots);

返回对象所依赖的所有资源列表

知识点三 更多内容

Unity - Scripting API: EditorUtility (unity3d.com)

第9节: AssetDatabase公共类

AssetDatabase是什么

知识点一 AssetDatabase公共类是用来做什么的?

它是 Unity 引擎中的一个编辑器类

用于在编辑器环境中管理和操作项目中的资源(Assets)

它提供了一系列静态方法

使得开发者能够在编辑器脚本中进行资源的创建、拷贝、移动、删除等操作

知识点二 在哪里使用AssetDatabase公共类中的相关内容

在编辑器相关处都可以使用AssetDatabase公共类中的相关内容

它主要提供的是一些资源相关的辅助功能,可以在编辑器拓展开发的任意地方使用

但一定注意,它属于编辑器功能,无法被打包出去,只能在Unity编辑器中使用

常用API

知识点一 AssetDatabase中的常用API

1.创建资源,我们可以通过代码动态创建一些资源

路径从 Assets/...开始

AssetDatabase.CreateAsset(资源,路径);

注意:

不能在StreamingAssets中创建资源,

不能创建预设体(预设体创建之后会讲),

只能创建资源相关,例如材质球等

路径需要写后缀

2.创建文件夹,路径从 Assets/...开始

AssetDatabase.CreateFolder(父文件夹,新文件夹名)

3.拷贝资源,路径从 Assets/...开始

AssetDatabase.CopyAsset(源资源,目标路径)

注意:

需要写后缀名

4.移动资源,路径从 Assets/...开始

AssetDatabase.MoveAsset(老路径, 新路径);

5.删除资源,路径从 Assets/...开始

AssetDatabase.DeleteAsset(资源路径)

6.批量删除资源,路径从 Assets/...开始

AssetDatabase.DeleteAssets(string[] 路径们, List<string> 用于存储删除失败的路径)

7.获取资源路径 可以配合Selection选中资源一起使用

AssetDatabase.GetAssetPath(资源)

8.根据路径加载资源,路径从Assets/开始

AssetDatabase.LoadAssetAtPath(资源路径)

9.根据路径加载所有资源,路径从Assets/开始

AssetDatabase.LoadAllAssetsAtPath(资源路径);

一般可以用来加载图集资源,返回值为Object数据

如果是图集,第一个为图集本身,之后的便是图集中的所有Sprite

10.刷新,当对资源进行移动、导入、删除等操作后,需要执行刷新,目录才能马上看到改变

AssetDatabase.Refresh()

11.返回资源所属的AB包名,路径从Assets/开始

GetImplicitAssetBundleName(资源路径);

知识点二 更多内容

Unity - Scripting API: AssetDatabase (unity3d.com)

第10节: 其它更多内容

PrefabUtility公共类

知识点一 PrefabUtility公共类是什么

它是 Unity 编辑器中的一个公共类

提供了一些用于处理 Prefab(预制体或称预设体)的方法

主要功能包括 实例化预制体、创建预制体、修改预制体 等等

只要你有对预制体操作的相关需求

它可以在编辑器开发的任何地方对其进行使用

知识点二 常用API

1.动态创建预设体 路径从Assets/...开始

PrefabUtility.SaveAsPrefabAsset(GameObject对象, 路径);

2.加载预制体对象(不能用于创建,一般用于修改,会把预设体加载到内存中)

路径从Assets/...开始

PrefabUtility.LoadPrefabContents(路径)

释放加载的预设体对象

PrefabUtility.UnloadPrefabContents(GameObject对象)

注意:这两个方法需要配对使用,加载了就要写在

3.修改已有预设体

PrefabUtility.SavePrefabAsset(预设体对象, out bool 是否保存成功);

可以配合AssetDatabase.LoadAssetAtPath使用

4.实例化预设体

PrefabUtility.InstantiatePrefab(Object对象)

知识点三 更多内容

Unity - Scripting API: PrefabUtility (unity3d.com)

EditorApplication公共类

知识点一 EditorApplication公共类是用来干什么的?

它是 Unity 编辑器中的一个公共类,它主要提供了一些和编辑器本身相关的一些功能

比如 编辑器事件监听(播放、暂停等)、生命周期判断(是否运行中、暂停中、编译中)

编辑器进入播放模式、退出播放模式 等等功能

知识点二 常用API

1.监听编辑器事件

  • EditorApplication.update:每帧更新事件,可以用于在编辑器中执行一些逻辑

  • EditorApplication.hierarchyChanged:层级视图变化事件,当场景中的对象发生变化时触发

  • EditorApplication.projectChanged:项目变化事件,当项目中的资源发生变化时触发

  • EditorApplication.playModeStateChanged:编辑器播放状态变化时触发

  • EditorApplication.pauseStateChanged:编辑器暂停状态变化时触发

2.管理编辑器生命周期相关

  • EditorApplication.isPlaying:判断当前是否处于游戏运行状态。

  • EditorApplication.isPaused:判断当前游戏是否处于暂停状态。

  • EditorApplication.isCompiling:判断Unity编辑器是否正在编译代码

  • EditorApplication.isUpdating:判断Unity编辑器是否正在刷新AssetDatabase

3.获取Unity应用程序路径相关

  • EditorApplication.applicationContentsPath:Unity安装目录Data路径

  • EditorApplication.applicationPath:Unity安装目录可执行程序路径

4.常用方法

  • EditorApplication.Exit(0):退出Unity编辑器

  • EditorApplication.ExitPlaymode():退出播放模式,切换到编辑模式

  • EditorApplication.EnterPlaymode():进入播放模式

知识点三 更多内容

EditorApplication:Unity - Scripting API: EditorApplication (unity3d.com)

EditorSceneManager:Unity - Scripting API: EditorSceneManager (unity3d.com)

CompilationPipeline公共类

知识点一 CompilationPipeline公共类是用来做什么的

它是 Unity 编辑器中的一个公共类,用于处理代码编译相关的操作和事件的

对于我们来说,我们最常用的是利用它得知代码是否编译结束

比如动态生成脚本时,我们需要在编译结束后才能使用新的脚本

知识点二 常用内容

1.CompilationPipeline.assemblyCompilationFinished

命名空间:UnityEditor.Compilation;

主要作用:当一个程序集编译结束会主动调用该回调函数

传入的两个参数分别是

string arg1 : 编译完成的程序集名

CompilerMessage[] arg2 : 编译完成后产生的编译消息数组,包括编译警告和错误信息

2.CompilationPipeline.compilationFinished

命名空间:UnityEditor.Compilation;

主要作用:当所有程序集编译结束会主动调用该回调函数

参数

object obj: ActiveBuildStatus 活动生成状态对象

知识点三 更多内容

Unity - Scripting API: CompilationPipeline (unity3d.com)

AssetImporter和AssetPostprocessor

知识点一 说明

它们主要是用于处理

AssetImporter:资源导入批量设置(对导入的资源进行统一设置)

AssetPostprocessor:资源导入后处理(对导入的资源进行统一的预处理)

知识点二 AssetPostprocessor的作用

AssetPostprocessor(资源后处理器类)

它主要用于处理资源导入时的通用逻辑

我们可以通过继承该类

并实现其中的一些回调方法来自定义处理资源

我们

继承它后的常用回调方法有:

纹理相关

void OnPreprocessTexture()

导入纹理资源之前调用,允许修改纹理的导入设置

void OnPostprocessTexture(Texture2D texture)

导入纹理资源之后调用,允许你对导入后为其进行后处理,比如 修改纹理格式、尺寸、压缩等等

模型相关

void OnPreprocessModel()

导入模型资源之前调用,允许修改纹理的导入设置

void OnPostprocessModel(GameObject obj)

导入模型资源之后调用,允许你对导入后为其进行后处理,比如 修改网格、材质、动画等

音频相关

void OnPreprocessAudio()

导入音频资源之前调用,允许修改纹理的导入设置

void OnPostprocessAudio(AudioClip clip)

导入音频资源之后调用,允许你对导入后为其进行后处理,比如 修改音频格式、质量等

等等等等

更多内容:Unity - Scripting API: AssetPostprocessor (unity3d.com)

注意:

如果只想对某种资源中的某些内容进行处理

可以自己加命名规则一般会进行以下处理:

  1. 进行某种类型资源的通用设置

  2. 对某种类型资源进行统一批量的处理

继承它后的常用属性:

AssetImporter assetImporter:对应类型的资源导入器对象

string assetPath: 导入资源的路径

知识点三 AssetImporter的作用

AssetImporter(资源导入器 类)

它是 特定资源类型的资源导入程序的基类

它提供了一些方法和属性,用于配置和管理资源的导入设置

一般我们不会直接使用该类,而是通过使用继承它的子类来设置导入资源的相关信息

当我们导入一个资源时,在Inspector窗口中进行的相关设置

都是通过继承该类的子类实现的

它的子类一般按照资源类型来划分:

TextureImporter

用于导入纹理资源,并配置纹理的压缩格式、尺寸、平铺方式等设置

API说明:

Unity - Scripting API: TextureImporter (unity3d.com)

ModelImporter

用于导入模型资源,并配置模型的导入设置,如网格、材质、动画等

API说明:

Unity - Scripting API: ModelImporter (unity3d.com)

AudioImporter

用于导入音频资源,并配置音频的导入设置,如压缩格式、音频质量等

API说明:

Unity - Scripting API: AudioImporter (unity3d.com)

VideoClipImporter

用于导入视频资源,并配置视频的导入设置,如视频质量、循环模式等

API说明:

Unity - Scripting API: VideoClipImporter (unity3d.com)

ScriptedImporter

用于创建自定义的资源导入器,可以通过编写脚本来实现对特定类型资源的导入设置和处理逻辑

如果想要对某些特定格式的资源进行自定义配置处理,可以通过继承该类的方式去实现

API说明:

Unity - Scripting API: ScriptedImporter (unity3d.com)