【Unity3D】UI Toolkit数据动态绑定

1 前言

​ 本文将实现 cvs 表格数据与 UI Toolkit 元素的动态绑定。

​ 如果读者对 UI Toolkit 不是太了解,可以参考以下内容。

​ 本文完整资源见→UI Toolkit数据动态绑定

2 数据动态绑定案例

2.1 UI 搭建

​ 样式和 UI 层级结构如下。

​ MainLayout.xml

XML 复制代码
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
    <Style src="project://database/Assets/Role/View/StyleSheets/RoleStyle.uss?fileID=7433441132597879392&amp;guid=d93d80f270ec5014c90e97cc8c404d1f&amp;type=3#RoleStyle" />
    <ui:VisualElement name="Background" style="flex-grow: 1; background-image: url('project://database/Assets/Role/Img/Background_Sky.png?fileID=2800000&amp;guid=02ebb0e77ccd96143911134d6e39e1db&amp;type=3#Background_Sky'); padding-left: 4%; padding-right: 4%; padding-top: 4%; padding-bottom: 4%; -unity-background-scale-mode: scale-and-crop;">
        <ui:Label text="Game Role" display-tooltip-when-elided="true" name="TitleLab" style="height: 10%; margin-bottom: 1%; -unity-text-align: middle-left; font-size: 100px; -unity-font-style: italic; color: rgb(34, 34, 34);" />
        <ui:VisualElement name="Body" style="flex-grow: 1; flex-direction: row;">
            <ui:VisualElement name="RoleTemplate" style="flex-basis: 25%; margin-left: 10px; margin-right: 10px; margin-top: 10px; margin-bottom: 10px; background-color: rgba(0, 0, 0, 0.2); border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-top-right-radius: 10px; border-bottom-right-radius: 10px;">
                <ui:VisualElement name="Image" style="flex-basis: 50%; margin-left: 5%; margin-right: 5%; margin-top: 5%; margin-bottom: 0; background-color: rgba(0, 0, 0, 0.39); border-top-left-radius: 10px; border-top-right-radius: 10px; background-image: url('project://database/Assets/Role/Img/Avatar_1.png?fileID=2800000&amp;guid=95b3aee3bc9bae64f8b70aba356b50b1&amp;type=3#Avatar_1'); -unity-background-scale-mode: scale-and-crop;" />
                <ui:Label text="角色" display-tooltip-when-elided="true" name="NameLab" style="margin-left: 3%; margin-right: 3%; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; background-color: rgb(255, 96, 96); border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-top-right-radius: 10px; border-bottom-right-radius: 10px; flex-shrink: 1; font-size: 35px; -unity-text-align: middle-center; color: rgb(255, 254, 254);" />
                <ui:VisualElement name="Properties" style="flex-grow: 1; margin-left: 5%; margin-right: 5%; margin-top: 0; margin-bottom: 5%; background-color: rgba(0, 0, 0, 0.39); border-bottom-right-radius: 10px; border-bottom-left-radius: 10px;">
                    <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                        <ui:Label text="等级" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                        <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                    </ui:VisualElement>
                    <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                        <ui:Label text="行动力" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                        <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                    </ui:VisualElement>
                    <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                        <ui:Label text="最大HP" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                        <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                    </ui:VisualElement>
                    <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                        <ui:Label text="最大MP" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                        <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                    </ui:VisualElement>
                    <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                        <ui:Label text="攻击力" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                        <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                    </ui:VisualElement>
                    <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                        <ui:Label text="防御力" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                        <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                    </ui:VisualElement>
                </ui:VisualElement>
            </ui:VisualElement>
        </ui:VisualElement>
    </ui:VisualElement>
</ui:UXML>

​ RoleStyle.uss

css 复制代码
#RoleTemplate:hover {
    transition-duration: 0.1s;
    translate: 0 -20px;
    border-left-width: 5px;
    border-right-width: 5px;
    border-top-width: 5px;
    border-bottom-width: 5px;
    border-left-color: rgb(248, 242, 242);
    border-right-color: rgb(248, 242, 242);
    border-top-color: rgb(248, 242, 242);
    border-bottom-color: rgb(248, 242, 242);
}

#Property Label {
    font-size: 25px;
    color: rgba(0, 0, 0, 255);
    -unity-text-align: middle-center;
    -unity-font-style: bold;
}

​ 显示效果如下。

2.2 创建模板

​ 在 Hierarchy 窗口选中 RoleTemplate 元素,右键弹出菜单,选择 Create Template,选择 Resources 目录下保存 RoleTemplate.uxml,修改 Grow 为 1。

​ RoleTemplate.xml

