目录

一、背景知识:

1、C#特性:

2、Unity中特殊目录:

3、*注意:

二、编辑器检视面板扩展属性

二、通过Editor脚本扩展组件(检视器外挂式开发)

三、检视器窗口开发

四、完成点击生成方块的工具


一、背景知识:

1、C#特性:

[System.Serializable]

2、Unity中特殊目录:

Resources:存储资源目录。

Plugins:需要跨语言条用的逻辑代码目录,三方插件,sdk等。

StreamingAssets:只读,存储更游戏包的资源目录,热更新资源目录。

Editor:编辑器目录,存放于编辑器相关的资源和逻辑代码。

3、*注意:

1、多目录是可以同时存在的。

2、在打包的时候不会生成到项目中

3、UnityEditor命名空间不要出现在游戏发布的代码中

二、编辑器检视面板扩展属性

//隐藏公共成员变量,防止Inspector的值影响到它
//同时保证脚本中变量的可访问度
[HideInInspector]

//私有变量,检视面板可见
//Unity会将对象进行序列化存储,所以即使是私有的,那么标记为可序列化后,就会显示,共有默认是可序列化的
[SerializeField]

//初始化 System.SerializableAttribute 类的新实例。
//表示类可以被序列化,此类不能被继承
[Serializable]

//给数值设定范围(最小min,最大max)
[Range(min, max)]

//添加变量悬浮提示文字
//一个成员变量可以添加多个特性
[Tooltip("这是一个提示")]

//指定输入框,拥有line行
[Multiline(int line)]

//默认显示5行,最多显示10行内容,再多用滚动条控制显示区域
[TextArea(5, 10)]

//给一个成员变量添加右键菜单
//第一个参数是菜单的名称
//第二个参数是右键点击的回调函数
[ContextMenuItem(string name, string function)]

//给小齿轮增加一个回调函数
[ContextMenu(string itemName)]

//使生命周期函数,在编辑器状态下可以执行,游戏中也可以正常使用
//Update()在场景中对象发生变化或项目组织发生变化时会在编辑器下执行
[ExecuteInEditMode]

//当前组件依赖于盒子碰撞体
//当前组件挂载在对象上时,被依赖组件会一起被添加上去
//当依赖组件没有被移除时,被依赖组件不能被删除
[RequireComponent(Type requiredComponent)]

//将组件添加到AddComponent下
//第一个参数:分类名/组件名
//第二个参数:列表中显示的顺序
[AddComponentMenu(string menuName,int order)]

//将组件添加到顶部菜单栏下
[MenuItem(string itemName)]

//将当前编辑器扩展脚本和需要扩展的组件,建立关联关系(当前脚本的功能就可以扩展到对应的组件上了)
[CustomEditor(Type inspectedType)]

二、通过Editor脚本扩展组件(检视器外挂式开发)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;  //步骤1:检视器的扩展开发属于编辑器范畴,所以使用 UnityEditor 命名空间

[CustomEditor(typeof(Player))]  //步骤3:将当前编辑器扩展脚本和需要扩展的组件,建立关联关系(当前脚本的功能就可以扩展到对应的组件上了)
public class PlayerEditor : Editor  //步骤2:继承Editor,使用声明周期函数及成员变量,参考 MonoBehavior
{
    //步骤5:选中或挂载时,需要获取被编辑的组件,只有获取到组件,才能对检视器的显示做处理
    private Player _Player;

    //步骤4:确定,什么时候显示内容在监视器面板中,即使用生命周期函数
    //关联组件被挂载在场景中时,或选中挂载对象时被调用
    private void OnEnable()
    {
        //步骤5:通过父类获取当前选中或挂载的脚本组件
        _Player = target as Player;
        Debug.Log("Enable");
    }

    //关联组件在场景中被移除时,或挂载对象失去焦点时被调用
    private void OnDisable()
    {
        Debug.Log("Disable");
    }

