官网文档
使用 UI Toolkit 可以在 unity 中自定义一些 UI 工具,可以在运行时或编辑器状态下运行。例如,农场经营类游戏可以写一个物品编辑器 ItemEditor,卡牌类游戏可以写卡牌编辑器。
对于农场经验项目,写一个物品编辑器 (麦田物语课程) ,方便我们操作 CropDataList 数据库(scriptableobject),增、删、改物品数据。
Editor Window 创建编辑器窗口
UI Document可以用于创建物品的模板
C#
该文件是物品编辑器的主要脚本
UXML
该文件类似于 HTML 一样,描述物品编辑器的内容
双击,默认使用 UI Builder 打开编辑窗口
USS
该文件类似于 CSS ,描述物品编辑器的样式
基本操作步骤:
Library 窗口 包含各种控件,一般先用 VisualElement 做容器,里面再放置一些 Lable Button 等控件
Hierarchy 窗口 包含各个控件的层级信息
Viewport 窗口 预览当前物品编辑器的内容
《ItemEditor.cs》
public class ItemEditor : EditorWindow
{
// 在菜单栏显示物品编辑器选项
// [MenuItem("Window/UI Toolkit/ItemEditor")]
[MenuItem("My Tools/ItemEditor")]
public static void ShowExample()
{
ItemEditor wnd = GetWindow<ItemEditor>();
wnd.titleContent = new GUIContent("ItemEditor");
}
}
加载 uxml
public void CreateGUI()
{
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
// Import UXML
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/UIBuilder/ItemEditor.uxml");
VisualElement labelFromUXML = visualTree.Instantiate();
root.Add(labelFromUXML);
}
先确定 Hierarchy 窗口中各个组件的层级顺序,查找控件
查询控件,从 root 节点根据类型 ListView 和名称 ItemList ,查找对于控件
// 取得我们在物品编辑器中绘制的左边栏 listview
private ListView listView;
// 物品编辑器的右侧详情栏
private ScrollView itemDetailsSection;
...
// 找到物品编辑器中的左边栏
listView = root.Q<VisualElement>("ItemList").Q<ListView>("ListViewer");
itemDetailsSection = root.Q<ScrollView>("ItemDetails");
绑定回调,在编辑物品数据时,自动将数据写回数据库
回调的写法可以参考官方的例子
itemNameField.RegisterCallback<ChangeEvent<string>>(
evt =>
{
activeItem.itemName = evt.newValue;
// 更改名称后刷新左侧物品栏名称
listView.Rebuild();
}
);
加载物品数据
// 加载物品数据库
private void LoadDatabase()
{
// 找到的是 GUID 字符串
var dataArray = AssetDatabase.FindAssets("t:ItemListSO");
// 其他版本使用字符串 ItemListSO 也行
if (dataArray.Length > 0)
{
dataArray[0] = AssetDatabase.GUIDToAssetPath(dataArray[0]);
database = AssetDatabase.LoadAssetAtPath<ItemListSO>(dataArray[0]);
}
itemList = database.itemDetailsList;
// 为了修改文件数据,必须标记该对象
EditorUtility.SetDirty(database);
// 加载 cropData 不希望在 editor window 中修改其中内容,只读取
dataArray = AssetDatabase.FindAssets("t:CropDataList_SO");
if (dataArray.Length > 0)
{
dataArray[0] = AssetDatabase.GUIDToAssetPath(dataArray[0]);
cropDatabase = AssetDatabase.LoadAssetAtPath<CropDataList_SO>(dataArray[0]);
}
cropDetailList = cropDatabase.cropDetailsList;
}
参考例子,生成左侧物品列表
private void GenerateListView()
{
Func<VisualElement> makeItem = () => itemTemplate.CloneTree();
Action<VisualElement, int> bindItem = (e, i) =>
{
if (i < itemList.Count)
{
if (itemList[i].itemIcon != null)
{
e.Q<VisualElement>("ItemIcon").style.backgroundImage = itemList[i].itemIcon.texture;
}
e.Q<Label>("ItemName").text = itemList[i].itemName == null ? "No Item" : itemList[i].itemName;
}
};
listView.fixedItemHeight = 56;
listView.itemsSource = itemList;
listView.makeItem = makeItem;
listView.bindItem = bindItem;
listView.onSelectionChange += OnListSectionChange;
// 未选择左侧物品时,不显示右侧详情栏
itemDetailsSection.visible = false;
}
如果选择了左侧某一个物品,自动生成右侧物品详情
private void OnListSectionChange(IEnumerable<object> obj)
{
// 选中选项时传递的参数 obj 表示选中的一个或多个物体 因此取第一个物体表示当前选择的一个物体
activeItem = obj.First() as ItemDetails;
// 标记后可以执行编辑、撤销等操作
itemDetailsSection.MarkDirtyRepaint();
GetItemDetails();
itemDetailsSection.visible = true;
}