您现在的位置是:首页 >其他 >Unity3D :创建您的第一个运行时 UI网站首页其他

Unity3D :创建您的第一个运行时 UI

yevtte2023 2024-06-17 10:43:02
简介Unity3D :创建您的第一个运行时 UI

ygtu

May 16, 2023 • 27 min read

推荐:将 NSDT场景编辑器 加入你的3D工具链
3D工具集: NSDT简石数字孪生

创建您的第一个运行时 UI

本页将指导您完成使用 UI 工具包设置简单字符选择屏幕的步骤。它涵盖了 UI 元素和模板的创建、场景设置以及如何将脚本逻辑连接到 UI。本指南不会介绍通过 USS 设置样式,并且仅使用默认样式和主题。

您可以在本页底部此处找到本指南的最终源代码。

主题:UI 生成器、列表视图、标签、面板设置、UIDocument、选择处理

本指南将指导您完成以下步骤:

  • 创建主 UI 视图
  • 设置场景
  • 创建要显示的示例数据
  • 为主视图创建控制器
  • 创建列表条目 UI 模板
  • 为列表条目创建控制器
  • 对用户选择做出反应

创建主 UI 视图

最终的 UI 屏幕由两个单独的 UI 模板 (UXML) 组成。主视图模板包含包含字符名称的列表、用于显示所选字符详细信息的较小面板以及一个按钮。在本节中,您将使用 UI 生成器设置此模板。

注意:如果您熟悉 UI 生成器并希望跳过此步骤,则可以从此页面底部复制主视图的 UXML 代码,并将其直接粘贴到新文件中。将其另存为 Assets/UI/MainView.uxml

主视图的 UI 布局设置

通过菜单“UI Toolkit> UI Builder”打开“UI 生成器”窗口>“UI 生成器”窗口。使用视口左上角的文件菜单创建新的 UXML 文档。

UI 生成器文件菜单

开发游戏 UI 时,请始终确保选择 UI 生成器视口右上角的 。编辑器和运行时主题之间的默认字体大小和颜色不同,这会影响布局。Unity Default Runtime Theme

通过从库中拖动来创建新元素

层次结构中选择新的 UXML 文件,然后启用匹配游戏视图复选框。您可能需要将 Unity 编辑器设置为横向分辨率(如果尚未设置)。

启用比赛游戏视图

现在是时候创建 UI 元素了! 通过将新的可视元素从拖到层次结构中来创建新的可视元素。

通过从库中拖动来创建新元素

新元素需要覆盖整个屏幕,因此需要将属性设置为 1。从层次结构中选择元素,然后在右侧的“检查器”面板中找到标记为 Flex 的折叠页。将“增长”的值从 0 更改为 1。flex-grow

设置 Flex 属性

若要将此可视元素的所有子项居中置于屏幕中间,请更改可视元素的“对齐”属性。您需要将“对齐项目”和“内容对齐”都设置为居中

居中儿童

最后,您可以在“背景>颜色”下选择一种背景颜色。此步骤是可选的。此示例用作颜色。#732526

根元素背景色

接下来,在现有可视元素下创建一个新的可视元素。这将成为 UI 左侧和右侧部分的父容器。

添加子可视元素

将此新元素的属性设置为 (默认为列)。您还需要将固定高度设置为 350 像素。flex-directionrow

中心容器属性

这就是当前 UI 的外观。请注意,您的屏幕可能看起来会有所不同,具体取决于游戏视图的分辨率和纵横比。

内部有空元素的背景容器

若要为字符名称创建列表,请从库中选择一个 ListView 控件,并将其作为子项添加到刚创建的可视元素下。选择元素并在检查器中为其指定名称。这是必需的,以便您稍后可以通过控制器脚本访问此列表。CharacterList

内部有空元素的背景容器

将列表设置为固定宽度为 230 像素。还要在右侧给它一个 6 px 宽的边距,以便与要创建的下一个元素保持一定距离。

字符列表的大小和边距折叠

您还可以为列表指定背景颜色并设置圆角边框。本指南用于背景和边框颜色,边框宽为 4px,半径为 15px。此步骤是可选的。#6E3925#311A11

样式字符列表

