Unity Editor扩展编辑器中显示脚本属性

Unity Editor扩展编辑器中显示脚本属性

背景

近期需要完成一个扩展编辑器中的功能,即在Scene视图中任意选择某GameObject,然后给这个GameObject动态添加指定脚本,难点是需要让脚本的属性也同时暴露出来,让我们可以随时修改其中公共属性,并序列化下来。

实现效果

test

如上图所示,具体展示的功能就是可以给场景中任意物体附加指定的脚本,并且显示脚本想要序列化的属性。(这里我其实想将指定的脚本也做成可以随时拖动替换的,奈何技术不够,只能先将要拖动的脚本写在代码里。)

总体结构

为了实现这个功能,需要有以下几个脚本:

  • ExposePropertyAttribute.cs:该脚本为特性申明类,注意该脚本不能放到Editor文件夹下
  • ExposeProperties.cs:该脚本为特性实现类,是这个功能实现的核心脚本,需要放到Editor文件夹下
  • MyType.cs:任意你需要显示修改属性的类
  • MyTypeEditor.cs:你要实现扩展编辑器脚本

以上脚本中,也可以清楚地看出后两个脚本是自定义的,核心是实现前两个脚本。

代码

  • ExposePropertyAttribute.cs
using System;

[AttributeUsage(AttributeTargets.Property)]
public class ExposePropertyAttribute : Attribute
{
}
  • ExposeProperties.cs
using UnityEditor;
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Reflection;

/*
    - Integer
    - Float
    - Boolean
    - String
    - Vector2
    - Vector3
    - Enum
    - UnityEngine.Object
    代码中支持以上几种形式的显示,还可以继续扩展
 */

public static class ExposeProperties
{
    public static void Expose(PropertyField[] properties)
    {

        GUILayoutOption[] emptyOptions = new GUILayoutOption[0];

        EditorGUILayout.BeginVertical(emptyOptions);

        foreach (PropertyField field in properties)
        {
            EditorGUILayout.BeginHorizontal(emptyOptions);
            switch (field.Type)
            {
                case SerializedPropertyType.Integer:
                    field.SetValue(EditorGUILayout.IntField(field.Name,(int)field.GetValue(),emptyOptions));
                    break;
                case SerializedPropertyType.Float:
                    field.SetValue(EditorGUILayout.FloatField(field.Name,(float)field.GetValue(),emptyOptions));
                    break;
                case SerializedPropertyType.Boolean:
                    field.SetValue(EditorGUILayout.Toggle(field.Name,(bool)field.GetValue(),emptyOptions));
                    break;
                case SerializedPropertyType.String:
                    field.SetValue(EditorGUILayout.TextField(field.Name,(String)field.GetValue(),emptyOptions));
                    break;
                case SerializedPropertyType.Vector2:
                    field.SetValue(EditorGUILayout.Vector2Field(field.Name,(Vector2)field.GetValue(),emptyOptions));
                    break;
                case SerializedPropertyType.Vector3:
                    field.SetValue(EditorGUILayout.Vector3Field(field.Name,(Vector3)field.GetValue(),emptyOptions));
                    break;
                case SerializedPropertyType.Enum:
                    field.SetValue(EditorGUILayout.EnumPopup(field.Name,(Enum)field.GetValue(),emptyOptions));
                    break;
                case SerializedPropertyType.ObjectReference:
                    field.SetValue(EditorGUILayout.ObjectField(field.Name,(UnityEngine.Object)field.GetValue(),field.GetPropertyType(),true,emptyOptions));
                    break;
                default:
                    break;
            }
            EditorGUILayout.EndHorizontal();
        }
        EditorGUILayout.EndVertical();
    }

    public static PropertyField[] GetProperties(System.Object obj)
    {
        List<PropertyField> fields = new List<PropertyField>();
        PropertyInfo[] infos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (PropertyInfo info in infos)
        {
            if (!(info.CanRead && info.CanWrite))
                continue;
            object[] attributes = info.GetCustomAttributes(true);
            bool isExposed = false;
            foreach (object o in attributes)
            {
                if (o.GetType() == typeof(ExposePropertyAttribute))
                {
                    isExposed = true;
                    break;
                }
            }
            if (!isExposed)
                continue;
            SerializedPropertyType type = SerializedPropertyType.Integer;
            if (PropertyField.GetPropertyType(info,out type))
            {
                PropertyField field = new PropertyField(obj,info,type);
                fields.Add(field);
            }
        }
        return fields.ToArray();
    }
}

public class PropertyField
{
    System.Object m_Instance;
    PropertyInfo m_Info;
    SerializedPropertyType m_Type;

    MethodInfo m_Getter;
    MethodInfo m_Setter;

    public SerializedPropertyType Type
    {
        get
        {
            return m_Type;
        }
    }
    public String Name
    {
        get
        {
            return ObjectNames.NicifyVariableName(m_Info.Name);
        }
    }
    public PropertyField(System.Object instance,PropertyInfo info,SerializedPropertyType type)
    {

        m_Instance = instance;
        m_Info = info;
        m_Type = type;

        m_Getter = m_Info.GetGetMethod();
        m_Setter = m_Info.GetSetMethod();
    }
    public System.Object GetValue()
    {
        return m_Getter.Invoke(m_Instance,null);
    }
    public void SetValue(System.Object value)
    {
        m_Setter.Invoke(m_Instance,new System.Object[] { value });
    }
    public Type GetPropertyType()
    {
        return m_Info.PropertyType;
    }
    public static bool GetPropertyType(PropertyInfo info,out SerializedPropertyType propertyType)
    {

        propertyType = SerializedPropertyType.Generic;

        Type type = info.PropertyType;

        if (type == typeof(int))
        {
            propertyType = SerializedPropertyType.Integer;
            return true;
        }

        if (type == typeof(float))
        {
            propertyType = SerializedPropertyType.Float;
            return true;
        }

        if (type == typeof(bool))
        {
            propertyType = SerializedPropertyType.Boolean;
            return true;
        }

        if (type == typeof(string))
        {
            propertyType = SerializedPropertyType.String;
            return true;
        }

        if (type == typeof(Vector2))
        {
            propertyType = SerializedPropertyType.Vector2;
            return true;
        }

        if (type == typeof(Vector3))
        {
            propertyType = SerializedPropertyType.Vector3;
            return true;
        }

        if (type.IsEnum)
        {
            propertyType = SerializedPropertyType.Enum;
            return true;
        }
        // COMMENT OUT to NOT expose custom objects/types
        propertyType = SerializedPropertyType.ObjectReference;
        return true;

        //return false;

    }
}
  • MyType.cs
