版本: 2022.3+
此示例演示如何使用 C# 脚本创建自定义编辑器窗口以响应用户输入,使UI(用户界面) 允许用户与您的应用程序交互。Unity 目前支持三种 UI 系统。 更多信息
参见 术语表 可调整大小,并处理热重载。
自定义编辑器窗口是一个从 EditorWindow
类派生的类。UI 工具包使用 CreateGUI 方法将控件添加到编辑器 UI,并且当需要显示窗口时,Unity 会自动调用 CreateGUI
方法。此方法的工作方式与 Awake
或 Update
等方法相同。
创建自定义编辑器窗口时,请遵循以下准则
CreateGUI
方法中,以确保所有必要的资源都可用。CreateGUI
内部或 CreateGUI
调用之后。下图显示了编辑器窗口的执行顺序
有关更多信息,请参阅 EditorWindow 类文档。
此示例创建了一个精灵2D 图形对象。如果您习惯于使用 3D,则精灵本质上只是标准纹理,但有一些特殊的技术可以结合和管理精灵纹理,以便在开发过程中提高效率和便利性。 更多信息
参见 术语表 浏览器,它查找并显示项目中的所有精灵,并将它们显示在列表中。如果选择列表中的精灵,则精灵的图像将显示在窗口右侧。
您可以在此 GitHub 存储库 中找到此示例创建的完整文件。
本指南适用于熟悉 Unity 编辑器、UI 工具包和 C# 脚本的开发人员。在开始之前,请熟悉以下内容
要向 UI 添加 UI 控件,请将视觉元素视觉树的一个节点,它实例化或派生自 C# VisualElement
类。您可以设置外观样式、定义行为,并将其显示在屏幕上作为 UI 的一部分。 更多信息
参见 术语表 添加到视觉树中。UI 工具包使用 VisualElement.Add()
方法将子元素添加到现有的视觉元素中,并通过 rootvisualElement
属性访问编辑器窗口的 视觉树。
MyCustomEditor
。为了呈现精灵列表,此示例使用 AssetDatabase
查找项目中的所有精灵。对于精灵浏览器,添加一个 TwoPaneSplitView
以将可用的窗口空间分成两个窗格:一个固定大小,一个灵活大小。当您调整窗口大小时,灵活窗格会调整大小,而固定大小的窗格保持相同大小。
在文件顶部,添加列表所需的以下指令
using System.Collections.Generic;
将 CreateGUI()
内部的代码替换为以下代码。这将枚举项目中的所有精灵。
public void CreateGUI()
{
// Get a list of all sprites in the project
var allObjectGuids = AssetDatabase.FindAssets("t:Sprite");
var allObjects = new List<Sprite>();
foreach (var guid in allObjectGuids)
{
allObjects.Add(AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GUIDToAssetPath(guid)));
}
}
在 CreateGUI()
内部,添加以下代码。这将创建一个 TwoPaneSplitview 并添加两个子元素作为不同控件的占位符。
// Create a two-pane view with the left pane being fixed.
var splitView = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
// Add the view to the visual tree by adding it as a child to the root element.
rootVisualElement.Add(splitView);
// A TwoPaneSplitView needs exactly two child elements.
var leftPane = new VisualElement();
splitView.Add(leftPane);
var rightPane = new VisualElement();
splitView.Add(rightPane);
从菜单中,选择“窗口”>“UI 工具包”>“MyCustomEditor”以打开窗口。窗口显示一个带有两个空面板的拆分视图。移动分隔线以查看其工作原理。
对于精灵浏览器,左侧窗格将是一个包含项目中所有精灵名称的列表。 ListView 控件派生自 VisualElement
,因此可以轻松修改代码以使用 ListView 而不是 VisualElement
。
ListView 控件显示一个可选择项目的列表。它经过优化,可以创建足够多的元素来覆盖可见区域,并在您滚动列表时池化和回收视觉元素。这优化了性能并减少了内存占用,即使在包含许多项目的列表中也是如此。
要利用此功能,请使用以下内容初始化 ListView
您可以为列表中的每个元素创建复杂的 UI 结构。出于演示目的,此示例使用简单的文本标签来显示精灵名称。
在 CreateGUI()
内部,将左侧窗格更改为 ListView 而不是 VisualElement
public void CreateGUI()
{
...
var leftPane = new ListView();
splitView.Add(leftPane);
...
}
在 CreateGUI()
的底部,添加以下代码以初始化 ListView
public void CreateGUI()
{
...
// Initialize the list view with all sprites' names
leftPane.makeItem = () => new Label();
leftPane.bindItem = (item, index) => { (item as Label).text = allObjects[index].name; };
leftPane.itemsSource = allObjects;
}
从菜单中,选择“窗口”>“UI 工具包”>“MyCustomEditor”以打开自定义编辑器窗口。窗口显示一个可滚动的列表视图和可选择的项目,类似于下面的图像。
要从列表中选择精灵时在右侧面板上显示精灵的图像,请使用左侧窗格的 selectionChanged
属性并添加回调函数。
要显示图像,请为选定的精灵创建一个新的 Image 控件,并使用 VisualElement.Clear()
删除所有先前的内容,然后再添加控件。
提示:如果您丢失了窗口并且菜单无法重新打开,请通过“窗口”>“面板”>“关闭所有浮动面板”下的菜单关闭所有浮动面板,或重置窗口布局。
当左侧窗格中的列表选择发生变化时添加回调函数。
public void CreateGUI()
{
...
// React to the user's selection
leftPane.selectionChanged += OnSpriteSelectionChange;
}
private void OnSpriteSelectionChange(IEnumerable<object> selectedItems)
{
}
回调函数需要访问 TwoPaneSplitview 的右侧窗格。为此,将 CreateGUI()
内部创建的右侧窗格更改为成员变量
private VisualElement m_RightPane;
public void CreateGUI()
{
...
m_RightPane = new VisualElement();
splitView.Add(m_RightPane);
...
}
将以下代码添加到 OnSpriteSelectionChange
函数中。这将清除窗格中的所有先前内容,获取选定的精灵,并添加一个新的 Image 控件以显示精灵。
private void OnSpriteSelectionChange(IEnumerable<object> selectedItems)
{
// Clear all previous content from the pane.
m_RightPane.Clear();
// Get the selected sprite and display it.
var enumerator = selectedItems.GetEnumerator();
if (enumerator.MoveNext())
{
var selectedSprite = enumerator.Current as Sprite;
if (selectedSprite != null)
{
// Add a new Image control and display the sprite.
var spriteImage = new Image();
spriteImage.scaleMode = ScaleMode.ScaleToFit;
spriteImage.sprite = selectedSprite;
// Add the Image control to the right-hand pane.
m_RightPane.Add(spriteImage);
}
}
}
从菜单中,选择“窗口”>“UI 工具包”>“MyCustomEditor”以打开自定义编辑器窗口。当您从左侧列表中选择一个精灵时,精灵的图像将显示在窗口右侧,类似于下面的图像。
编辑器窗口在其允许的最小和最大尺寸内可调整大小。要设置这些尺寸,请写入 EditorWindow.minSize 和 EditorWindow.maxSize 属性。要防止窗口调整大小,请为这两个属性分配相同的尺寸。
如果窗口尺寸太小而无法显示整个 UI,则可以使用 ScrollView
元素为窗口提供滚动功能。左侧窗格上的 ListView 在内部使用 ScrollView
,但右侧窗格是常规 VisualElement
。要使右侧窗格可调整大小,请将其更改为具有双向滚动的 ScrollView
。
在 ShowMyEditor()
函数的底部添加以下代码以限制窗口的大小
public static void ShowMyEditor()
{
...
// Limit size of the window.
wnd.minSize = new Vector2(450, 200);
wnd.maxSize = new Vector2(1920, 720);
}
在 CreateGUI()
内部,将右侧窗格 VisualElement
更改为具有双向滚动的 ScrollView
public void CreateGUI()
{
...
m_RightPane = new ScrollView(ScrollViewMode.VerticalAndHorizontal);
splitView.Add(m_RightPane);
...
}
从菜单中,选择“窗口”>“UI 工具包”>“MyCustomEditor”以打开自定义编辑器窗口。精灵浏览器窗口现在具有滚动条。调整窗口大小以查看滚动条的工作原理。
当脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间推移修改组件属性并以任何您喜欢的方式响应用户输入。 更多信息
参见 术语表 重新编译或编辑器进入播放模式时,会发生 C# 域重载。在您刚刚创建的编辑器窗口中,打开精灵浏览器,选择一个精灵,然后进入播放模式。窗口将重置,并且选择将消失。
正确的编辑器窗口需要与 热重载 工作流配合使用。由于 VisualElement
对象不可序列化,因此您必须在每次发生重载时重新创建 UI。这意味着 CreateGUI()
方法在重载完成后被调用。这使您可以在重载之前通过将必要的数据存储在您的 EditorWindow
类中来恢复 UI 状态。
向 MyCustomEditor
类添加一个成员变量以保存精灵列表中的选中索引。当您进行选择时,此成员变量将存储 ListView 的新选中索引。
public class MyCustomEditor : EditorWindow
{
[SerializeField] private int m_SelectedIndex = -1;
....
}
在 CreateGUI()
的底部添加以下代码以存储和恢复选定的列表索引。
public void CreateGUI()
{
...
// Restore the selection index from before the hot reload.
leftPane.selectedIndex = m_SelectedIndex;
// Store the selection index when the selection changes.
leftPane.selectionChanged += (items) => { m_SelectedIndex = leftPane.selectedIndex; };
}
从菜单中选择**窗口** > **UI 工具包** > **MyCustomEditor** 以打开自定义编辑器窗口。从列表中选择一个精灵并进入播放模式以测试热重载。
作为参考,以下是完成的脚本
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
public class MyCustomEditor : EditorWindow
{
[SerializeField] private int m_SelectedIndex = -1;
private VisualElement m_RightPane;
[MenuItem("Window/UI Toolkit/MyCustomEditor")]
public static void ShowMyEditor()
{
// This method is called when the user selects the menu item in the Editor.
EditorWindow wnd = GetWindow<MyCustomEditor>();
wnd.titleContent = new GUIContent("My Custom Editor");
// Limit size of the window.
wnd.minSize = new Vector2(450, 200);
wnd.maxSize = new Vector2(1920, 720);
}
public void CreateGUI()
{
// Get a list of all sprites in the project.
var allObjectGuids = AssetDatabase.FindAssets("t:Sprite");
var allObjects = new List<Sprite>();
foreach (var guid in allObjectGuids)
{
allObjects.Add(AssetDatabase.LoadAssetAtPath<Sprite>(AssetDatabase.GUIDToAssetPath(guid)));
}
// Create a two-pane view with the left pane being fixed.
var splitView = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
// Add the panel to the visual tree by adding it as a child to the root element.
rootVisualElement.Add(splitView);
// A TwoPaneSplitView always needs two child elements.
var leftPane = new ListView();
splitView.Add(leftPane);
m_RightPane = new ScrollView(ScrollViewMode.VerticalAndHorizontal);
splitView.Add(m_RightPane);
// Initialize the list view with all sprites' names.
leftPane.makeItem = () => new Label();
leftPane.bindItem = (item, index) => { (item as Label).text = allObjects[index].name; };
leftPane.itemsSource = allObjects;
// React to the user's selection.
leftPane.selectionChanged += OnSpriteSelectionChange;
// Restore the selection index from before the hot reload.
leftPane.selectedIndex = m_SelectedIndex;
// Store the selection index when the selection changes.
leftPane.selectionChanged += (items) => { m_SelectedIndex = leftPane.selectedIndex; };
}
private void OnSpriteSelectionChange(IEnumerable<object> selectedItems)
{
// Clear all previous content from the pane.
m_RightPane.Clear();
var enumerator = selectedItems.GetEnumerator();
if (enumerator.MoveNext())
{
var selectedSprite = enumerator.Current as Sprite;
if (selectedSprite != null)
{
// Add a new Image control and display the sprite.
var spriteImage = new Image();
spriteImage.scaleMode = ScaleMode.ScaleToFit;
spriteImage.sprite = selectedSprite;
// Add the Image control to the right-hand pane.
m_RightPane.Add(spriteImage);
}
}
}
}