在与 相同的父级下添加新的可视元素。这将包含角色详细信息面板和按钮。在“对齐”折叠式下,将“项目对齐”“内容对齐”的设置更改为 。CharacterListflex-endspace-between

证明内容属性的合理性

将新的可视元素添加到此新容器。这将成为角色详细信息面板。当用户从左侧列表中选择一个角色时,它将显示该角色的肖像、名称和类。

为元素设置 276 像素的固定宽度,并将“对齐项目”和“内容对齐”切换为居中。还要为元素添加一个 8 像素宽的填充,以便子项与容器边界保持最小距离。

字符详细信息容器的属性

您可以通过将背景颜色设置为 4px 宽边框和 15px 半径的边框颜色来设置面板样式。此步骤是可选的。#AA5939#311A11

您的 UI 布局现在应类似于下图。

空字符详细信息面板

接下来,您将各个 Ui 控件添加到字符详细信息中。首先是人物肖像。它由两个元素组成 - 背景中的帧和前景中的图像。

首先将新的可视元素添加到背景帧的字符详细信息容器中。为其分配 120x120 像素的固定大小和 4 像素的填充,以便包含的图像不会直接接触边框。

您可以使用 2px 宽、半径为 15px 的边框(颜色为 和背景色)来设置元素样式。随意应用您自己的颜色和样式。#311A11#FF8554

人物肖像的背景框架

对于实际图像,将新的可视元素作为子元素添加到刚创建的框架中。为其命名,以便以后可以在控制器脚本中访问它。CharacterPortrait

“Flex > Grow”设置为 1,以便图像利用所有可用空间。此外,请确保将“背景>缩放模式”下的缩放模式更改为 ,以便可以放大或缩小图像以匹配元素大小,同时保持正确的纵横比。scale-to-fit

纵向图像的可视元素

接下来,将两个标签控件添加到字符详细信息容器,稍后将使用这些控件显示所选字符的名称和类。将它们命名为 和 。CharacterNameCharacterClass

为名称和类添加标签

若要使字符的名称比类更突出,请将标签的字体大小更改为 18,并将样式设置为体。

更改字体设置

您的 UI 屏幕现在应类似于下图。

完成的角色详细信息面板

最后,将按钮控件添加到右侧 UI 容器。稍后,您将在控制器脚本中访问此按钮,并在选择或取消选择字符时启用或禁用它。命名按钮并为其指定 150px 的固定宽度。还应将按钮的标签文本设置为 。SelectCharButtonSelect Character

用于字符选择的“添加”按钮

要设置按钮样式,请将背景色设置为 ,并将 2px 边框设置为颜色为 。此步骤是可选的。#FF8554#311A11

完成的主视图应类似于下图。

最终主视图布局

将 UXML 模板另存为 Assets/UI/MainView.uxml。 您还可以在此处的页面底部找到此模板的最终 UXML 代码。

设置场景

在本节中,你将了解如何在运行时在游戏中加载和显示你在上一节中创建的 UI 模板。

要开始,您需要创建一个面板设置资产。此资产将定义屏幕的设置,例如缩放模式和渲染顺序。它还将确定 UI 在 UI 工具包调试器中显示的名称。

创建新的面板设置资源

通过在项目视图中单击鼠标右键来创建新的。选择创建> UI 工具包>面板设置资产。将新创建的文件命名为 。 对于本指南,您可以将所有设置保留为其默认值。Panel Settings AssetGameUI_Panel

无需更改默认面板设置

要显示上一节中的主视图 UI 模板,您需要在场景中创建新的游戏对象。将 UIDocument 组件附加到它。

在 Unity 中进入播放模式时,将自动加载分配的内容。A 是一个 UXML 模板。将面板设置和新面板设置都分配给组件。UIDocumentVisualTreeAssetVisualTreeAssetMainView.uxmlGameUI_Panel

UI 文档组件

注意:如果未将资源分配给 UI 文档组件,它将自动搜索项目并使用自动找到的第一个面板设置资源。重命名或移动资产时请记住这一点。PanelSettings

现在,您可以在 Unity 编辑器中进入运行模式,并在游戏视图中查看您的 UI。

运行时显示的 UI

: 如果场景中有多个 UI 文档,则可以将同一面板设置资源分配给所有文档。这将导致所有 UI 呈现在同一面板上,从而优化性能。

创建要显示的示例数据

