效果演示

createNode.gif

注意:外挂式脚本需要放在Editor目录。

NodeManagerEditor脚本

using System;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(NodeManager1))]
public class NodeManagerEditor1:Editor
{

    public NodeManager1 _nodeManger;

    /// <summary>
    /// 是否进入编辑模式
    /// </summary>
    public bool _isEditor;

    private void OnEnable()
    {
        _nodeManger = (NodeManager1)target;
    }


    private void OnDisable()
    {
        _nodeManger = null;
    }

    public override void OnInspectorGUI()
    {
        SerializedProperty sp = serializedObject.FindProperty("nodes");
        // 第一个参数:要为其创建字段的 SerializedProperty
        // 第二个参数: 要使用的可选标签, 如果未指定,则使用属性本身的标签
        // 第三个参数:如果为 true,将绘制包含子项的属性;否则仅绘制控件本身
        EditorGUILayout.PropertyField(sp, new GUIContent("路点"), true);
        serializedObject.ApplyModifiedProperties();

        // 绘制对象
        _nodeManger.go = EditorGUILayout.ObjectField("节点对象样式", _nodeManger.go, typeof(GameObject), true) as GameObject;


        // 绘制开始编辑按钮
        if (!_isEditor && GUILayout.Button("开始编辑"))
        {
            // 点击开始编辑,打开windows窗口。思考为什么需要打开windows窗口
            NodeWindows1.openWindow(_nodeManger.gameObject);
            _isEditor = true;

        }
        else if(_isEditor && GUILayout.Button("关闭编辑")){
            NodeWindows1.closeWindow();
            _isEditor = false;
        }

        if (GUILayout.Button("删除最后一个节点"))
        {
            RemoveLastNode();
        }

        if (GUILayout.Button("删除所有节点"))
        {
            RemoveAll();
        }
    }

    private void RemoveAll()
    {
        for (int i = 0; i < _nodeManger.nodes.Count; i++)
        {
            if(_nodeManger.nodes[i] != null)
            {
                DestroyImmediate(_nodeManger.nodes[i]);
            }
        }
        _nodeManger.nodes.Clear();
    }

    private void RemoveLastNode()
    {
        // 只要保证存在节点,才能删除节点
        if(_nodeManger.nodes.Count > 0)
        {
            // 从场景中删除游戏物体
            DestroyImmediate(_nodeManger.nodes[_nodeManger.nodes.Count - 1]);
            // 从列表中删除游戏物体
            _nodeManger.nodes.RemoveAt(_nodeManger.nodes.Count - 1);
        }
    }


    // 进入编辑模式后,需要创建结点,需要获取鼠标点击事件,通过射线

    RaycastHit hitInfo;
    // 在编辑场景中,只要鼠标移动、点击等,都会触发该函数
    private void OnSceneGUI()
    {
        // 如果没有进入编辑模式,直接返回
        if (!_isEditor)
        {
            return;
        }
        // 如果进入编辑模式,需要获取射线、鼠标事件
        if(Event.current.button == 0 && Event.current.type == EventType.MouseDown)
        {
            // 鼠标按下
            Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
            if(Physics.Raycast(ray,out hitInfo,100, 1 << 31))
            {
                // 生成一个节点
                // 为什么要加一个 Vector3.up * 0.1f?
                // 使得物体生成的位置,在水平面上
                GengerateNode(hitInfo.point + Vector3.up * 0.1f);
            }

        }

    }



    private void GengerateNode(Vector3 position)
    {
        // GameObject go = Resources.Load<GameObject>("PathNode");
        GameObject go = Instantiate(_nodeManger.go, position, Quaternion.identity, _nodeManger.transform);
        _nodeManger.nodes.Add(go);
    }
}

public class NodeWindows1 : EditorWindow
{

    static NodeWindows1 window;

    static GameObject go;

    private void Update()
    {
        // 始终保持面板不会跳转
        Selection.activeGameObject = go;
    }

    public static void openWindow(GameObject node)
    {
        go = node;
        window = EditorWindow.GetWindow<NodeWindows1>();
    }

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


NodeManager脚本

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

[ExecuteInEditMode] // 在编辑模式下可以执行一些生命周期函数
public class NodeManager1 : MonoBehaviour
{
    /// <summary>
    /// 存储路径的所有节点
    /// </summary>
    public List<GameObject> nodes;

    public GameObject go;

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