LOADING

加载过慢请开启缓存 浏览器默认开启

Unity插件制作学习笔记

记录自己学习 unity 制作插件时的细节和 tips,有任何问题或疑惑请随时通过主页联系方式联系我一起探讨。



一、自定义 Inspector 界面

1、属性相关标识

5 种变量在 inspector 隐藏和显示的方法

using System;

public class Value : MonoBehaviour
{
    
    public int int1;  

    [HideInInspector]//直接隐藏在inspector  
    public int int2=3;  

    [NonSerialized]//隐藏的同时不会保存被inspector序列化后的值   
    public int int3=3;//即更改inspector的值不会影响脚本的初始赋值  

    [SerializeField]//将私有变量显示在inspector  
    private int int4;  

    public ValueTest valueTest = new ValueTest();  

}

[Serializable]//可让自定义的类显示在inspector  
public class ValueTest   
{
    public int int5;  
}

最终在 inspector 的表现效果如下:

image-20231025225218480

这里涉及到 unity 中 inspector 的数据持久化问题,即序列化,参考文档:脚本序列化 - Unity 手册

“序列化是将数据结构或对象状态转换为 Unity 可存储并在以后可重构的格式的自动过程。”

如果在自定义 inspector 的脚本中,没有使用派生子 editor 基类的方法 FindProperty () 来获取 target 中的变量时,是无法被序列化保存的,需要注意。

2、自定义界面属性

创建自定义界面需要在 Asset 路径下创建名为 Editor 的文件夹,此命名的文件夹打包时不会被打包(Resources 文件夹同理,在 load 方法加载资源时要注意路径(Resources-Load - Unity 脚本 API)

创建上述 Value 的自定义 inspector:

第一步:创建继承 Editor 基类的脚本,并将其放在 Editor 文件夹下,并声明是 Value 的界面

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Value))]
public class ValueEditor :Editor
{
  
    public SerializedObject mObj;   //定义序列化目标对象
    //序列化出来几个属性
    //属性对应的是[CustomEditor(typeof(Value))]中,Value类里面的属性

    public SerializedProperty int1;
    public SerializedProperty int2;
    public SerializedProperty int3;
    public SerializedProperty int4;
    public SerializedProperty valueTest;

    //选择当前游戏对象
    private void OnEnable()
    {
        //初始化
        this.mObj = new SerializedObject(target);
        //通过字符串名字,查找Test1类里面的属性,并进行初始化
        this.int1 = this.mObj.FindProperty("int1");
        this.int2 = this.mObj.FindProperty("int2");
        this.int3 = this.mObj.FindProperty("int3");
        this.int4 = this.mObj.FindProperty("int4");
        this.valueTest = this.mObj.FindProperty("valueTest");
    }

    //绘制
    public override void OnInspectorGUI()
    {
        this.mObj.Update();

        EditorGUILayout.PropertyField(this.int1);
        EditorGUILayout.PropertyField(this.int2);
        //EditorGUILayout.PropertyField(this.int3);
        //因为int3是不被序列化的,所以这里不能写此语句
        EditorGUILayout.PropertyField(this.int4);
        EditorGUILayout.PropertyField(this.valueTest, true);
        //true属性意思是设置显示子节点,也就是显示TypeDemo里面的属性

        this.mObj.ApplyModifiedProperties();
        //应用属性修改
    }
}

效果如图

image-20231025232755437

可以看到自定义的 Editor 界面不受类本身的属性标识控制。

3、自定义序列化属性字段

此时,我想要当前属性随着我的枚举值的变化而变化,即我选择一个枚举时,其他属性处于不被 editor 绘制的状态。

方法:

第一步,创建并书写对应类的脚本和编辑器脚本,这里分别命名为 EnumEditor 和 Enum。

using System;

public class Enum : MonoBehaviour
{
    public selectedEnum selectedEnum;
    public GameObject 碰撞盒;
    public GameObject 特效;
    public AnimationClip 动作;
}

[Serializable]
public enum selectedEnum
{ 
碰撞盒,
特效,
动作,
}

仅有 Enum 时

image-20231025234839268

