一 说明
该页功能扩展是指针对某个被选择的物体进行编辑的扩展,比如操作杆,提示等。如果物体不被选择则无法触发相关功能
大量可直接调用的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();
}
}