在本节中,你将创建一些示例数据,这些数据将用于用数据填充 UI 中的字符列表。

对于字符列表,您需要一个简单的类,其中包含角色名称、类和肖像图像。 创建新的 ScriptableObject 脚本 Assets/Scripts/CharacterData.cs并将以下代码粘贴到文件中:

using UnityEngine;

public enum ECharacterClass
{
    Knight, Ranger, Wizard
}

[CreateAssetMenu]
public class CharacterData : ScriptableObject
{
    public string m_CharacterName;
    public ECharacterClass m_Class;
    public Sprite m_PortraitImage;
}

该属性将自动向“创建”菜单添加一个条目。 右键单击“项目”视图中的文件夹以创建新的脚本化对象的实例。[CreateAssetMenu]

新建“创建”菜单项

现在,您需要创建一些实例并用随机数据填充它们。将它们全部放在资源/字符文件夹中。稍后您将编写一个脚本,该脚本会自动解析并加载此文件夹中的所有字符数据。CharacterData

创建一些示例字符

创建列表条目 UI 模板

在本节中,您将为列表中的各个条目创建 UI 模板。在运行时,控制器脚本将为每个字符创建此 UI 的实例,并将其添加到列表中。 字符列表条目的 UI 由彩色背景框架和字符名称组成。

显示字符名称的列表条目

注意:如果要跳过此步骤,可以从此页面底部复制列表条目的UXML代码,并将其直接粘贴到新文件中。将其另存为 Assets/UI/ListEntry.uxml

通过菜单“UI Toolkit> UI Builder”打开“UI 生成器”窗口>“UI 生成器”窗口。 通过选择“新建文件”>创建新的 UXML 模板。

在 UI 生成器中创建新的 UXML 模板

为背景添加一个视觉元素,并将固定高度设置为 41px。由于条目内的文本应左对齐并放置在元素的中间,因此请打开“对齐”折叠页并将“项目左对齐”和内容对齐”设置为居中。 还要设置 10px 的左边距,以使标签与框架左边框的距离最小。

对于样式,您可以使用背景色并添加 2px 宽的边框,半径为 15px,颜色为 .此步骤是可选的,您可以应用自己的颜色和样式。#AA5939#311A11

背景视觉元素

将标签作为子项添加到现有可视元素并命名 ,以便稍后可以在控制器脚本中访问它。将“字体样式”设置为体,将“字体大小”设置为 18。CharacterName

为角色名称添加标签

将 UXML 模板另存为 Assets/UI/ListEntry.uxml。 您还可以在此处的页面底部找到此模板的最终 UXML 代码。

为列表条目创建控制器

在本节中,您将为列表条目创建控制器脚本。该脚本的目的是在列表条目的 UI 中显示字符实例的数据。它需要访问字符名称的标签,并将其设置为显示给定字符实例的名称。

创建一个新脚本 Assets/Scripts/UI/CharacterListEntryController.cs并将以下代码粘贴到其中:

using UnityEngine.UIElements;

public class CharacterListEntryController
{
    Label m_NameLabel;

    public void SetVisualElement(VisualElement visualElement)
    {
        m_NameLabel = visualElement.Q<Label>("CharacterName");
    }

    public void SetCharacterData(CharacterData characterData)
    {
        m_NameLabel.text = characterData.m_CharacterName;
    }
}

此类中有两个函数,它们都是函数。Set

SetVisualElement(VisualElement visualElement)此函数将接收一个可视元素,该元素是您在上一节中创建的 UI 模板的实例。主视图控制器将创建此实例。此函数的目的是检索对 UI 元素内字符名称标签的引用。ListEntry

SetCharacterData(CharacterData characterData)此函数接收此列表元素应显示其名称的字符。由于 中的元素列表是池化和重用的,因此必须有一个函数来更改要显示的角色的数据。ListViewSet

请注意,该类不是 .由于 UI 工具包中的可视元素不是游戏对象,因此无法将组件附加到它们。相反,此类将附加到下一节中的属性。CharacterListEntryMonoBehaviouruserData

为主视图创建控制器

在本节中,您将为主视图中的字符列表创建一个控制器脚本,以及一个实例化并将其分配给可视化树的 MonoBehavior 脚本。

