包含此页的版本:
不含此页的版本:
版本: 2022.3+
此示例演示如何使用 C# 脚本创建自定义编辑器窗口,以对用户输入做出反应、使 UI 可调整大小以及处理热重载。
自定义编辑器窗口是一个类,它派生自EditorWindow类。UI Toolkit 使用 CreateGUI 方法向编辑器 UI 添加控件,Unity 调用CreateGUI方法在需要显示窗口时自动。此方法的工作方式与Awake或Update.
创建自定义编辑器窗口时,请遵循以下准则:
CreateGUI方法,以确保所有必要的资产都可用。CreateGUI或之后CreateGUI被称为。下图显示了编辑器窗口的执行顺序:
有关更多信息,请参阅 EditorWindow 类文档。
此示例创建了一个sprite2D 图形对象。如果你习惯于在3D中工作,精灵本质上只是标准纹理,但有一些特殊的技术可以组合和管理精灵纹理,以提高开发过程中的效率和便利性。更多信息
请参阅术语表浏览器,它会查找并显示项目内的所有精灵,并将它们显示在列表中。如果在列表中选择精灵,则精灵的图像将显示在窗口的右侧。
您可以在此 GitHub 存储库中找到此示例创建的已完成文件。
本指南适用于熟悉 Unity 编辑器、UI 工具包和 C# 脚本的开发人员。在开始之前,请熟悉以下内容:
要将 UI 控件添加到 UI,请将视觉元素实例化或派生自 C# 的可视化树的节点VisualElement类。您可以设置外观样式、定义行为并将其作为 UI 的一部分显示在屏幕上。更多信息
请参阅术语表到可视树。UI Toolkit 使用VisualElement.Add()方法将子项添加到现有视觉元素,并通过rootvisualElement财产。
地方脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
请参阅术语表用于 Editor 文件夹中的自定义编辑器窗口,因为 Unity 需要它来进行正确的编译和程序集管理。有关详细信息,请参阅保留文件夹名称参考。
Editor,在“项目”窗口中创建一个。Editor文件夹,然后选择>编辑器窗口>创建 UI 工具包。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);
从菜单中,选择 窗口(Window) > UI 工具包(UI Toolkit) > 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;
}
从菜单中,选择 窗口(Window) > UI 工具包(UI Toolkit) > MyCustomEditor 以打开自定义编辑器窗口。该窗口显示可滚动的列表视图和可选择的项目,如下图所示。
要在从列表中选择精灵时在右侧面板上显示精灵的图像,请使用selectionChanged属性,并添加回调函数。
若要显示图像,请为所选精灵创建新的图像控件,然后使用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功能。这将从窗格中清除所有以前的内容,获取选定的精灵,并添加新的图像控件来显示精灵。
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);
}
}
}
从菜单中,选择 窗口(Window) > UI 工具包(UI Toolkit) > 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);
...
}
从菜单中,选择 窗口(Window) > UI 工具包(UI Toolkit) > MyCustomEditor 以打开自定义编辑器窗口。精灵浏览器窗口现在有滚动条。调整窗口大小以查看滚动条的运行情况。
当脚本重新编译或编辑器进入播放模式时,会发生C#域重新加载。在刚刚创建的 编辑器(Editor) 窗口中,打开精灵浏览器,选择精灵,然后进入 播放模式(Play mode)。窗口将重置,选择将消失。
一个合适的编辑器窗口需要与热重载工作流程配合使用。因为VisualElement对象不可序列化,则每次重新加载时都必须重新创建 UI。这意味着CreateGUI()重新加载完成后调用方法。这样,您就可以在重新加载之前还原 UI 状态,方法是将必要的数据存储在EditorWindow类。
将成员变量添加到MyCustomEditorclass 将选定的索引保存在精灵列表中。进行选择时,此成员变量存储 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; };
}
从菜单中,选择 窗口(Window) > UI 工具包(UI Toolkit) > 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);
}
}
}
}