unity editor 编辑器插件制作基础:四、Scene窗口中的子窗口面板菜单UI组件,以及物体对象控制组件功能扩展

一 说明

该页功能扩展是指针对某个被选择的物体进行编辑的扩展,比如操作杆,提示等。如果物体不被选择则无法触发相关功能
大量可直接调用的scene组件都可在这里找到 https://docs.unity3d.com/ScriptReference/Handles.html

1.1 引用库

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

1.1 脚本结构

// 需要与场景中物体的脚本相关联
[CustomEditor(typeof(SceneTools))]
// 允许编辑多个物体
[CanEditMultipleObjects]
// 必须继承Editor
public class SceneToolsEditor : Editor{
    
    
    // 设定绑定的目标脚本
    private SceneTools targetScript;
    // 支持普通生命周期,Awake在这里是点击到物体既触发
    private void Awake(){
    
    
        targetScript = target as SceneTools;
    }
    // ui绘制周期 所有组件必须在这里实现更新
    private void OnSceneGUI(){
    
    

    }
}

二 Scene窗口菜单的实现

scene窗口菜单只在scene窗口中显示。
该脚本实现 点击脚本绑定的物体既显示菜单,取消点击关闭菜单

    // 必须在该周期运行
    protected virtual void OnSceneGUI(){
    
    
        this.DrawWindowInScene();
    }
    // 用于存储按钮状态
    private bool _toggleStatus;
    public void DrawWindowInScene()
    {
    
    
        // 在该标签内,才会把3位控件转为2维控件,相当于在canvas中
        Handles.BeginGUI();
        // 垂直布局
        GUILayout.BeginVertical("My Tools", "", new[] {
    
     GUILayout.Height(400), GUILayout.Width(100) });
        //定义 ui stayle
        var buttonStyle = new[] {
    
     GUILayout.Height(30), GUILayout.Width(100) };
        //按钮组写法 1 先获取存储值 2 初始化两个按钮的状态 3 如果按钮状态改变,就返回状态值,4 将状态值返回给存储值,5 下一帧更新按钮状态
        bool toggleStatus = _toggleStatus;
        toggleStatus = GUILayout.Toggle(toggleStatus, "button 1", "button", buttonStyle);
        toggleStatus = !GUILayout.Toggle(!toggleStatus, "button 2", "button", buttonStyle);
        _toggleStatus = toggleStatus;
        //空行
        GUILayout.Space(10);
        //按钮
        if (GUILayout.Button("button 3", buttonStyle))
        {
    
    
            Debug.Log("show menu 1");
        }

        GUILayout.EndVertical();
        Handles.EndGUI();
    }