首先,在 Assets/Scripts/UI/CharacterListController 下创建一个新脚本.cs并将以下代码粘贴到其中。

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class CharacterListController
{
    public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
    {
    }
}

稍后将填充该方法,但现在添加空方法很重要,以便可以在下一节中调用它。InitializeCharacterList()

将控制器脚本附加到主视图

就像 一样,the 不是 ,需要以不同的方式附加到可视化树。 您需要创建一个 MonoBehavior 脚本,该脚本可以附加到与 相同的游戏对象。它将实例化并将其附加到可视化树。CharacterListEntryControllerCharacterListControllerMonoBehaviourUIDocumentCharacterListController

创建一个新脚本 Assets/Scripts/UI/MainView.cs并将以下代码粘贴到其中:

using UnityEngine;
using UnityEngine.UIElements;

public class MainView : MonoBehaviour
{
    [SerializeField]
    VisualTreeAsset m_ListEntryTemplate;

    void OnEnable()
    {
        // The UXML is already instantiated by the UIDocument component
        var uiDocument = GetComponent<UIDocument>();

        // Initialize the character list controller
        var characterListController = new CharacterListController();
        characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
    }
}

进入 Unity 编辑器,将脚本附加到 所在的同一游戏对象。将 分配给“列表条目模板”属性。UIDocumentListEntry.uxml

添加主视图脚本并分配引用

脚本组件无需实例化 MainView UXML,因为这是在同一游戏对象的组件中自动完成的。该脚本访问 UIDocument 组件以获取已实例化的可视化树的引用。然后,它会创建可视化树的根元素和用于各个列表元素的 UXML 模板的 和 的实例。UIDocumentMainViewCharacterListController

注意:重新加载 UI 时,包含该组件的同一游戏对象上的配套 MonoBehavior 组件将在重新加载之前被禁用,然后在重新加载后重新启用。因此,最好将与 UI 交互的代码放置在这些 MonoBehavior 的 and 方法中。UIDocumentOnEnableOnDisable

枚举所有字符数据实例

应添加到控制器脚本的第一个功能是枚举之前创建的所有字符数据实例的函数。这些将用于填写列表。

将下面的代码复制到类中。CharacterListController

List<CharacterData> m_AllCharacters;

void EnumerateAllCharacters()
{
    m_AllCharacters = new List<CharacterData>();
    m_AllCharacters.AddRange(Resources.LoadAll<CharacterData>("Characters"));
}

注意:此代码假定您在“资源/字符”文件夹中创建了字符实例。如果将字符放在其他文件夹中,则可能需要相应地调整文件夹名称。

现在,您需要在初始化期间调用该方法。将对其的调用添加到方法的顶部:EnumerateAllCharacterInitializeCharacterList

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
    EnumerateAllCharacters();
}

获取对 UI 元素的引用

在本节中,您将填写该方法的内容。此方法需要做的第一件事是获取对它访问以显示信息所需的所有单个 UI 控件的引用。使用 UQuery 系列 API 按名称、USS 类、类型或这些控件的组合检索各个 UI 控件。InitializeCharacterList

使用以下代码扩展类中的代码:CharacterListController

// UXML template for list entries
VisualTreeAsset m_ListEntryTemplate;

// UI element references
ListView m_CharacterList;
Label m_CharClassLabel;
Label m_CharNameLabel;
VisualElement m_CharPortrait;
Button m_SelectCharButton;

public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
{
    EnumerateAllCharacters();

    // Store a reference to the template for the list entries
    m_ListEntryTemplate = listElementTemplate;

    // Store a reference to the character list element
    m_CharacterList = root.Q<ListView>("CharacterList");

    // Store references to the selected character info elements
    m_CharClassLabel = root.Q<Label>("CharacterClass");
    m_CharNameLabel = root.Q<Label>("CharacterName");
    m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

    // Store a reference to the select button
    m_SelectCharButton = root.Q<Button>("SelectCharButton");
}

用条目填充列表

接下来,您需要用之前枚举和加载的字符填充屏幕上的列表。为此,您需要在类中创建一个新方法。FillCharacterListCharacterListController

用元素填充列表视图需要 4 个步骤:

  1. 创建函数makeItem
  2. 创建函数bindItem
  3. 设置项目高度
  4. 设置项目源