XML 复制代码
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement name="RoleTemplate" style="flex-basis: 25%; margin-left: 10px; margin-right: 10px; margin-top: 10px; margin-bottom: 10px; background-color: rgba(0, 0, 0, 0.2); border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-top-right-radius: 10px; border-bottom-right-radius: 10px; flex-grow: 1;">
        <ui:VisualElement name="Image" style="flex-basis: 50%; margin-left: 5%; margin-right: 5%; margin-top: 5%; margin-bottom: 0; background-color: rgba(0, 0, 0, 0.39); border-top-left-radius: 10px; border-top-right-radius: 10px; background-image: url('project://database/Assets/Role/Img/Avatar_1.png?fileID=2800000&amp;guid=95b3aee3bc9bae64f8b70aba356b50b1&amp;type=3#Avatar_1'); -unity-background-scale-mode: scale-and-crop;" />
        <ui:Label text="角色" display-tooltip-when-elided="true" name="NameLab" style="margin-left: 3%; margin-right: 3%; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0; background-color: rgb(255, 96, 96); border-top-left-radius: 10px; border-bottom-left-radius: 10px; border-top-right-radius: 10px; border-bottom-right-radius: 10px; flex-shrink: 1; font-size: 35px; -unity-text-align: middle-center; color: rgb(255, 254, 254);" />
        <ui:VisualElement name="Properties" style="flex-grow: 1; margin-left: 5%; margin-right: 5%; margin-top: 0; margin-bottom: 5%; background-color: rgba(0, 0, 0, 0.39); border-bottom-right-radius: 10px; border-bottom-left-radius: 10px;">
            <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                <ui:Label text="等级" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
            </ui:VisualElement>
            <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                <ui:Label text="行动力" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
            </ui:VisualElement>
            <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                <ui:Label text="最大HP" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
            </ui:VisualElement>
            <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                <ui:Label text="最大MP" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
            </ui:VisualElement>
            <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                <ui:Label text="攻击力" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
            </ui:VisualElement>
            <ui:VisualElement name="Property" style="flex-direction: row; margin-left: 5px; margin-right: 5px; margin-top: 5px; margin-bottom: 5px; flex-grow: 0; justify-content: center;">
                <ui:Label text="防御力" display-tooltip-when-elided="true" name="Name" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(185, 251, 192); border-top-left-radius: 10px; border-bottom-left-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
                <ui:Label text="1" display-tooltip-when-elided="true" name="Value" style="flex-basis: 50%; flex-shrink: 1; background-color: rgb(255, 200, 200); border-top-right-radius: 10px; border-bottom-right-radius: 10px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;" />
            </ui:VisualElement>
        </ui:VisualElement>
    </ui:VisualElement>
</ui:UXML>

​ 保存模板后,删除 Hierarchy 窗口中的 RoleTemplate 元素,后面会通过脚本加载 RoleTemplate。

2.3 自定义元素

​ RoleView.cs

cs 复制代码
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
 
public class RoleView : VisualElement {
    // 便于在UI Builder中导入自定义UI, 需要有无参构造函数
    public new class UxmlFactory : UxmlFactory<RoleView> {}
    private TemplateContainer container; // 模板容器
    private List<VisualElement> properties; // 角色属性

    public RoleView() {
        container = Resources.Load<VisualTreeAsset>("RoleTemplate").Instantiate();
        container.style.flexGrow = 1;
        hierarchy.Add(container);
        properties = container.Query("Property").ToList();
    }

    public RoleView(RoleData roleData) : this() {
        userData = roleData;
        UpdateRoleData();
        container.RegisterCallback<MouseDownEvent>(OnClick);
    }

    private void OnClick(MouseDownEvent mouseDownEvent) { // 单击角色模板回调函数
        RoleData roleData = (RoleData) userData;
        if (mouseDownEvent.button == 0) { // 按下鼠标左键
            roleData.RoleLevel++;
        } else if (mouseDownEvent.button == 1) { // 按下鼠标右键
            roleData.RoleLevel--;
        }
        UpdateRoleData();
    }

    private void UpdateRoleData() { // 更新角色数据
        RoleData roleData = (RoleData) userData;
        container.Q<VisualElement>("Image").style.backgroundImage = roleData.RoleImage;
        container.Q<Label>("NameLab").text = roleData.RoleName;
        SetProperty(properties[0], roleData.RoleLevel);
        SetProperty(properties[1], roleData.LevelData.initiative);
        SetProperty(properties[2], roleData.LevelData.maxHp);
        SetProperty(properties[3], roleData.LevelData.maxMp);
        SetProperty(properties[4], roleData.LevelData.attack);
        SetProperty(properties[5], roleData.LevelData.defense);
    }

    private void SetProperty(VisualElement property, int value) { // 更新角色属性
        property.Q<Label>("Value").text = value.ToString();
    }
}

2.4 自定义数据

​ LevelData.cs

cs 复制代码
public class LevelData { // 等级属性数据
    public int initiative; // 主动权(行动力/速度)
    public int maxHp; // 最大生命值
    public int maxMp; // 最大魔法值
    public int attack; // 攻击力
    public int defense; // 防御力
}