EnumEditor:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(Enum))]
public class EnumEditor : Editor
{
    public SerializedObject mObj;
    public SerializedProperty selectedEnum;
    public SerializedProperty 碰撞盒;
    public SerializedProperty 特效;
    public SerializedProperty 动作;

    private void OnEnable()
    {
        //初始化
        this.mObj = new SerializedObject(target);
        this.selectedEnum = this.mObj.FindProperty("selectedEnum");
        this.碰撞盒 = this.mObj.FindProperty("碰撞盒");
        this.特效 = this.mObj.FindProperty("特效");
        this.动作 = this.mObj.FindProperty("动作");
    }

    public override void OnInspectorGUI()
    {
        this.mObj.Update();
        EditorGUILayout.PropertyField(this.selectedEnum);
        switch (this.selectedEnum.enumValueIndex)
        {
            case 0:
                EditorGUILayout.PropertyField(this.碰撞盒);
                break;
            case 1:
                EditorGUILayout.PropertyField(this.特效);
                break;
            case 2:
                EditorGUILayout.PropertyField(this.动作);
                break;
        }

        this.mObj.ApplyModifiedProperties();
    }
}

编写后

image-20231025235035035

4、自定义操作界面

当我们需要自定义操作界面时:

需要用到 EditorGUILayout 方法:EditorGUILayout - Unity 脚本 API

以下是常用的。

a、界面的启动和关闭

创建一个类,存放在 Assets–>Editor 里面,并继承自 EditorWindow

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
public class WindowsEditor : EditorWindow
{
    [MenuItem("Menu/EditorWindow")]
    //声明窗口路径

    public static void ShowWindow()
    {
        //CreateInstance会允许创建多个窗口,推荐使用系统自带的单例模式getwindows
        //使用官方提供的实例化窗口方法调用
        WindowsEditor.CreateInstance<WindowsEditor>().Show();

        //浮动型的窗口,跟点击Building Setting出现的窗口效果一样
        WindowsEditor.CreateInstance<WindowsEditor>().ShowUtility();

        //弹出窗口时的效果
        WindowsEditor.CreateInstance<WindowsEditor>().ShowPopup();
        //此方法和下面的OnGUI配合着使用,否则会出现页面关不掉的情况,此时不会出现UI自带的关闭界面

        //使用系统提供的单例模式,在unity面板中打开窗口时,只会出现一个(推荐)
        WindowsEditor.GetWindow<WindowsEditor>().Show();
    }

    public void OnGUI()
    {
        if (GUILayout.Button("关闭"))
        {
            this.Close();
        }
    }
    
}

每个方法效果如下

image-20231026000101143 image-20231026000129453 image-20231026000158455
Show ShowUtility ShowPopup

b、界面的事件相关

自定义页面中有时会使用自定义的事件,事件通常如下:

    public int index_1 = 0;
    //刷新方法,每秒钟100次
    public void Update()
    {
        index_1++;
        Debug.Log("index_1:" + index_1);
    }

    public int index_2 = 0;
    //刷新方法,刷新周期比Update小
    public void OnInspectorUpdate()
    {
        index_2++;
        Debug.Log("index_2:" + index_2);
    }

    //视图被删除的时候触发
    public void OnDestroy()
    {
        Debug.Log("OnDestroy:触发");
    }

    //选择一个对象的时候触发
    public void OnSelectionChange()
    {
        //获取选择Hierarchy里面游戏物体的时候的名字
        for (int i = 0; i < Selection.gameObjects.Length; i++)
        {
            Debug.Log("OnSelectionChange:触发" + Selection.gameObjects[i].name);
        }

        //获取选择Project里面的文件的名字
        for (int i = 0; i < Selection.objects.Length; i++)
        {
            Debug.Log("OnSelectionChange:触发" + Selection.objects[i].name);
        }
    }

    //获得焦点的时候触发
    public void OnFocus()
    {
        Debug.Log("OnFocus:触发");
    }

    //失去焦点的时候触发
    public void OnLostFocus()
    {
        Debug.Log("OnLostFocus:触发");
    }

    //Hierarchy更改的时候触发
    public void OnHierarchyChange()
    {
        Debug.Log("OnHierarchyChange:触发");
    }

    //Project更改的时候触发
    public void OnProjectChange()
    {
        Debug.Log("OnProjectChange:触发");
    }