makeItem 回调函数的目的是创建一个小的可视化树,该树表示单个列表项的 UI,并返回此树的根可视元素。

在这种情况下,回调需要实例化您为列表条目创建的 UXML 模板。它还需要创建控制器脚本的实例,该实例负责使用来自 .makeItemCharacterListEntryControllerCharacterData

在类内创建一个方法并粘贴下面的代码。FillCharacterList

void FillCharacterList()
{
    // Set up a make item function for a list entry
    m_CharacterList.makeItem = () =>
    {
        // Instantiate the UXML template for the entry
        var newListEntry = m_ListEntryTemplate.Instantiate();

        // Instantiate a controller for the data
        var newListEntryLogic = new CharacterListEntryController();

        // Assign the controller script to the visual element
        newListEntry.userData = newListEntryLogic;
    
        // Initialize the controller script
        newListEntryLogic.SetVisualElement(newListEntry);

        // Return the root of the instantiated visual tree
        return newListEntry;
    };
}

作为回调的一部分,将控制器脚本存储在实例化可视元素的属性中。这允许您稍后访问脚本并为列表元素分配不同的字符。makeItemuserData

// Assign the controller script to the visual element
newListEntry.userData = newListEntryLogic;

作为内存和性能优化,重用列表元素,而不是为列表中的每个条目实例化一个元素。它只创建足够的视觉元素来填充可见区域,然后在滚动列表时汇集并重复使用它们。ListView

出于这个原因,你需要提供一个 bindItem 回调,它将你的一个实例(在本例中)绑定到一个单独的列表元素。CharacterData

通过在底部添加下面的代码来扩展该方法。FillCharacterList