​ RoleData.cs

cs 复制代码
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = ("RoleData"), fileName = ("RoleData_"))]
public class RoleData : ScriptableObject { // 角色属性数据
    private const int roleMaxLevel = 10; // 最大等级
    [SerializeField]
    private TextAsset levelDataFile; // 等级数据csv文件
    [SerializeField]
    private Texture2D roleImage; // 角色头像
    [SerializeField]
    private string roleName; // 角色名
    [SerializeField, Range(1, roleMaxLevel)]
    private int roleStartLevel = 1; // 角色开始等级
    [SerializeField]
    private List<LevelData> levelDatas; // 等级数据
    private int roleLevel; // 角色当前等级

    public Texture2D RoleImage => roleImage; // 获取角色头像

    public string RoleName => roleName; // 获取角色名

    public int RoleLevel { // 获取/设置角色等级
        get => roleLevel;
        set {
            if (roleLevel == value || value < 1 || value > roleMaxLevel) {
                return;
            }
            roleLevel = value;
        }
    }

    public LevelData LevelData => levelDatas[roleLevel - 1]; // 获取角色等级数据

    private void OnEnable() {
        roleLevel = roleStartLevel;
    }

    private void OnValidate() {
        if (levelDataFile == null) {
            return;
        }
        if (levelDatas == null) {
            levelDatas = new List<LevelData>();
        }
        levelDatas.Clear();
        string[] textInLines = levelDataFile.text.Split('\n');
        for (int i = 1; i < textInLines.Length; i++) {
            string[] statsValues = textInLines[i].Split(",");
            LevelData levelData = new LevelData();
            levelData.initiative = int.Parse(statsValues[0]);
            levelData.maxHp = int.Parse(statsValues[1]);
            levelData.maxMp = int.Parse(statsValues[2]);
            levelData.attack = int.Parse(statsValues[3]);
            levelData.defense = int.Parse(statsValues[4]);
            levelDatas.Add(levelData);
        }
    }
}

​ 编译后,在 Assets 窗口右键,依次选择【Create→RoleData】,创建 4 个对象,对应 4 个角色的配置,分别重命名为 RoleData_1.asset、RoleData_2.asset、RoleData_3.asset、RoleData_4.asset。

​ 选中 ScriptableObject 配置文件后,在 Inspector 窗口配置角色属性。

​ 其中 LevelDataFile 是角色每个等级的属性 cvs 表,内容如下。

2.5 元素加载

​ RoleLoader.cs

cs 复制代码
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
 
public class RoleLoader : MonoBehaviour {
    [SerializeField]
    private List<RoleData> roleDatas; // 角色数据
    private VisualElement root; // 根容器
 
    private void Awake() {
        root = GetComponent<UIDocument>().rootVisualElement;
        var bodyContainer = root.Q("Body");
        bodyContainer.Clear();
        for(int i = 0; i < roleDatas.Count; i++) {
            RoleView roleView = new RoleView(roleDatas[i]);
            roleView.style.flexBasis = Length.Percent(25.0f);
            bodyContainer.Add(roleView);
        }
    }
}

​ 说明:RoleLoader 脚本组件挂在 UIDocument 对象上,并且需要将 RoleData_1.asset、RoleData_2.asset、RoleData_3.asset、RoleData_4.asset 赋给 RoleDatas,如下。

2.6 运行效果

​ 运行效果如下,单击卡片,角色的等级会升 1 级,等级属性也会按照 cvs 表格中的策略数据同步更新。

​ 声明:本文转自【Unity3D】UI Toolkit数据动态绑定

相关推荐
Thomas游戏开发3 天前
Unity3D事件驱动架构设计指南
前端框架·unity3d·游戏开发
古力德6 天前
Unity中造轮子:定时器
c#·unity3d
Mapmost9 天前
【数据融合实战手册·进阶篇】模型融合总出错?先看看这些“对齐”了没!
unity3d
北桥苏10 天前
如何在 Unity3D 导入 Spine 动画
unity3d
Thomas游戏开发11 天前
Unity3D状态管理器实现指南
前端框架·unity3d·游戏开发
土豆宝17 天前
Unity Visual Scripting(可视化脚本) 自定义节点 踩坑教程
unity3d
Thomas游戏开发17 天前
Unity3D光照层级与动态切换指南
前端框架·unity3d·游戏开发
Thomas游戏开发1 个月前
Unity3D 崩溃分析工具的集成与优化
前端框架·unity3d·游戏开发
Thomas游戏开发1 个月前
Unity3D网格简化与LOD技术详解
前端框架·unity3d·游戏开发
Thomas_YXQ1 个月前
Unity3D 图形渲染(Graphics & Rendering)详解
开发语言·unity·图形渲染·unity3d·shader