三 Scene控件实现

    // 必须在该周期内实现
    protected virtual void OnSceneGUI()
    {
    
    
        // serializedObject.Update();
        // 绘制指示方向用的滑竿头  与button组合就能制作出类似移动或是旋转的操作杆
        this.DrawHandleCap(1f);
        // 绘制可调节范围的球盒
        this.DrawRadiusHandle();
        // 绘制可调节位置的滑竿
        // this.DrawSliderHundle();
        // 旋转控件
        this.RotateHandle();
        // 尺寸控件
        this.ScaleHandle();
        // 移动控件
        this.PositionHandle();
        this.SliderHandle();
        //虚拟按钮,与cap组合可以制作控件
        this.VirButton();
    }
    public void DrawHandleCap(float size)
    {
    
    
        // Handles.:
        // ArrowHandleCap
        // CircleHandleCap
        // ConeHandleCap
        // CubeHandleCap
        // DotHandleCap
        // RectangleHandleCap
        // ShperehandleCap
        if (Event.current.type == EventType.Repaint)
        {
    
    
            Transform transform = targetScript.transform;
            Handles.color = Handles.xAxisColor;
            Handles.ArrowHandleCap(
                0,
                transform.position + new Vector3(3f, 0f, 0f),
                transform.rotation * Quaternion.LookRotation(Vector3.right),
                size,
                EventType.Repaint
            );
            Handles.color = Handles.yAxisColor;
            Handles.ArrowHandleCap(
                0,
                transform.position + new Vector3(0f, 3f, 0f),
                transform.rotation * Quaternion.LookRotation(Vector3.up),
                size,
                EventType.Repaint
            );
            Handles.color = Handles.zAxisColor;
            Handles.ArrowHandleCap(
                0,
                transform.position + new Vector3(0f, 0f, 3f),
                transform.rotation * Quaternion.LookRotation(Vector3.forward),
                size,
                EventType.Repaint
            );
        }
    }
    float areaOfEffect = 2f;
    public void DrawRadiusHandle()
    {
    
    
        // 圆形盒 可以像设置碰撞盒那样用作范围设置
        // 当handle发生改变触发
        EditorGUI.BeginChangeCheck();
        float areaOfEffect = Handles.RadiusHandle(Quaternion.identity, targetScript.transform.position, this.areaOfEffect);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            Undo.RecordObject(target, "Changed Area Of Effect");
            this.areaOfEffect = areaOfEffect;
        }
    }
    public void PositionHandle()
    {
    
    
        EditorGUI.BeginChangeCheck();
        Vector3 newTargetPosition = Handles.PositionHandle(targetScript.transform.position, Quaternion.identity);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            Undo.RecordObject(targetScript.transform, "Change Look At Target Position");
            targetScript.transform.position = newTargetPosition;
        }
    }
    public void RotateHandle()
    {
    
    
        EditorGUI.BeginChangeCheck();
        Quaternion rot = Handles.RotationHandle(targetScript.transform.rotation, Vector3.zero);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            //记录操作点 用于ctrl+z 动作回退
            Undo.RecordObject(targetScript.transform, "Rotated RotateAt Point");
            targetScript.transform.rotation = rot;
        }
    }
    public void ScaleHandle()
    {
    
    
        EditorGUI.BeginChangeCheck();
        Vector3 scale = Handles.ScaleHandle(targetScript.transform.localScale, Vector3.zero, Quaternion.identity, 1);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            // 操作回退记录,一定要保存transform
            Undo.RecordObject(targetScript.transform, "Scaled ScaleAt Point");
            targetScript.transform.localScale = scale;
        }
    }
    public void SliderHandle()
    {
    
    
        float size = HandleUtility.GetHandleSize(targetScript.targetTransform.position) * 0.5f;
        float snap = 0.1f;

        EditorGUI.BeginChangeCheck();
        Vector3 newTargetPosition = Handles.Slider(targetScript.targetTransform.position, Vector3.right, size, Handles.ConeHandleCap, snap);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            Undo.RecordObject(targetScript.targetTransform, "Change Look At Target Position");
            targetScript.targetTransform.position = newTargetPosition;
        }
    }

    public void VirButton()
    {
    
    
        // Vector3 position = targetScript.transform.position + Vector3.up * 2f;
        // float size = 2f;
        // float pickSize = size * 2f;

        // if (Handles.Button(position, Quaternion.identity, size, pickSize, Handles.RectangleHandleCap))
        //     Debug.Log("The button was pressed!");
    }

四 SnapGrid 吸附辅助线和增量修改功能

吸附辅助线是指unity编辑器中的辅助线,使用该功能会自动贴近附近的辅助线
增量修改功能是指每次移动、旋转、缩放都按照固定间隔数值操作。一般地图编辑器比较常用的功能。
以上两个都是用于与Rotate Move scale等操作器连用

    protected virtual void OnSceneGUI()
    {
    
    
        this.SetSnapRotate();
        this.SnapToGrid();
    }
    public void SetSnapRotate(){
    
    
        // 物体增量操作 返回增量值
        //计算修正值,可以是flaot,vector2,vector3
        Vector3 snapValue = Handles.SnapValue(Vector3.one,Vector3.one); 
        // 增量操作
        targetTranform[0].Rotate(snapValue,Space.World);
	}
    public void SnapToGrid(){
    
    
        // 物体对齐辅助线  可以多个物体多种对齐方式,通过SnapAxis设置
        Transform[] targetTranform = {
    
    GameObject.Find("").transform};
        Handles.SnapToGrid(targetTranform, SnapAxis.All);
	}

五 全部代码

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