// Set up bind function for a specific list entry
m_CharacterList.bindItem = (item, index) =>
{
    (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
};

回调接收对列表条目的可视化树的根可视元素的引用,以及数据的索引。由于您在可视元素的属性中存储了对 的引用,因此代码可以访问它并直接设置 .bindItemCharacterListEntryControlleruserDataCharacterData

最后,您需要设置元素的项高度,并为列表提供对数据源的引用。这告诉列表它包含多少元素。

通过在底部添加下面的代码来扩展该方法。FillCharacterList

// Set a fixed item height
m_CharacterList.fixedItemHeight = 45;

// Set the actual item's source list/array
m_CharacterList.itemsSource = m_AllCharacters;

最后,您需要在初始化结束时调用该方法。 将调用添加到方法底部,如下所示:FillCharacterListInitializeCharacterList

FillCharacterList();

如果您现在进入播放模式,角色列表将填满您创建的角色的名称。

字符列表不再为空

您可以在本指南底部找到脚本的最终代码,此处CharacterListController

对用户选择做出反应

当用户选择角色时,角色的详细信息 - 即肖像,全名和类别 - 需要显示在屏幕右侧的角色详细信息部分中。此外,选择字符时,需要启用选择按钮。如果未选择任何字符,该按钮应再次禁用。

请注意,您已经可以单击并选择列表中的字符。用于选择和突出显示的功能是 ListView 控件的一部分。您只需要添加一个回调函数,以便在用户更改列表中的选择时做出反应。该控件包含用于此目的的事件:ListViewonSelectionChange

将以下代码添加到方法的底部:InitializeCharacterList

// Register to get a callback when an item is selected
m_CharacterList.onSelectionChange += OnCharacterSelected;

现在,您需要实现在上面的代码中设置的回调函数。此函数将接收列表中所有选定项目的列表。但是,由于列表仅允许选择单个项,因此可以直接通过列表的 selectedItem 属性访问所选项。OnCharacterSelected

将以下代码复制到您的类中:

void OnCharacterSelected(IEnumerable<object> selectedItems)
{
    // Get the currently selected item directly from the ListView
    var selectedCharacter = m_CharacterList.selectedItem as CharacterData;
}

该属性可能返回 null。如果未选择任何内容,或者用户按下该键取消选择所有内容,则会出现这种情况。这个案子需要先处理。selectedItemESC

扩展方法,如下所示:OnCharacterSelected

void OnCharacterSelected(IEnumerable<object> selectedItems)
{
    // Get the currently selected item directly from the ListView
    var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

    // Handle none-selection (Escape to deselect everything)
    if (selectedCharacter == null)
    {
        // Clear
        m_CharClassLabel.text = "";
        m_CharNameLabel.text = "";
        m_CharPortrait.style.backgroundImage = null;

        // Disable the select button
        m_SelectCharButton.SetEnabled(false);

        return;
    }
}

如果选择有效,则需要在UI中显示角色的详细信息。可以通过在类的方法中检索到的引用来访问标签和纵向图像视觉元素。InitializeCharacterList

将下面的代码复制到方法中:OnCharacterSelected

// Fill in character details
m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
m_CharNameLabel.text = selectedCharacter.m_CharacterName;
m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

// Enable the select button
m_SelectCharButton.SetEnabled(true);

您现在可以进入播放模式并查看您的角色选择列表。按键取消选择字符。Escape

最终运行时 UI

最终脚本

您可以在下面找到本指南中创建的所有文件的完整源代码。

MainView.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement style="flex-grow: 1; align-items: center; justify-content: center; background-color: rgb(115, 37, 38);">
        <ui:VisualElement style="flex-direction: row; height: 350px;">
            <ui:ListView focusable="true" name="CharacterList" style="width: 230px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-left-width: 4px; border-right-width: 4px; border-top-width: 4px; border-bottom-width: 4px; background-color: rgb(110, 57, 37); border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; margin-right: 6px;" />
            <ui:VisualElement style="justify-content: space-between; align-items: flex-end;">
                <ui:VisualElement style="align-items: center; background-color: rgb(170, 89, 57); border-left-width: 4px; border-right-width: 4px; border-top-width: 4px; border-bottom-width: 4px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px; width: 276px; justify-content: center; padding-left: 8px; padding-right: 8px; padding-top: 8px; padding-bottom: 8px;">
                    <ui:VisualElement style="border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; height: 120px; width: 120px; border-top-left-radius: 13px; border-bottom-left-radius: 13px; border-top-right-radius: 13px; border-bottom-right-radius: 13px; padding-left: 4px; padding-right: 4px; padding-top: 4px; padding-bottom: 4px; background-color: rgb(255, 133, 84);">
                        <ui:VisualElement name="CharacterPortrait" style="flex-grow: 1; -unity-background-scale-mode: scale-to-fit;" />
                    </ui:VisualElement>
                    <ui:Label text="Label" name="CharacterName" style="-unity-font-style: bold; font-size: 18px;" />
                    <ui:Label text="Label" display-tooltip-when-elided="true" name="CharacterClass" style="margin-top: 2px; margin-bottom: 8px; padding-top: 0; padding-bottom: 0;" />
                </ui:VisualElement>
                <ui:Button text="Select Character" display-tooltip-when-elided="true" name="SelectCharButton" style="width: 150px; border-left-color: rgb(49, 26, 17); border-right-color: rgb(49, 26, 17); border-top-color: rgb(49, 26, 17); border-bottom-color: rgb(49, 26, 17); background-color: rgb(255, 133, 84); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px;" />
            </ui:VisualElement>
        </ui:VisualElement>
    </ui:VisualElement>
</ui:UXML>

ListEntry.uxml

<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
    <ui:VisualElement style="height: 41px; align-items: flex-start; justify-content: center; padding-left: 10px; background-color: rgba(170, 89, 57, 255); border-left-color: rgba(49, 26, 17, 255); border-right-color: rgba(49, 26, 17, 255); border-top-color: rgba(49, 26, 17, 255); border-bottom-color: rgba(49, 26, 17, 255); border-left-width: 2px; border-right-width: 2px; border-top-width: 2px; border-bottom-width: 2px; border-top-left-radius: 15px; border-bottom-left-radius: 15px; border-top-right-radius: 15px; border-bottom-right-radius: 15px;">
        <ui:Label text="Label" display-tooltip-when-elided="true" name="CharacterName" style="-unity-font-style: bold; font-size: 18px;" />
    </ui:VisualElement>
</ui:UXML>

字符数据.cs

using UnityEngine;

public enum ECharacterClass
{
    Knight, Ranger, Wizard
}

[CreateAssetMenu]
public class CharacterData : ScriptableObject
{
    public string m_CharacterName;
    public ECharacterClass m_Class;
    public Sprite m_PortraitImage;
}

字符列表入口控制器.cs

using UnityEngine.UIElements;

public class CharacterListEntryController
{
    Label m_NameLabel;

    public void SetVisualElement(VisualElement visualElement)
    {
        m_NameLabel = visualElement.Q<Label>("CharacterName");
    }

    public void SetCharacterData(CharacterData characterData)
    {
        m_NameLabel.text = characterData.m_CharacterName;
    }
}

主视图.cs

using UnityEngine;
using UnityEngine.UIElements;

public class MainView : MonoBehaviour
{
    [SerializeField]
    VisualTreeAsset m_ListEntryTemplate;

    void OnEnable()
    {
        // The UXML is already instantiated by the UIDocument component
        var uiDocument = GetComponent<UIDocument>();

        // Initialize the character list controller
        var characterListController = new CharacterListController();
        characterListController.InitializeCharacterList(uiDocument.rootVisualElement, m_ListEntryTemplate);
    }
}

字符列表控制器.cs

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class CharacterListController
{
    // UXML template for list entries
    VisualTreeAsset m_ListEntryTemplate;

    // UI element references
    ListView m_CharacterList;
    Label m_CharClassLabel;
    Label m_CharNameLabel;
    VisualElement m_CharPortrait;
    Button m_SelectCharButton;

    public void InitializeCharacterList(VisualElement root, VisualTreeAsset listElementTemplate)
    {
        EnumerateAllCharacters();

        // Store a reference to the template for the list entries
        m_ListEntryTemplate = listElementTemplate;

        // Store a reference to the character list element
        m_CharacterList = root.Q<ListView>("CharacterList");

        // Store references to the selected character info elements
        m_CharClassLabel = root.Q<Label>("CharacterClass");
        m_CharNameLabel = root.Q<Label>("CharacterName");
        m_CharPortrait = root.Q<VisualElement>("CharacterPortrait");

        // Store a reference to the select button
        m_SelectCharButton = root.Q<Button>("SelectCharButton");

        FillCharacterList();

        // Register to get a callback when an item is selected
        m_CharacterList.onSelectionChange += OnCharacterSelected;
    }

    List<CharacterData> m_AllCharacters;

    void EnumerateAllCharacters()
    {
        m_AllCharacters = new List<CharacterData>();
        m_AllCharacters.AddRange(Resources.LoadAll<CharacterData>("Characters"));
    }

    void FillCharacterList()
    {
        // Set up a make item function for a list entry
        m_CharacterList.makeItem = () =>
        {
            // Instantiate the UXML template for the entry
            var newListEntry = m_ListEntryTemplate.Instantiate();

            // Instantiate a controller for the data
            var newListEntryLogic = new CharacterListEntryController();

            // Assign the controller script to the visual element
            newListEntry.userData = newListEntryLogic;

            // Initialize the controller script
            newListEntryLogic.SetVisualElement(newListEntry);

            // Return the root of the instantiated visual tree
            return newListEntry;
        };

        // Set up bind function for a specific list entry
        m_CharacterList.bindItem = (item, index) =>
        {
            (item.userData as CharacterListEntryController).SetCharacterData(m_AllCharacters[index]);
        };

        // Set a fixed item height
        m_CharacterList.fixedItemHeight = 45;

        // Set the actual item's source list/array
        m_CharacterList.itemsSource = m_AllCharacters;
    }

    void OnCharacterSelected(IEnumerable<object> selectedItems)
    {
        // Get the currently selected item directly from the ListView
        var selectedCharacter = m_CharacterList.selectedItem as CharacterData;

        // Handle none-selection (Escape to deselect everything)
        if (selectedCharacter == null)
        {
            // Clear
            m_CharClassLabel.text = "";
            m_CharNameLabel.text = "";
            m_CharPortrait.style.backgroundImage = null;

            // Disable the select button
            m_SelectCharButton.SetEnabled(false);

            return;
        }

        // Fill in character details
        m_CharClassLabel.text = selectedCharacter.m_Class.ToString();
        m_CharNameLabel.text = selectedCharacter.m_CharacterName;
        m_CharPortrait.style.backgroundImage = new StyleBackground(selectedCharacter.m_PortraitImage);

        // Enable the select button
        m_SelectCharButton.SetEnabled(true);
    }
}
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。