    //已经获得了需要扩展显示的组件,现在需要一种方式修改检视器内部的内容
    //重写OnInspectorGUI,内部可以实现重新绘制检视器内部的内容
    public override void OnInspectorGUI()
    {
        //所有API都是处理检视器
        //实现文字显示
        EditorGUILayout.LabelField("人物相关属性");

        //简单数据类型绘制//

        //使用绘制方法,重新绘制ID字段(获取原始ID,绘制后,再对原始ID赋值)
        _Player.ID = EditorGUILayout.IntField("玩家ID", _Player.ID);

        //文本
        _Player.Name = EditorGUILayout.TextField("玩家名称", _Player.Name);
        //浮点数
        _Player.Atk = EditorGUILayout.FloatField("玩家攻击力", _Player.Atk);
        //布尔
        _Player.isMan = EditorGUILayout.Toggle("是否为男性", _Player.isMan);
        //向量
        _Player.HeadDir = EditorGUILayout.Vector3Field("头部方向", _Player.HeadDir);
        //颜色
        _Player.Hair = EditorGUILayout.ColorField("头发颜色", _Player.Hair);

        对象数据类型绘制-绘制对象类型//
        //参数1:标题
        //参数2:原始组件的值
        //参数3:成员变量的类型
        //参数4:是否可以将场景中的对象拖给这个成员变量
        _Player.Weapon = EditorGUILayout.ObjectField("持有武器", _Player.Weapon, typeof(GameObject), true) as GameObject;
        //纹理
        _Player.Cloth = EditorGUILayout.ObjectField("衣服材质贴图", _Player.Cloth, typeof(Texture), false) as Texture;

        枚举数据类型绘制
        //整数转枚举
        //int id = 0;
        //PLAYER_PROFESSION p = (PLAYER_PROFESSION)id;

        //单选枚举(标题, 组件上的原始值)
        _Player.Profession = (PLAYER_PROFESSION)EditorGUILayout.EnumPopup("玩家职业", _Player.Profession);

        //多选枚举(标题, 组件上的原始值)
        _Player.Talent = (PLAYER_TALENT)EditorGUILayout.EnumFlagsField("玩家天赋", _Player.Talent);

        终极数据类型绘制
        //更新可序列化数据
        serializedObject.Update();
        //通过成员变量名找到组件上的成员变量
        SerializedProperty sp = serializedObject.FindProperty("Items");
        //可序列化数据绘制(取到的数据,标题,是否将所有获得的序列化数据显示出来)
        EditorGUILayout.PropertyField(sp, new GUIContent("道具信息"), true);
        //将修改的数据,写入到可序列化的原始数据中
        serializedObject.ApplyModifiedProperties();

        滑动条绘制
        //滑动条显示(1.标题,2.原始变量,最小值,最大值)
        _Player.Atk = EditorGUILayout.Slider(new GUIContent("玩家攻击力"), _Player.Atk, 0, 100);

        if(_Player.Atk > 80)
        {
            //显示消息框(红色)
            EditorGUILayout.HelpBox("攻击力太高了", MessageType.Error);
        }

        if(_Player.Atk < 20)
        {
            //显示消息框(黄色)
            EditorGUILayout.HelpBox("攻击力太低了", MessageType.Warning);
        }

        //按钮显示和元素排列
        //(按钮是否被按下)显示按钮(按钮名称)
        GUILayout.Button("来个按钮");
        GUILayout.Button("来个按钮");

        //根据按钮返回值,判定按钮是否被按下
        if(GUILayout.Button("测试"))
        {
            Debug.Log("测试");
        }

        //开始横向排列绘制
        EditorGUILayout.BeginHorizontal();

        if (GUILayout.Button("检查"))
        {
            Debug.Log("检查");
        }

        if (GUILayout.Button("排查"))
        {
            Debug.Log("排查");
        }

        //结束横向排列绘制
        EditorGUILayout.EndHorizontal();
    }
}

三、检视器窗口开发

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;  //使用编辑器命名空间

//继承EditorWindow,需要使用内部声明周期函数和成员变量
public class TestWindow : EditorWindow
{
    [MenuItem("工具/创建窗口")]
    static void OpenWin()
    {
        //创建窗口
        //泛型:窗口类对象类型
        //是否为工具窗口
        //窗口的标签名
        //创建窗口后,是否马上让窗口获得焦点
        TestWindow window = GetWindow<TestWindow>(false, "测试窗口", true);
        //给窗口最小尺寸赋值
        window.minSize = new Vector2(400, 300);
        //给窗口最大尺寸赋值
        window.maxSize = new Vector2(800, 600);
    }

    //开启窗口时,被调用
    private void OnEnable()
    {
        Debug.Log("窗口Enable");
    }

    //窗口关闭时,被调用
    private void OnDisable()
    {
        Debug.Log("窗口Disable");
    }

    //只要窗口存在时,Update每帧都会被调用
    public void Update()
    {
        //Debug.Log("窗口Update");
    }

    //场景内部结构有变化时,回调生命周期函数(变一次调一次)
    private void OnHierarchyChange()
    {
        Debug.Log("窗口Hierarchy");
    }

    //项目有变化时,回调生命周期函数(变一次调一次)
    private void OnProjectChange()
    {
        Debug.Log("窗口Project");
    }

    //当选中物体发生变化时调用
    private void OnSelectionChange()
    {
        //获得当前选中的GameObject名称
        Debug.Log("窗口SelectionChange: " + Selection.activeGameObject.name);
    }

    //窗口开启时,每帧会调用 OnGUI 绘制窗口内容
    private void OnGUI()
    {
        if(GUILayout.Button("测试"))
        {
            ShowNotification(new GUIContent("测试窗口按钮"));
        }
    }
}

四、完成点击生成方块的工具

1、Editor类:

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

public class NodeWindow : EditorWindow
{
    static NodeWindow window;

    static GameObject nodeManager;