c、文本 / 颜色字段


    public string 文本 = "默认文字";
    public Color 颜色 = Color.white;
    public void OnGUI()
    {
        if (GUILayout.Button("关闭"))
        {
            this.Close();
        }
        //一行文本,单行输入框
        this.文本 = EditorGUILayout.TextField(this.文本);
        //多行输入框
        this.文本 = EditorGUILayout.TextArea(this.文本);
        //密码输入框
        this.文本 = EditorGUILayout.PasswordField(this.文本);
        //颜色选择框
        this.颜色 = EditorGUILayout.ColorField(this.颜色);
    }

效果如下:

image-20231026001735821

d、标签字段

类似于 [header (“”)] 标识,在 gui 中显示 tag。

    public string 文本 = "默认文字";
    public Color 颜色 = Color.white;
	public void OnGUI()
    {
        //标签
        EditorGUILayout.PrefixLabel(this.文本);//只写这么一行代码,窗口里面不出现任何内容
        EditorGUILayout.Space();//换行

        //标签(下拉框)
        this.文本 = EditorGUILayout.TagField(this.文本);
        EditorGUILayout.Space();

        //标签(非可选择标签提示)
        EditorGUILayout.LabelField(this.文本);
        EditorGUILayout.Space();

        //标签(可选择标签提示)
        EditorGUILayout.SelectableLabel(this.文本);
        EditorGUILayout.Space();
    }

效果如下:

image-20231026002300584

e、输入字段

   public string 文本 = "默认文字";
    public int 数字;

    public void OnGUI()
    {
        EditorGUILayout.LabelField("输入整形数字");
        this.数字 = EditorGUILayout.IntField(this.数字);
        EditorGUILayout.LabelField("输入字符");
        this.文本 = EditorGUILayout.TextField(this.文本);
    }

效果如下:

image-20231026002601592

f、滑动条

    public int 数字;
    public float 浮点数;
    public float 浮点最大值;
    public float 浮点最小值;
    public void OnGUI()
    {
        //滑动条  参数1:默认值,参数2:最小值,参数3:最大值
        this.浮点数 = EditorGUILayout.Slider(this.浮点数, 0, 100);//取值区间有小数

        this.数字 = EditorGUILayout.IntSlider(this.数字, 0, 100);//取值区间为整数

        //最小值到最大值的取值范围,为0-100
        EditorGUILayout.MinMaxSlider(ref 浮点最小值, ref 浮点最大值, 0, 100);
    }

效果如下:

image-20231026003117435

g、Vector 输入字段

    public Vector3 mPos3;
    public void OnGUI()
    {
        this.mPos3 = EditorGUILayout.Vector3Field("三维坐标", this.mPos3);
        EditorGUILayout.Space();
    }

效果如下:

image-20231026003443557

h、选择

public int index;
public void OnGUI()
    {        
    	string[] strs = { "a", "b", "c", "d" ,"e"};
        int[] ints = { 1, 2, 3, 4, 5 };
        this.index = EditorGUILayout.Popup(this.index, strs);
        this.index = EditorGUILayout.IntPopup(this.index, strs,ints);
        EditorGUILayout.Space();
    }

image-20231026004155844

i、对象选择

    public string mTag;
    public int mLayer;
    public Object mObject;
    public void OnGUI()
    {
        //标签,下面两种写法,哪个都可以
        //this.mTag = EditorGUILayout.TagField(this.mTag);
        this.mTag = EditorGUILayout.TagField("Tag");
        EditorGUILayout.Space();
        //层
         this.mLayer = EditorGUILayout.LayerField(this.mLayer);
         EditorGUILayout.Space();
         //对象选择,如果想要存放所有的元素,那么参数就写为Object: typeof(Object)
        this.mObject = EditorGUILayout.ObjectField("对象选择器", this.mObject, typeof(Camera), true);
   }

参考文章:

Unity3D 插件开发教程 - 挽风入我怀 - 博客园 (cnblogs.com)

Unity 自定义 Inspector 面板时的数据持久化问题 - FancyBit - 博客园 (cnblogs.com)