Version: 6000.3
语言: 中文
创建宽高比自定义控件
样式 UI

创建自定义库存属性抽屉

您可以创建自定义属性抽屉一种 Unity 功能,允许你使用脚本上的属性或通过控制特定 Serializable 类的外观
来自定义检查器窗口中某些控件的外观更多信息 请参阅术语表
以自定义自定义控件的 UXML 属性的外观和行为,以在检查器一个 Unity 窗口,显示有关当前选定游戏对象、资产或项目设置的信息,允许您检查和编辑值。更多信息
请参阅术语表
UI Builder 的面板。

SerializedFields 支持自定义属性抽屉,类似于 ScriptableObjectMonoBehaviour。您可以将自定义属性抽屉应用于类型或字段。

示例概述

此示例创建一个清单系统,其中包括Item类、各种项目和Inventory类。这Inventory类在视觉元素实例化或派生自 C# 的可视化树的节点VisualElement类。您可以设置外观样式、定义行为并将其作为 UI 的一部分显示在屏幕上。更多信息
请参阅术语表
.该示例使用自定义属性抽屉来管理清单系统。

您可以在此 GitHub 存储库中找到此示例创建的已完成文件。

先决条件

本指南适用于熟悉 Unity 编辑器、UI 工具包和 C# 脚本的开发人员。在开始之前,请熟悉以下内容:

创建项目和各种项目

首先,创建一个Item类。此类是抽象的,用作所有类型对象的蓝图,包含它们的共享属性。接下来,创建各种物品,包括健康包和不同类型的武器。

  1. 使用任何模板在 Unity 中创建项目。

  2. “项目”窗口中,创建一个名为inventory-property-drawers以存储您的文件。

  3. inventory-property-drawers文件夹中,创建一个名为Scripts以存储 C#脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
    请参阅术语表
    .

  4. 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;
    }
    
  5. 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自定义控件来管理库存项目。

  1. 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}";
        }
    }
        
    
  2. 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 值不会自动分配。要解决此问题,请添加一个InventoryPropertyDrawerInventory类。这允许您直接在 UI Builder 中管理库存项目。请注意,添加自定义属性抽屉时,请编辑UxmlSerializedData而不是Inventoryclass 直接。

为确保Ammo类,将计数值限制为小于maxCount,创建一个AmmoPropertyDrawer对于Ammo类。

  1. 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);
        }
    }
    
  2. 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 中测试库存系统

要测试库存系统,请在 UI Builder 中添加一个Character元素并将物品添加到库存中。

  1. 从主菜单中,选择窗口>UI 工具包> UI 生成器以打开 UI 生成器窗口。
  2. 在 UI Builder 的 库(Library) 面板中,选择 项目(Project) > 自定义控件(Custom Controls) 选项卡。
  3. Character元素,从“库”面板到“层次结构”面板。
  4. “检查器”面板中,您现在可以将项目添加到库存中。ID 值是自动分配的,您可以添加预配置的项目集。
库存属性抽屉示例
库存属性抽屉示例

其他资源

创建宽高比自定义控件
样式 UI