包含此页的版本:
不含此页的版本:
您可以创建自定义属性抽屉一种 Unity 功能,允许你使用脚本上的属性或通过控制特定 Serializable 类的外观
来自定义检查器窗口中某些控件的外观更多信息 请参阅术语表以自定义自定义控件的 UXML 属性的外观和行为,以在检查器一个 Unity 窗口,显示有关当前选定游戏对象、资产或项目设置的信息,允许您检查和编辑值。更多信息
请参阅术语表UI Builder 的面板。
SerializedFields 支持自定义属性抽屉,类似于 ScriptableObject 或 MonoBehaviour。您可以将自定义属性抽屉应用于类型或字段。
此示例创建一个清单系统,其中包括Item类、各种项目和Inventory类。这Inventory类在视觉元素实例化或派生自 C# 的可视化树的节点VisualElement类。您可以设置外观样式、定义行为并将其作为 UI 的一部分显示在屏幕上。更多信息
请参阅术语表.该示例使用自定义属性抽屉来管理清单系统。
您可以在此 GitHub 存储库中找到此示例创建的已完成文件。
本指南适用于熟悉 Unity 编辑器、UI 工具包和 C# 脚本的开发人员。在开始之前,请熟悉以下内容:
CustomPropertyDrawerUxmlObjectUxmlAttributeUxmlSerializedDataCreator.CreateUxmlSerializedData首先,创建一个Item类。此类是抽象的,用作所有类型对象的蓝图,包含它们的共享属性。接下来,创建各种物品,包括健康包和不同类型的武器。
使用任何模板在 Unity 中创建项目。
在“项目”窗口中,创建一个名为inventory-property-drawers以存储您的文件。
在inventory-property-drawers文件夹中,创建一个名为Scripts以存储 C#脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
请参阅术语表.
在Scripts文件夹中,创建一个名为Item.cs内容如下:
using UnityEngine.UIElements;
using UnityEngine;
[UxmlObject]
public abstract partial class Item
{
[UxmlAttribute, HideInInspector]
public int id;
[UxmlAttribute]
public string name;
[UxmlAttribute]
public float weight;
}
在Scripts文件夹中,创建一个名为HealthPack.cs内容如下:
using System;
using UnityEngine;
using UnityEngine.UIElements;
[UxmlObject]
public partial class HealthPack : Item
{
[UxmlAttribute]
public float healAmount = 100;
public HealthPack()
{
name = "Health Pack";
}
}
[UxmlObject]
public partial class Sword : Item
{
[UxmlAttribute, Range(1, 100)]
public float slashDamage;
}
[Serializable]
public class Ammo
{
public int count;
public int maxCount;
}
[UxmlObject]
public partial class Gun : Item
{
[UxmlAttribute]
public float damage;
[UxmlAttribute]
public Ammo ammo = new Ammo { count = 10, maxCount = 10 };
}
此示例使用名为Ammo,因此,您必须为其定义属性转换器。您需要一个Inventoryclass 来存储所有项目。要使用库存,请创建一个Character自定义控件来管理库存项目。
在Scripts文件夹中,创建一个名为AmmoConverter.cs内容如下:
using UnityEditor.UIElements;
public class AmmoConverter : UxmlAttributeConverter<Ammo>
{
public override Ammo FromString(string value)
{
var ammo = new Ammo();
var values = value.Split('/');
if (values.Length == 2)
{
int.TryParse(values[0], out ammo.count);
int.TryParse(values[1], out ammo.maxCount);
}
return ammo;
}
public override string ToString(Ammo value)
{
return $"{value.count}/{value.maxCount}";
}
}
在Scripts文件夹中,创建一个名为Inventory.cs内容如下:
using System.Collections.Generic;
using UnityEngine.UIElements;
[UxmlObject]
public partial class Inventory
{
List<Item> m_Items = new List<Item>();
Dictionary<int, Item> m_ItemDictionary = new Dictionary<int, Item>();
[UxmlAttribute]
int nextItemId = 1;
[UxmlObjectReference("Items")]
public List<Item> items
{
get => m_Items;
set
{
m_Items = value;
m_ItemDictionary.Clear();
foreach (var item in m_Items)
{
m_ItemDictionary[item.id] = item;
}
}
}
public Item GetItem(int id) => m_ItemDictionary.TryGetValue(id, out var item) ? item : null;
}
当您添加Character元素添加到 UI Builder 中,您可以管理库存项目,但 ID 值不会自动分配。要解决此问题,请添加一个InventoryPropertyDrawer到Inventory类。这允许您直接在 UI Builder 中管理库存项目。请注意,添加自定义属性抽屉时,请编辑UxmlSerializedData而不是Inventoryclass 直接。
为确保Ammo类,将计数值限制为小于maxCount,创建一个AmmoPropertyDrawer对于Ammo类。
在Scripts文件夹中,创建一个名为InventoryPropertyDrawer.cs内容如下:
// When you add a UxmlObject to the inventory list, include an instance of UxmlSerializedData, not an Item.
// To simplify this process, this example uses `UxmlSerializedDataCreator.CreateUxmlSerializedData`,
// a utility method that creates a UxmlObject’s UxmlSerializedData with default values.
//
// In this approach, the assignment of an ID value is introduced. To manage this, the last used ID value is stored
// within the element as a hidden field labeled `nextItemId`. Additionally, buttons are incorporated to add preconfigured
// sets of items. For instance, a Soldier might receive a Rifle, Machete, and Performance Pack.
using UnityEditor;
using UnityEngine.UIElements;
using UnityEngine;
using UnityEditor.UIElements;
[CustomPropertyDrawer(typeof(Inventory.UxmlSerializedData))]
public class InventoryPropertyDrawer : PropertyDrawer
{
SerializedProperty m_InventoryProperty;
SerializedProperty m_ItemsProperty;
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
m_InventoryProperty = property;
var root = new VisualElement();
m_ItemsProperty = property.FindPropertyRelative("items");
var items = new ListView
{
showAddRemoveFooter = true,
showBorder = true,
showFoldoutHeader = false,
reorderable = true,
virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight,
reorderMode = ListViewReorderMode.Animated,
bindingPath = m_ItemsProperty.propertyPath,
overridingAddButtonBehavior = OnAddItem
};
root.Add(items);
var addSniperGear = new Button(() =>
{
AddGun("Rifle", 4.5f, 33, 30, 30);
AddSword("Knife", 0.5f, 7);
AddHealthPack();
m_InventoryProperty.serializedObject.ApplyModifiedProperties();
});
addSniperGear.text = "Add Sniper Gear";
var addWarriorGear = new Button(() =>
{
AddGun("Rifle", 4.5f, 33, 30, 30);
AddHealthPack();
AddSword("Machete", 1, 11);
m_InventoryProperty.serializedObject.ApplyModifiedProperties();
});
addWarriorGear.text = "Add Warrior Gear";
var addMedicGear = new Button(() =>
{
AddGun("Pistol", 1.5f, 10, 15, 15);
AddHealthPack();
AddHealthPack();
AddHealthPack();
m_InventoryProperty.serializedObject.ApplyModifiedProperties();
});
addMedicGear.text = "Add Medic Gear";
root.Add(addSniperGear);
root.Add(addWarriorGear);
root.Add(addMedicGear);
root.Bind(property.serializedObject);
return root;
}
void AddGun(string name, float weight, float damage, int ammo, int maxAmmo)
{
m_ItemsProperty.arraySize++;
var newItem = m_ItemsProperty.GetArrayElementAtIndex(m_ItemsProperty.arraySize - 1);
newItem.managedReferenceValue = UxmlSerializedDataCreator.CreateUxmlSerializedData(typeof(Gun));
newItem.FindPropertyRelative("id").intValue = NextItemId();
newItem.FindPropertyRelative("name").stringValue = name;
newItem.FindPropertyRelative("weight").floatValue = weight;
newItem.FindPropertyRelative("damage").floatValue = damage;
var ammoInstance = newItem.FindPropertyRelative("ammo");
ammoInstance.FindPropertyRelative("count").intValue = ammo;
ammoInstance.FindPropertyRelative("maxCount").intValue = maxAmmo;
}
void AddSword(string name, float weight, float damage)
{
m_ItemsProperty.arraySize++;
var newItem = m_ItemsProperty.GetArrayElementAtIndex(m_ItemsProperty.arraySize - 1);
newItem.managedReferenceValue = UxmlSerializedDataCreator.CreateUxmlSerializedData(typeof(Sword));
newItem.FindPropertyRelative("id").intValue = NextItemId();
newItem.FindPropertyRelative("name").stringValue = name;
newItem.FindPropertyRelative("weight").floatValue = weight;
newItem.FindPropertyRelative("slashDamage").floatValue = damage;
}
void AddHealthPack()
{
m_ItemsProperty.arraySize++;
var newItem = m_ItemsProperty.GetArrayElementAtIndex(m_ItemsProperty.arraySize - 1);
newItem.managedReferenceValue = UxmlSerializedDataCreator.CreateUxmlSerializedData(typeof(HealthPack));
newItem.FindPropertyRelative("id").intValue = NextItemId();
}
int NextItemId() => m_InventoryProperty.FindPropertyRelative("nextItemId").intValue++;
void OnAddItem(BaseListView baseListView, Button button)
{
var menu = new GenericMenu();
var items = TypeCache.GetTypesDerivedFrom<Item>();
foreach (var item in items)
{
if (item.IsAbstract)
continue;
menu.AddItem(new GUIContent(item.Name), false, () =>
{
m_ItemsProperty.arraySize++;
var newItem = m_ItemsProperty.GetArrayElementAtIndex(m_ItemsProperty.arraySize - 1);
newItem.managedReferenceValue = UxmlSerializedDataCreator.CreateUxmlSerializedData(item);
newItem.FindPropertyRelative("id").intValue = NextItemId();
m_InventoryProperty.serializedObject.ApplyModifiedProperties();
});
}
menu.DropDown(button.worldBound);
}
}
在Scripts文件夹中,创建一个名为AmmoPropertyDrawer.cs内容如下:
// Note that this example creates a PropertyDrawer for the Ammo type because it's not a UxmlObject.
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
[CustomPropertyDrawer(typeof(Ammo))]
public class AmmoPropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var root = new VisualElement { style = { flexDirection = FlexDirection.Row } };
var count = property.FindPropertyRelative("count");
var maxCount = property.FindPropertyRelative("maxCount");
var ammoField = new IntegerField("Ammo") { isDelayed = true, bindingPath = count.propertyPath };
ammoField.TrackPropertyValue(count, p =>
{
count.intValue = Mathf.Min(p.intValue, maxCount.intValue);
property.serializedObject.ApplyModifiedProperties();
});
root.Add(ammoField);
root.Add(new Label("/"));
var countField = new IntegerField { isDelayed = true, bindingPath = maxCount.propertyPath };
countField.TrackPropertyValue(maxCount, p =>
{
count.intValue = Mathf.Min(p.intValue, count.intValue);
property.serializedObject.ApplyModifiedProperties();
});
root.Add(countField);
root.Bind(property.serializedObject);
return root;
}
}
要测试库存系统,请在 UI Builder 中添加一个Character元素并将物品添加到库存中。
Character元素,从“库”面板到“层次结构”面板。