using UnityEngine;

public class MyType : MonoBehaviour
{
    [HideInInspector] [SerializeField] int m_SomeInt;
    [HideInInspector] [SerializeField] float m_SomeFloat;
    [HideInInspector] [SerializeField] bool m_SomeBool;
    [HideInInspector] [SerializeField] string m_Etc;

    [ExposeProperty]
    public int SomeInt
    {
        get
        {
            return m_SomeInt;
        }
        set
        {
            m_SomeInt = value;
        }
    }

    [ExposeProperty]
    public float SomeFloat
    {
        get
        {
            return m_SomeFloat;
        }
        set
        {
            m_SomeFloat = value;
        }
    }

    [ExposeProperty]
    public bool SomeBool
    {
        get
        {
            return m_SomeBool;
        }
        set
        {
            m_SomeBool = value;
        }
    }

    [ExposeProperty]
    public string SomeString
    {
        get
        {
            return m_Etc;
        }
        set
        {
            m_Etc = value;
        }
    }
}
  • MyTypeEditor.cs
using UnityEditor;
using UnityEngine;
using System.Collections;

[CustomEditor(typeof(MyType))]
public class MyTypeEditor : EditorWindow
{
    private PropertyField[] _fields;

    [MenuItem("Tools/Test")]
    static void CreateWindow()
    {
        var window = GetWindow(typeof(MyTypeEditor),true);
        window.Show();
    }
    private void OnGUI()
    {
        EditorGUILayout.HelpBox("请在场景中选择任意物体",MessageType.Info);
        EditorGUILayout.LabelField("选中的物体:");
        
        foreach (var item in Selection.gameObjects)
        {
            EditorGUILayout.BeginVertical("Box");
            GUILayout.Label(item.name);
            var sp = item.GetComponent<MyType>();
            if (sp != null)
            {
                sp = (MyType)EditorGUILayout.ObjectField(sp,typeof(MyType),true);
                _fields = ExposeProperties.GetProperties(sp);
                ExposeProperties.Expose(_fields);

                EditorGUILayout.BeginHorizontal("HelpBox");
                if (GUILayout.Button("删除脚本"))
                {
                    DestroyImmediate(sp);
                }
                EditorGUILayout.EndHorizontal();
            }
            else
            {
                if (GUILayout.Button("添加脚本"))
                {
                    item.AddComponent<MyType>();
                }
            }
            EditorGUILayout.EndVertical();
        }

        EditorGUILayout.BeginHorizontal();
        if (GUILayout.Button("全部添加脚本"))
        {
            foreach (var item in Selection.gameObjects)
            {
                item.GetOrAddComponent<MyType>();
            }
        }
        if (GUILayout.Button("全部删除脚本"))
        {
            foreach (var item in Selection.gameObjects)
            {
                var sp = item.GetComponent<MyType>();
                if (item != null)
                {
                    DestroyImmediate(sp);
                }
            }
        }
        EditorGUILayout.EndHorizontal();
    }
    private void OnInspectorUpdate()
    {
        this.Repaint();
    }
}

OK,以上就是实现该功能的所有源码啦,都比较简单。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍了Unity游戏开发中外观模式是什么意思,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章主要介绍Unity中地面检测方案的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1.普通射线在角色坐标(一般是脚底)...
这篇文章主要介绍了Unity游戏开发中如何消除不想要的黄色警告,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带...
这篇文章主要介绍了Unity中有多少种渲染队列,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解
这篇文章主要介绍Unity中如何实现Texture,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!了解Texture2D 如上图,Texture2D是一张
小编给大家分享一下Unity中DOTS要实现的特点有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让...
这篇文章给大家分享的是有关unity中如何实现UGUI遮罩流光特效的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。下面是核心shader:Sh...
这篇文章主要为大家展示了“Unity中如何实现3D坐标转换UGUI坐标”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下...
这篇文章主要介绍了Unity游戏开发中设计模式的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章主要介绍了Unity中如何实现仿真丝袜渲染,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了...
这篇文章给大家分享的是有关Unity插件OVRLipSync有什么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。项目需要接入对话口型动...
这篇文章主要介绍了Unity性能优化之DrawCall的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家...
这篇文章给大家分享的是有关Unity给力插件之Final IK怎么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。这插件有什么用:一般游...
这篇文章给大家分享的是有关Unity中如何内嵌网页插件UniWebView的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、常见Unity中内...
小编给大家分享一下Unity如何做流体物理的几个轮子,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让...
小编给大家分享一下Unity中Lod和Occlusion Culling的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收...
这篇文章将为大家详细讲解有关Unity中LineRenderer与TrailRenderer有什么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获...
这篇文章主要介绍了Unity中coroutine问题的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起...
这篇文章将为大家详细讲解有关unity中spine怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。骨骼动画首先我们来看到...
这篇文章主要为大家展示了“Unity Shader后处理中如何实现简单均值模糊”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学...