介绍
文档中会进行SceneView的自定义扩展,实现显示常驻GUI和添加自定义叠加层(Custom Overlay)。
最近项目开发用回了原生的Unity UI相关内容。对于之前常用的FairyGUI来说,原生的UGUI对于UI同学来讲有些不太方便。再加上这次会进行一些固定的脚本绑定,想在UI制作时直接加进到组件上,所以有了这篇文章。
这篇文章是在制作完工具后进行的提炼,偏向代码方面。
在制作相关工具时,根据UI同学来讲,想做到在场景中能够直接点击创建对应的UI组件,切换、编辑和预览等操作时,工具不会消失。在此基础上又需要加上创建对应UI组件时直接添加好对应的脚本。
之前虽然也写过一些编辑器的扩展,但对于Scene界面涉及的不多。故我直接在网上找了一篇比较符合工具的文章,根据这篇文章做出了第一版的界面工具。引用:unity Scene View扩展之显示常驻GUI - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/124269658
准备
找一些功能图标,方便更直观的查看功能,推荐网站:iconfont-阿里巴巴矢量图标库
将准备好的图标,拷贝到当前工程的编辑器默认资源路径下。示例,我准备的按钮图标(Assets/Editor Default Resources/UITools/icon_button.png)
设置图标的属性为:Editor GUI and Legacy GUI
制作Scene常驻GUI
脚本中涉及到:
1.场景每次调用 OnGUI 方法时接收回调
SceneView.duringSceneGui
2.脚本重新编译后自动执行指定的操作
[DidReloadScripts]
3.编辑器模式下照常执行当前脚本
[ExecuteInEditMode]
4.编辑器执行指定菜单
EditorApplication.ExecuteMenuItem("已经存在的菜单项路径") // 例子:创建UI按钮 // EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Button");
1、添加GUI窗口
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
public class SceneTools1 : MonoBehaviour
{
private Rect _initPosition = new Rect(50, 50, 0, 0);
private readonly GUILayoutOption _winWidth = GUILayout.Width(120);
private void Awake()
{
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui -= OnSceneViewGui;
SceneView.duringSceneGui += OnSceneViewGui;
#else
SceneView.onSceneGUIDelegate -= OnSceneViewGui;
SceneView.onSceneGUIDelegate += OnSceneViewGui;
#endif
}
private void OnSceneViewGui(SceneView obj)
{
Handles.BeginGUI();
_initPosition = GUILayout.Window(1, _initPosition, WindowContentFunc, "UI Tools", _winWidth);
Handles.EndGUI();
}
private void WindowContentFunc(int id)
{
GUILayout.Label("当前选中项:");
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(Selection.activeTransform, typeof(Transform), true);
EditorGUI.EndDisabledGroup();
GUI.DragWindow();
}
private void OnDestroy()
{
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui -= OnSceneViewGui;
#else
SceneView.onSceneGUIDelegate -= OnSceneViewGui;
#endif
}
[DidReloadScripts]
private static void Reload()
{
var canvas = FindObjectOfType<SceneTools1>();
if (canvas != null)
{
canvas.Awake();
}
}
}
2、添加GUI按钮
// 添加按钮创建功能
var uiContent = EditorGUIUtility.TrTextContent("按钮", "点击后会创建一个按钮对象", $"Assets/Editor Default Resources/UITools/icon_button.png");
if (GUILayout.Button(uiContent))
{
Debug.LogError("创建按钮被点击");
// 执行菜单 按钮创建
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Button");
}
注1:可以通过修改GUI按钮风格来达到自己的想要的表现效果,详细的UI菜单执行方法可以在UI包的MenuOptions.cs中查看。
完整代码:
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
public class SceneTools1 : MonoBehaviour
{
private Rect _initPosition = new Rect(50, 50, 0, 0);
private readonly GUILayoutOption _winWidth = GUILayout.Width(120);
private void Awake()
{
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui -= OnSceneViewGui;
SceneView.duringSceneGui += OnSceneViewGui;
#else
SceneView.onSceneGUIDelegate -= OnSceneViewGui;
SceneView.onSceneGUIDelegate += OnSceneViewGui;
#endif
}
private void OnSceneViewGui(SceneView obj)
{
Handles.BeginGUI();
_initPosition = GUILayout.Window(1, _initPosition, WindowContentFunc, "UI Tools", _winWidth);
Handles.EndGUI();
}
private void WindowContentFunc(int id)
{
GUILayout.Label("当前选中项:");
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(Selection.activeTransform, typeof(Transform), true);
EditorGUI.EndDisabledGroup();
var uiContent = EditorGUIUtility.TrTextContent("按钮", "点击后会创建一个按钮对象", $"Assets/Editor Default Resources/UITools/icon_button.png");
if (GUILayout.Button(uiContent))
{
Debug.LogError("创建按钮被点击");
// 执行菜单 按钮创建
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Button");
}
GUI.DragWindow();
}
private void OnDestroy()
{
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui -= OnSceneViewGui;
#else
SceneView.onSceneGUIDelegate -= OnSceneViewGui;
#endif
}
[DidReloadScripts]
private static void Reload()
{
var canvas = FindObjectOfType<SceneTools1>();
if (canvas != null)
{
canvas.Awake();
}
}
}
结果
当具体的功能实现以及同步到UI同学后,在使用的过程中发现了一些小问题。常见的有以下这些
1、工具跟着场景走,如果想要预览和编辑都有工具,需要在预览场景中和UI制作场景中都挂载当前脚本。
2、工具位置不会跟着窗口大小调整,如果调整了Scene窗口的大小可能会导致工具找不到。
3、如果在UI编辑场景中双击预览会导致工具异常。
制作Scene叠加层
为了解决上面的问题,我更换了扩展方案,要创建类似Scene中工具条的那种扩展。经过一番查找终于在官方手册中找到想要的扩展功能:Create your own overlay - Unity 手册 (unity3d.com)https://docs.unity3d.com/cn/current/Manual/overlays-custom.html
跟着手册的操作执行下来,我如愿的创建了自己的工具条和面板。如图
如果界面中没有出现上面的示意图,在确保代码没有出错以及代码位置正确的情况下,通过点击右上角的三个点或者右键点击Scene标签,弹出工具条相关的菜单,选中自己自定义的菜单名即可。如图:
1、创建自定义工具条
在Editor目录下新建脚本并修改继承类,手册中的示例已经很详细了,这里将手册中的代码给贴了过来方便查看。
using UnityEditor;
using UnityEditor.Overlays;
using UnityEditor.Toolbars;
using UnityEngine;
using UnityEngine.UIElements;
namespace Game.Editor
{
[Overlay(typeof(SceneView), "工具条例子")]
public class EditorToolbarExample : ToolbarOverlay
{
[EditorToolbarElement(id, typeof(SceneView))]
class DropdownExample : EditorToolbarDropdown
{
public const string id = "ExampleToolbar/Dropdown";
static string dropChoice = null;
public DropdownExample()
{
text = "Axis";
clicked += ShowDropdown;
}
void ShowDropdown()
{
var menu = new GenericMenu();
menu.AddItem(new GUIContent("X"), dropChoice == "X", () =>
{
text = "X";
dropChoice = "X";
});
menu.AddItem(new GUIContent("Y"), dropChoice == "Y", () =>
{
text = "Y";
dropChoice = "Y";
});
menu.AddItem(new GUIContent("Z"), dropChoice == "Z", () =>
{
text = "Z";
dropChoice = "Z";
});
menu.ShowAsContext();
}
}
[EditorToolbarElement(id, typeof(SceneView))]
class ToggleExample : EditorToolbarToggle
{
public const string id = "ExampleToolbar/Toggle";
public ToggleExample()
{
text = "Toggle OFF";
this.RegisterValueChangedCallback(Test);
}
void Test(ChangeEvent<bool> evt)
{
if (evt.newValue)
{
Debug.Log("ON");
text = "Toggle ON";
}
else
{
Debug.Log("OFF");
text = "Toggle OFF";
}
}
}
[EditorToolbarElement(id, typeof(SceneView))]
class DropdownToggleExample : EditorToolbarDropdownToggle, IAccessContainerWindow
{
public const string id = "ExampleToolbar/DropdownToggle";
public EditorWindow containerWindow { get; set; }
static int colorIndex = 0;
static readonly Color[] colors = new Color[] { Color.red, Color.green, Color.cyan };
public DropdownToggleExample()
{
text = "Color Bar";
tooltip =
"Display a color rectangle in the top left of the Scene view. Toggle on or off, and open the dropdown" +
"to change the color.";
dropdownClicked += ShowColorMenu;
SceneView.duringSceneGui += DrawColorSwatch;
}
void DrawColorSwatch(SceneView view)
{
if (view != containerWindow || !value)
{
return;
}
Handles.BeginGUI();
GUI.color = colors[colorIndex];
GUI.DrawTexture(new Rect(8, 8, 120, 24), Texture2D.whiteTexture);
GUI.color = Color.white;
Handles.EndGUI();
}
void ShowColorMenu()
{
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Red"), colorIndex == 0, () => colorIndex = 0);
menu.AddItem(new GUIContent("Green"), colorIndex == 1, () => colorIndex = 1);
menu.AddItem(new GUIContent("Blue"), colorIndex == 2, () => colorIndex = 2);
menu.ShowAsContext();
}
}
[EditorToolbarElement(id, typeof(SceneView))]
class CreateCube : EditorToolbarButton //, IAccessContainerWindow
{
public const string id = "ExampleToolbar/Button";
public CreateCube()
{
text = "Create Cube";
icon = AssetDatabase.LoadAssetAtPath<Texture2D>("Assets/CreateCubeIcon.png");
tooltip = "Instantiate a cube in the scene.";
clicked += OnClick;
}
void OnClick()
{
var newObj = GameObject.CreatePrimitive(PrimitiveType.Cube).transform;
Undo.RegisterCreatedObjectUndo(newObj.gameObject, "Create Cube");
}
}
EditorToolbarExample() : base(
CreateCube.id,
ToggleExample.id,
DropdownExample.id,
DropdownToggleExample.id
)
{
}
}
}
2、创建自定义面板
创建自定义面板给的例子比较简单,只是显示个标签。接下来我会贴出之前在使用过程中的一些修改,比如 单行显示,空间组合等等。
先贴上手册中的代码:
using UnityEditor;
using UnityEditor.Overlays;
using UnityEngine.UIElements;
[Overlay(typeof(SceneView), "面板自绘例子", true)]
public class MyToolButtonOverlay : Overlay
{
public override VisualElement CreatePanelContent()
{
var root = new VisualElement() { name = "UI工具面板" };
root.Add(new Label() { text = "这是个自定义面板" });
return root;
}
}
接下来将之前的按钮创建在这个面板中:
using UnityEngine;
using UnityEditor.Toolbars;
using UnityEditor.Overlays;
using UnityEngine.UIElements;
using UnityEditor;
[Overlay(typeof(SceneView), "面板自绘例子", true)]
public class MyToolButtonOverlay : Overlay
{
public override VisualElement CreatePanelContent()
{
var root = new VisualElement() { name = "UI工具面板" };
var btn1 = new EditorToolbarButton()
{
text = "按钮",
icon = (Texture2D)EditorGUIUtility.LoadRequired($"Assets/Editor Default Resources/UITools/icon_button.png"),
tooltip = "点击后会创建一个按钮对象",
clickable = new Clickable(() =>
{
Debug.LogError("创建按钮被点击");
// 执行菜单 按钮创建
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Button");
}),
};
root.Add(btn1);
return root;
}
}
效果图预览:
可以看到这个外观尺寸和布局都不太合适,尤其在有多个按钮时,可以想想会有多不方便。这时,可以通过修改当前按钮的风格来美化组件。总结了些常用的风格如下:
style =
{
color = Color.white, // 修改颜色
height = 32, // 修改高度
width = 120, // 修改宽度
marginLeft = 4, // 修改左边距
marginRight = 4, // 修改右边距
marginBottom = 2, // 修改下边距
marginTop = 2, // 修改上边距
borderBottomLeftRadius = 4, // 修改左下角圆角
borderBottomRightRadius = 4, // 修改右下角圆角
borderTopLeftRadius = 4, // 修改左上角圆角
unityTextAlign = TextAnchor.MiddleLeft, // 修改文本对齐方式
flexDirection = FlexDirection.Row, // 修改图文混排的布局方式显示
}
添加风格后的按钮示意图如下:
如果要单独修改按钮上的某个元素的风格时,可以通过方法 ElementAt 传入下标获取到对应的元素组件。比如将图片对齐到按钮左边等等。
var imgBtn1 = btn1.ElementAt(0);
var imgStyle1 = imgBtn1.style;
imgStyle1.width = 32;
imgStyle1.height = 32;
imgStyle1.alignSelf = Align.Center;
改后的效果图:
当一个按钮风格配置好后,如果想要通用当前的按钮,需要扩展当前的按钮类。
private class MyToolbarButton : EditorToolbarButton
{
public MyToolbarButton(string name,string iconPath,string tooltip,Action onClick)
{
this.text = name;
this.icon = (Texture2D)EditorGUIUtility.LoadRequired(iconPath);
this.tooltip = tooltip;
this.clicked += onClick;
var btnStyle = this.style;
btnStyle.color = Color.white;
btnStyle.height = 32;
btnStyle.width = 120;
btnStyle.marginLeft = 4;
btnStyle.marginRight = 4;
btnStyle.marginBottom = 2;
btnStyle.marginTop = 2;
btnStyle.borderBottomLeftRadius = 4;
btnStyle.borderBottomRightRadius = 4;
btnStyle.borderTopLeftRadius = 4;
btnStyle.borderTopRightRadius = 4;
btnStyle.unityTextAlign = TextAnchor.MiddleLeft;
btnStyle.flexDirection = FlexDirection.Row;
var imgBtn1 = this.ElementAt(0);
var imgStyle1 = imgBtn1.style;
imgStyle1.width = 32;
imgStyle1.height = 32;
imgStyle1.alignSelf = Align.Center;
}
}
修改之前的添加按钮代码,改成封装后的按钮,并再添加一个看看效果。
var btn1 = new MyToolbarButton("按钮", "Assets/Editor Default Resources/UITools/icon_button.png", "创建文本对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Button");
});
var btn2 = new MyToolbarButton("文本", "Assets/Editor Default Resources/UITools/icon_text.png", "创建文本对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Text");
});
root.Add(btn1);
root.Add(btn2);
效果图:
效果出来了但这时候会发现,不管添加多少按钮组件,都是垂直向下添加的。如果想让某些组件进行水平显示,又不影响现有布局的情况下,我们可以这样做:
1、创建一个新的VisualElement,加入到根节点,并修改风格中的布局方向:
flexDirection = FlexDirection.Row
2、将需要重新布局的组件添加到当前新的节点中:
var layer = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row, // 水平
marginLeft = 4,
marginRight = 4,
marginTop = 2,
marginBottom = 2,
}
};
var btn3 = new MyToolbarButton("图片", "Assets/Editor Default Resources/UITools/icon_image.png", "创建图片对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Image");
});
btn3.style.width = 75;
var btn4 = new MyToolbarButton("原图", "Assets/Editor Default Resources/UITools/icon_image.png", "创建Raw Image对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Raw Image");
});
btn4.style.width = 75;
layer.Add(btn3);
layer.Add(btn4);
root.Add(layer);
示意图查看:
注:如果需要更复杂的布局,可以进行布局嵌套实现。
完整代码:
using System;
using UnityEngine;
using UnityEditor.Toolbars;
using UnityEditor.Overlays;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
[Overlay(typeof(SceneView), "面板自绘例子", true)]
public class MyToolButtonOverlay : Overlay
{
private class MyToolbarButton : EditorToolbarButton
{
public MyToolbarButton(string name,string iconPath,string tooltip,Action onClick)
{
this.text = name;
this.icon = (Texture2D)EditorGUIUtility.LoadRequired(iconPath);
this.tooltip = tooltip;
this.clicked += onClick;
var btnStyle = this.style;
btnStyle.color = Color.white;
btnStyle.height = 32;
btnStyle.width = 120;
btnStyle.marginLeft = 4;
btnStyle.marginRight = 4;
btnStyle.marginBottom = 2;
btnStyle.marginTop = 2;
btnStyle.borderBottomLeftRadius = 4;
btnStyle.borderBottomRightRadius = 4;
btnStyle.borderTopLeftRadius = 4;
btnStyle.borderTopRightRadius = 4;
btnStyle.unityTextAlign = TextAnchor.MiddleLeft;
btnStyle.flexDirection = FlexDirection.Row;
var imgBtn1 = this.ElementAt(0);
var imgStyle1 = imgBtn1.style;
imgStyle1.width = 32;
imgStyle1.height = 32;
imgStyle1.alignSelf = Align.Center;
}
}
public override VisualElement CreatePanelContent()
{
var root = new VisualElement() { name = "UI工具面板" };
var btn1 = new MyToolbarButton("按钮", "Assets/Editor Default Resources/UITools/icon_button.png", "创建文本对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Button");
});
var btn2 = new MyToolbarButton("文本", "Assets/Editor Default Resources/UITools/icon_text.png", "创建文本对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Text");
});
root.Add(btn1);
root.Add(btn2);
var layer = new VisualElement()
{
style =
{
flexDirection = FlexDirection.Row, // 水平
marginLeft = 4,
marginRight = 4,
marginTop = 2,
marginBottom = 2,
}
};
var btn3 = new MyToolbarButton("图片", "Assets/Editor Default Resources/UITools/icon_image.png", "创建图片对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Image");
});
btn3.style.width = 75;
var btn4 = new MyToolbarButton("原图", "Assets/Editor Default Resources/UITools/icon_image.png", "创建Raw Image对象",
() =>
{
EditorApplication.ExecuteMenuItem("GameObject/UI/Legacy/Raw Image");
});
btn4.style.width = 75;
layer.Add(btn3);
layer.Add(btn4);
root.Add(layer);
return root;
}
}
Overlay的相关知识就分享到这了,如果后续有新的使用经验,会同步到当前文档中。
引用参考
1、unity Scene View扩展之显示常驻GUI - 知乎 (zhihu.com)
2、Create your own overlay - Unity 手册 (unity3d.com)