// 网格吸附Handles.SnapToGrid
[CustomEditor(typeof(SceneTools))]
[CanEditMultipleObjects]
public class SceneToolsEditor : Editor
{
    
    
    // 设定绑定的目标脚本
    private SceneTools targetScript;
    // 每次点击挂在物体时就会启动
    private void Awake()
    {
    
    
        targetScript = target as SceneTools;
    }
    // 场景内的组件必须在该周期内绘制
    protected virtual void OnSceneGUI()
    {
    
    
        // serializedObject.Update();
        // 绘制指示方向用的滑竿头  与button组合就能制作出类似移动或是旋转的操作杆
        this.DrawHandleCap(1f);
        // 绘制可调节范围的球盒
        this.DrawRadiusHandle();
        // 绘制可调节位置的滑竿
        // this.DrawSliderHundle();
        // 旋转控件
        this.RotateHandle();
        // 尺寸控件
        this.ScaleHandle();
        // 移动控件
        this.PositionHandle();
        this.SliderHandle();
        //虚拟按钮,与cap组合可以制作控件
        this.VirButton();
        // 绘制scene内的窗口
        this.DrawWindowInScene();
    }
    public void SetSnapRotate(){
    
    
        // 物体增量操作 返回增量值
        //计算修正值,可以是flaot,vector2,vector3
        Vector3 snapValue = Handles.SnapValue(Vector3.one,Vector3.one); 
        // 增量操作
        targetTranform[0].Rotate(snapValue,Space.World);
	}
    public void SnapToGrid(){
    
    
        // 物体对齐辅助线  可以多个物体多种对齐方式,通过SnapAxis设置
        Transform[] targetTranform = {
    
    GameObject.Find("").transform};
        Handles.SnapToGrid(targetTranform, SnapAxis.All);
	}
    // 绘制提示用的模型,比如箭头,圆环,圆柱,方块等
    public void DrawHandleCap(float size)
    {
    
    
        // Handles.:
        // ArrowHandleCap
        // CircleHandleCap
        // ConeHandleCap
        // CubeHandleCap
        // DotHandleCap
        // RectangleHandleCap
        // ShperehandleCap
        if (Event.current.type == EventType.Repaint)
        {
    
    
            Transform transform = targetScript.transform;
            Handles.color = Handles.xAxisColor;
            Handles.ArrowHandleCap(
                0,
                transform.position + new Vector3(3f, 0f, 0f),
                transform.rotation * Quaternion.LookRotation(Vector3.right),
                size,
                EventType.Repaint
            );
            Handles.color = Handles.yAxisColor;
            Handles.ArrowHandleCap(
                0,
                transform.position + new Vector3(0f, 3f, 0f),
                transform.rotation * Quaternion.LookRotation(Vector3.up),
                size,
                EventType.Repaint
            );
            Handles.color = Handles.zAxisColor;
            Handles.ArrowHandleCap(
                0,
                transform.position + new Vector3(0f, 0f, 3f),
                transform.rotation * Quaternion.LookRotation(Vector3.forward),
                size,
                EventType.Repaint
            );
        }
    }
    float areaOfEffect = 2f;
    public void DrawRadiusHandle()
    {
    
    
        // 圆形盒 可以像设置碰撞盒那样用作范围设置
        // 当handle发生改变触发
        EditorGUI.BeginChangeCheck();
        float areaOfEffect = Handles.RadiusHandle(Quaternion.identity, targetScript.transform.position, this.areaOfEffect);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            Undo.RecordObject(target, "Changed Area Of Effect");
            this.areaOfEffect = areaOfEffect;
        }
    }
    public void PositionHandle()
    {
    
    
        EditorGUI.BeginChangeCheck();
        Vector3 newTargetPosition = Handles.PositionHandle(targetScript.transform.position, Quaternion.identity);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            Undo.RecordObject(targetScript.transform, "Change Look At Target Position");
            targetScript.transform.position = newTargetPosition;
        }
    }
    public void RotateHandle()
    {
    
    
        EditorGUI.BeginChangeCheck();
        Quaternion rot = Handles.RotationHandle(targetScript.transform.rotation, Vector3.zero);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            //记录操作点 用于ctrl+z 动作回退
            Undo.RecordObject(targetScript.transform, "Rotated RotateAt Point");
            targetScript.transform.rotation = rot;
        }
    }
    public void ScaleHandle()
    {
    
    
        EditorGUI.BeginChangeCheck();
        Vector3 scale = Handles.ScaleHandle(targetScript.transform.localScale, Vector3.zero, Quaternion.identity, 1);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            // 操作回退记录,一定要保存transform
            Undo.RecordObject(targetScript.transform, "Scaled ScaleAt Point");
            targetScript.transform.localScale = scale;
        }
    }
    public void SliderHandle()
    {
    
    
        float size = HandleUtility.GetHandleSize(targetScript.targetTransform.position) * 0.5f;
        float snap = 0.1f;

        EditorGUI.BeginChangeCheck();
        Vector3 newTargetPosition = Handles.Slider(targetScript.targetTransform.position, Vector3.right, size, Handles.ConeHandleCap, snap);
        if (EditorGUI.EndChangeCheck())
        {
    
    
            Undo.RecordObject(targetScript.targetTransform, "Change Look At Target Position");
            targetScript.targetTransform.position = newTargetPosition;
        }
    }

    public void VirButton()
    {
    
    
        // Vector3 position = targetScript.transform.position + Vector3.up * 2f;
        // float size = 2f;
        // float pickSize = size * 2f;

        // if (Handles.Button(position, Quaternion.identity, size, pickSize, Handles.RectangleHandleCap))
        //     Debug.Log("The button was pressed!");
    }

    private bool _toggleStatus;
    private int toggleIndex = 0;
    public void DrawWindowInScene()
    {
    
    
        // 在该标签内,才会把3位控件转为2维控件,相当于在canvas中
        Handles.BeginGUI();
        // 垂直布局
        GUILayout.BeginVertical("My Tools", "Window", new[] {
    
     GUILayout.Height(400), GUILayout.Width(100) });
        //定义 ui stayle
        var buttonStyle = new[] {
    
     GUILayout.Height(30), GUILayout.Width(100) };
        //按钮组写法 1 先获取存储值 2 初始化两个按钮的状态 3 如果按钮状态改变,就返回状态值,4 将状态值返回给存储值,5 下一帧更新按钮状态
        bool toggleStatus = _toggleStatus;
        toggleStatus = GUILayout.Toggle(toggleStatus, "button 1", "button", buttonStyle);
        toggleStatus = !GUILayout.Toggle(!toggleStatus, "button 2", "button", buttonStyle);
        _toggleStatus = toggleStatus;
        //空行
        GUILayout.Space(10);
        //按钮
        if (GUILayout.Button("button 3", buttonStyle))
        {
    
    
            Debug.Log("show menu 1");
        }

        if (GUILayout.Button("button 4", buttonStyle))
        {
    
    
            Debug.Log("show menu 2");
        }

        GUILayout.EndVertical();
        Handles.EndGUI();
    }
    // Vector3 PosTarget;
    public void DrawSliderHundle()
    {
    
    
        // 滑竿颜色
        Handles.color = Color.red;
        // 获取绑定的目标物体的位置
        Vector3 targetPostion = targetScript.targetTransform.position;
        Vector3 targetScale = targetScript.targetTransform.localScale;
        // 滑竿尺寸
        float size = HandleUtility.GetHandleSize(targetScale) * 0.3f;
        // 创建滑竿
        Vector3 handle1_pos = Handles.Slider2D(
            targetPostion, // 滑竿位置 设置为 绑定目标物体的位置
            Vector3.forward, // 滑竿方向朝前
            Vector3.up, // 滑竿滑道的方向 1
            Vector3.right,// 竿滑道的方向 2
            size, // 滑竿尺寸
            Handles.CubeHandleCap, // 滑竿图标模型
            Vector2.one); // 滑动时的间隔
        // 如果滑竿位置发生了改变    
        if (handle1_pos != targetPostion)
        {
    
    
            EditorUtility.SetDirty(target);
            EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
            // 目标物体与滑竿位置同步
            targetPostion = handle1_pos;
        }
        // 绘制第二个滑竿
        Handles.Slider2D(
            Vector3.zero, // 滑竿位置 设置为 (0,0,0)
            Vector3.forward, //滑竿方向
            Vector3.up, //滑道方向
            Vector3.right,
            size, //滑竿尺寸
            Handles.CylinderHandleCap, // 滑竿模型
            Vector2.one);// 滑动的间隔
        //同步位置
        targetScript.targetTransform.position = postion;
        // 绘制线
        Handles.DrawLine(Vector3.zero, postion);
        // 重绘当前视图
        HandleUtility.Repaint();
    }
}

猜你喜欢

转载自blog.csdn.net/lengyoumo/article/details/113162725