Создание настраиваемого ящика свойств инвентаризации
Вы можете создать пользовательские ящики свойств для настройки внешнего вида и поведения атрибутов UXML пользовательского элемента управления в панели Inspector панели UI Builder.
SerializedFields поддерживает пользовательские ящики свойств, подобные ScriptableObject или MonoBehaviour. Вы можете применить пользовательский ящик свойств либо к типу, либо к полю.
Примерный обзор
В этом примере создается система инвентаризации, которая включает Item класс, набор предметов и Inventory class. The Inventory класс управляет предметами инвентаря внутри визуального элемента. В примере для работы с системой инвентаря используются пользовательские отрисовщики свойств.
Вы можете найти завершенные файлы, созданные этим примером, в хранилище GitHub.
Предварительные условия
Это руководство предназначено для разработчиков, знакомых со скриптами Unity Editor, UI Toolkit и C#. Перед началом работы ознакомьтесь со следующим:
- UXML
CustomPropertyDrawerUxmlObjectUxmlAttributeUxmlSerializedDataCreator.CreateUxmlSerializedData
Создание элемента и разнообразия элементов
Сначала создайте Item класс. Этот класс абстрактный и служит чертежом для всех типов предметов, объединяя их общие свойства. Затем создайте разные предметы, в том числе аптечку и оружие разных типов.
Создайте проект в Unity с любым шаблоном.
В окне Project создайте папку
inventory-property-drawersдля хранения ваших файлов.В папке
inventory-property-drawersсоздайте подпапкуScriptsдля хранения ваших скриптов C#.-
В папке
Scriptsсоздайте скрипт C# с именем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создайте скрипт C# с именем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, поэтому для него необходимо определить конвертер атрибутов. Вам нужно Inventory класс для хранения всех предметов. Чтобы воспользоваться инвентарём, создайте Character пользовательский контроль для управления инвентарными позициями.
-
В папке
Scriptsсоздайте скрипт C# с именем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создайте скрипт C# с именем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 не является Inventory class directly.
Для обеспечения Ammo класс, чтобы ограничить значение счётчика так, чтобы оно было меньше maxCount, создать AmmoPropertyDrawer на 2000 год Ammo class.
-
В папке
Scriptsсоздайте скрипт C# с именем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создайте скрипт C# с именем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
Чтобы проверить систему инвентаризации, в UI Builder добавьте элемент Character и добавьте предметы в инвентаризацию.
- В главном меню выберите Окно > UI Toolkit > UI Builder, чтобы открыть окно UI Builder.
- В UI Builder, в панели Библиотека выберите вкладку Project > Custom Controls.
- Перетащите элемент
Characterиз панели Библиотека на панели Hierarchy. - В панели Inspector теперь можно добавлять предметы в инвентарь. Значение ID присваивается автоматически, и вы можете добавлять предварительно настроенные наборы предметов.