    public static void OpenWindow(GameObject manager)
    {
        nodeManager = manager;
        //真正开启了一个窗口
        window = EditorWindow.GetWindow<NodeWindow>();
    }

    void Update()
    {
        //通过窗口的Update,每帧执行一次,当前被选中的对象为板子
        Selection.activeGameObject = nodeManager;
    }

    public static void CloseWindow()
    {
        window.Close();
    }
}

//外挂式关联NodeManager
[CustomEditor(typeof(NodeManager))]
public class NodeManagerEditor : Editor
{

    NodeManager manager;

    bool isEditor = false;//是否是编辑的状态

    //当选中带有NodeManager组件对象的时候,获得组件
    void OnEnable()
    {
        manager = (NodeManager)target;
        Debug.Log("manager:" + manager);
    }

    //绘制组件的生命周期函数
    public override void OnInspectorGUI()
    {
        //通过终极的数据获取方法,显示列表中的数据
        serializedObject.Update();
        SerializedProperty nodes = serializedObject.FindProperty("nodes");
        EditorGUILayout.PropertyField(nodes, new GUIContent("路径"), true);
        serializedObject.ApplyModifiedProperties();

        //开始编辑的开关
        if (!isEditor && GUILayout.Button("开始编辑"))
        {
            NodeWindow.OpenWindow(manager.gameObject);//调用打开界面的方法
            isEditor = true;//改变状态变成编辑模式
        }
        //结束编辑的开关
        else if (isEditor && GUILayout.Button("结束编辑"))
        {
            NodeWindow.CloseWindow();//调用关闭界面的方法
            isEditor = false;//改变状态变成非编辑模式
        }

        //删除按钮
        if (GUILayout.Button("删除最后一个节点"))
        {
            RemoveAtLast();
        }
        //删除所有按钮
        else if (GUILayout.Button("删除所有节点"))
        {
            RemoveAll();
        }
    }

    RaycastHit hit;
   
    //有点类似前期Update函数,发送射线
    //当选中关联的脚本挂载的物体
    //当鼠标在Scene视图下发生变化时,执行该方法,比如鼠标移动,比如鼠标的点击
    void OnSceneGUI()
    {

        if (!isEditor)//非编辑状态下不能生成路点
        {
            return;
        }

        //当鼠标按下左键时发射一条射线 
        //非运行时,使用Event类
        //Event.current.button 判断鼠标是哪个按键的(0是鼠标左键)
        //Event.current.type 判断鼠标的事件方式的(鼠标按下)
        if (Event.current.button == 0 && Event.current.type == EventType.MouseDown)
        {
            //从鼠标的位置需要发射射线了
            //因为是从Scene视图下发射射线,跟场景中的摄像机并没有关系,所以不能使用相机发射射线的方法
            //从编辑器GUI中的一个点向世界定义一条射线, 参数一般都是鼠标的坐标
            Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
            if (Physics.Raycast(ray, out hit, 100, 1 << 9))
            {
                //需要在检测到的点实例化,路点
                InstancePathNode(hit.point + Vector3.up * 0.1f);
            }

        }
    }

    /// <summary>
    /// 生成节点
    /// </summary>
    /// <param name="position"></param>
    void InstancePathNode(Vector3 position)
    {
        //点预制体
        GameObject prefab = Resources.Load<GameObject>("PathNode");
        //点对象,生成到Plane的子物体下
        GameObject pathNode= Instantiate<GameObject>(prefab, position, Quaternion.identity, manager.transform);
        //把生成的路点添加到列表里
        manager.nodes.Add(pathNode);
    }

    /// <summary>
    /// 删除最后一个节点
    /// </summary>
    void RemoveAtLast()
    {
        //保证有节点才能删节点
        if (manager.nodes.Count > 0)
        {
            //从场景中删除游戏物体
            DestroyImmediate(manager.nodes[manager.nodes.Count - 1]);
            //把该节点从列表中移除
            manager.nodes.RemoveAt(manager.nodes.Count - 1);
        }

    }


    /// <summary>
    /// 删除所有的节点
    /// </summary>
    void RemoveAll()
    {
        ///遍历删除所有的节点物体
        for (int i = 0; i < manager.nodes.Count; i++)
        {
            if (manager.nodes[i] != null)
            {
                DestroyImmediate(manager.nodes[i]);
            }
        }

        manager.nodes.Clear();//清空列表
    }


}

2、脚本类:

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

[ExecuteInEditMode]//在编辑模式下可以执行一些生命周期函数
public class NodeManager : MonoBehaviour {

    //存储了所有编辑器下点击生成的点,并使用预制体显示
    public List<GameObject> nodes;

	// Use this for initialization
	void Start () {
		
	}

	void Update () {
        if (nodes != null&&nodes.Count>1)
        {
            for (int i = 0; i < nodes.Count - 1; i++)
            {
                Debug.DrawLine(nodes[i].transform.position,
                    nodes[i + 1].transform.position,
                    Color.red,
                    Time.deltaTime);
            }
        }
        
	}
}