Version: 6000.3
语言: 中文
UI 工具包运行时示例
使用文本

在运行时移动元素

此示例演示如何动态设置视觉元素实例化或派生自 C# 的可视化树的节点VisualElement类。您可以设置外观样式、定义行为并将其作为 UI 的一部分显示在屏幕上。更多信息
请参阅术语表
在运行时。在运行时移动元素的推荐最佳实践是使用style.translate并将DynamicTransform移动元素上的用法提示。这种方法对于性能来说是最佳的,因为它可以避免弄脏布局,并将更新限制在转换阶段。有关更多信息,请参阅在运行时优化移动元素的性能

示例概述

此示例创建了一个预制件:一种资产类型,允许您存储包含组件和属性的游戏对象。预制件充当模板,你可以从中在场景中创建新的对象实例。更多信息
请参阅术语表
场景场景包含游戏的环境和菜单。将每个唯一的场景文件视为一个独特的关卡。在每个场景中,你放置你的环境、障碍物和装饰品,基本上是将你的游戏设计和构建成碎片。更多信息
请参阅术语表
.预制件表示在指定范围内随机移动的非玩家角色 (NPC)。NPC 后面有一个名称标签,并根据与相机在场景中创建特定视点图像的组件。输出要么绘制到屏幕上,要么作为纹理捕获。更多信息
请参阅术语表
.

在运行时移动元素
在运行时移动元素

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

先决条件

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

创建 UI 容器

在项目中创建一个UXML,并在其中定义一个VisualElement。VisualElment 充当您计划在运行时动态移动的任何子元素的父容器。

  1. 使用任何模板创建 Unity 项目。
  2. 项目窗口一个窗口,显示您的内容Assets文件夹(项目选项卡)更多信息
    术语表中查看
    ,创建一个名为UI以存储 UXML 和 USS 文件。
  3. UI文件夹中,创建一个名为NameTagContainer.uxml.
  4. 双击NameTagContainer.uxml文件以在 UI Builder 中打开它。
  5. 将 VisualElement 添加到“层次结构”面板,并将其命名为BaseContainer.
  6. 保存您的更改。

完成的NameTagContainer.uxml文件如下所示:

<engine:UXML xmlns:engine="UnityEngine.UIElements" >
    <engine:VisualElement name="BaseContainer" style="flex-grow: 1;" />
</engine:UXML>

创建名称标签UXML模板

创建一个 USS 文件来设置名称标签的样式,并创建一个 UXML 文件来定义名称标签模板。名称标签模板是一个 Label 元素,用于显示 NPC 的名称。名称标签跟随场景中的 NPC。

  1. UI文件夹中,创建一个名为NameTag.uss内容如下:

     #NameTag {
         /* This sets the position of the Label element to be absolute, rather than relative to the parent container. */
         position: absolute;
         /* This ensures that the pivot point of the Label element is centered, rather than in the top left corner.*/
         translate: -50% -50%;
         -unity-font-style: bold;
         color: rgb(181, 210, 248);
         -unity-text-outline-width: 1px;
         -unity-text-outline-color: rgb(11, 60, 123);
     }
    
  2. UI文件夹中,创建一个名为NameTag.uxml.

  3. 双击NameTag.uxml文件以在 UI Builder 中打开它。

  4. 将标签添加到“层次结构”面板并将其命名NameTag.

  5. 检查器一个Unity 窗口,显示有关当前选定游戏对象、资产或项目设置的信息,允许您检查和编辑值。更多信息
    请参阅术语表
    面板中,请执行以下作:

    • 文本设置为NPC. 此值表示 NPC 的名称。
    • 拾取模式设置为Ignore. 这可以防止 Label 元素响应鼠标事件,这对性能来说更优化。
  6. “样式表”面板中,选择“+”>“添加现有 USS”。

  7. 按照说明添加NameTag.uss文件。

  8. 检查视口用户在屏幕上应用的可见区域。
    术语表中查看
    . 这translate: -50% -50%style 将 Label 元素的枢轴点居中,使其显示在视口的左上角。这是预期行为。名称标签将相对于 NPC 定位游戏对象Unity 场景中的基本对象,可以表示角色、道具、风景、相机、航路点等。游戏对象的功能由附加到它的组件定义。更多信息
    请参阅术语表
    在场景中。

    视口中的NameTag

  9. 保存您的更改。

完成的NameTag.uxml文件可能如下所示:

<engine:UXML xmlns:engine="UnityEngine.UIElements">
    <Style src="NameTag.uss" />
    <engine:Label text="NPC" name="NameTag" picking-mode="Ignore" />
</engine:UXML>

创建随机移动脚本

创建一个RandomMovement处理 NPC 预制件随机移动的类。NPC 在指定范围内移动,并在到达边界时改变方向。

DynamicTransform移动元素上的使用提示以优化性能。

注意:建议的最佳实践是将使用提示应用于转换后的元素,在本例中是名称标记模板容器,而不是子标签。

  1. 在“项目”窗口中,创建一个名为Scripts以存储 C#脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
    请参阅术语表
    .
  2. Scripts文件夹中,创建一个名为RandomMovement.cs内容如下:
using UnityEngine;
using Random = UnityEngine.Random;

public class RandomMovement : MonoBehaviour
{
    public float moveSpeed;

    public float movementRange;

    public Vector3 targetPosition;

    private float m_PositionY;

    // Initialize the starting position and target position of the GameObject.
    void Start()
    {
        m_PositionY = transform.position.y;
        targetPosition = new Vector3(0, m_PositionY, 0);
    }

    // Updates the position of the GameObject at fixed intervals.
    // Move the GameObject towards the target position, and sets a new random target position when the current target is reached.
    void FixedUpdate()
    {
        if (transform.position != targetPosition)
        {
            transform.position = Vector3.MoveTowards(transform.position, targetPosition, moveSpeed * Time.deltaTime);
        }
        else
        {
            targetPosition = new Vector3(Random.Range(-movementRange, movementRange), m_PositionY, Random.Range(-movementRange, movementRange));
        }
    }
}

创建移动名称标签脚本

创建一个MovingNameTag管理名称标签的位置和比例的类。名称标签跟随场景中的 NPC 游戏对象。名称标签根据与摄像机的距离进行缩放,当名称标签距离太远或在摄像机后面时,该名称标签会被剔除。

  • Scripts文件夹中,创建一个名为MovingNameTag.cs内容如下:
using System;
using UnityEngine;
using UnityEngine.UIElements;

public class MovingNameTag : MonoBehaviour
{
    [SerializeField]
    VisualTreeAsset m_NameTagTemplate;

    [SerializeField]
    UIDocument m_BaseContainerDocument;

    [SerializeField]
    Transform m_UITransform;

    [SerializeField]
    float m_ScaleMultiplier;

    [SerializeField]
    float m_DistanceCullingRange;

    VisualElement m_Root;
    VisualElement m_BaseContainer;
    VisualElement m_NpcNameTag;
    
    Camera m_MainCamera;
    
    void Awake()
    {
        m_MainCamera = Camera.main;
        
        m_BaseContainer = m_BaseContainerDocument.rootVisualElement.Q<VisualElement>("BaseContainer");
        
        m_NpcNameTag = m_NameTagTemplate.Instantiate();

        // Set DynamicTransform hint on the moving element to optimize performance.
        m_NpcNameTag.usageHints = UsageHints.DynamicTransform;
        m_BaseContainer.Add(m_NpcNameTag);
        m_NpcNameTag.style.position = new StyleEnum<Position>(Position.Absolute);
    }

    void Update()
    {
        SetNameTagPositionAndScale();
    }

    void SetNameTagPositionAndScale()
    {
        var cameraSpaceLocation = GetCameraSpaceLocation(m_UITransform);
        
        // Use style.translate to set the position of the name tag.
        m_NpcNameTag.style.translate = new Translate(cameraSpaceLocation.x, cameraSpaceLocation.y);

        // Get distance of NPC from camera.
        var distance = Vector3.Distance(m_UITransform.position, m_MainCamera.transform.position);
        
        // Calculate 1/distance so the name tag get smaller as the distance gets bigger.
        var scale = 1 / distance * m_ScaleMultiplier;

        m_NpcNameTag.style.scale = new Scale(new Vector2(scale, scale));
        
        // Display name tag based on whether it's in front of the camera and within culling range.
        if (cameraSpaceLocation.z < 0 || distance > m_DistanceCullingRange)
        {
            m_NpcNameTag.style.display = DisplayStyle.None;
        }
        else
        {
            m_NpcNameTag.style.display = DisplayStyle.Flex;
        }
    }

    Vector3 GetCameraSpaceLocation(Transform objectTransform)
    {
        // Get the size of the parent visual element of the name tag.
        var containerSize = m_BaseContainer.layout.size;
        var screenPoint = m_MainCamera.WorldToViewportPoint(objectTransform.position);
        var output = new Vector3(screenPoint.x * containerSize.x, (1 - screenPoint.y) * containerSize.y, screenPoint.z);
        
        return output;
    }
}

创建排序元素脚本

创建一个SortElements类,根据名称标签的比例对名称标签进行排序,该比例基于它们所遵循的物体的距离。

  • Scripts文件夹中,创建一个名为SortElements.cs内容如下:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;

public class SortElements : MonoBehaviour
{
    [SerializeField]
    UIDocument m_MovingElements;

    VisualElement m_BaseContainer;

    MovingNameTag[] m_MovingNameTags;
    
    void Start()
    {
        m_MovingNameTags = FindObjectsByType<MovingNameTag>(FindObjectsSortMode.None);
        m_BaseContainer = m_MovingElements.rootVisualElement.Q<VisualElement>("BaseContainer");
    }

    void Update()
    {
        m_BaseContainer.Sort(CompareOrder);
    }

    static int CompareOrder(VisualElement x, VisualElement y)
    {
        // Compare the scale of the visual elements in the base container, which is
        // determined by the distance of the object it follows in the MovingNameTag component
        return x.style.scale.value.value.x.CompareTo(y.style.scale.value.value.x);
    }
}

设置场景基础知识

使用地板和 UI 文档游戏对象设置场景。地板是表示场景地板的平面游戏对象

UI 文档游戏对象是包含运行时移动的视觉元素的 UI。UI 包含SortElements组件,它根据名称标签的比例对名称标签进行排序。

  1. 在 SampleScene 中,添加名为Floor.

  2. 将材质添加到地板。

  3. 在“层次结构”窗口中,选择“UI 工具包”>“UIDocument”,并将其命名为UI.

  4. 在 UI 文档游戏对象的 Inspector 窗口中,执行以下作:

    • 源资产(Source Asset) 设置为NameTagContainer.
    • 添加SortElements元件。
  5. “对元素排序”组件字段中,将“移动元素”设置为UI.这使得SortElements组件引用 UI 文档组件,而 UI 文档组件又引用NameTagContainer源资产。

设置 NPC 预制件

创建一个在指定范围内随机移动的 NPC 预制件。它包含三个子游戏对象:

  • 表示 NPC 的胶囊游戏对象
  • 名为UI_NameTag代表 NPC 后面的名牌
  • 名为UI_Transform表示姓名标签的位置

UI_TransformGameObject 充当名称标签的参考点。 这MovingNameTag组件根据UI_TransformGameObject 中。这种设置允许更好的组织和灵活性。如果您需要调整名称标签的位置,只需修改UI_TransformGameObject 的位置,而不会影响 NPC GameObject 或其他组件。

  1. 在 SampleScene 中,创建一个名为NPC.

  2. Inspector 窗口中NPCGameObject,添加RandomMovement元件。 该组件管理 NPC 的随机移动。

  3. 在“随机移动”组件字段中,执行以下作:

  4. 速度设置为2. 该值决定了 NPC 移动的速度。

  5. 范围设置为10. 该值决定了 NPC 移动的范围。

注意随机移动组件包含一个目标位置一个关节属性,用于设置关节的驱动力应将其移动到的目标位置。更多信息
请参阅术语表
字段,表示 NPC 的下一个目标位置,在脚本中随机设置。该字段主要用于调试目的,不需要手动设置。

设置 NPC 预制件的子项

NPC 预制件包含三个子游戏对象:一个胶囊游戏对象和两个游戏对象。

Capsule GameObject 表示场景中的 NPC。一个 GameObject 表示 NPC 后面的名称标签。第二个 GameObject 表示名称标签相对于 NPC 的位置。此 GameObject 充当名称标签的参考点或锚点,定义名称标签相对于 NPC 的位置。

  1. 右键单击NPCGameObject 并选择“创建空”以添加子游戏对象并将其命名为UI_NameTag.

  2. 添加MovingNameTag组件添加到UI_NameTagGameObject 的 GameObject 中。此组件管理名称标签的位置和比例。

  3. 在“移动名称标记”组件字段中,执行以下作:

    • “名称标签模板”设置为NameTag.
    • 基本容器文档设置为UI (UI Document).此值表示在运行时移动的 UI 元素的容器。
    • UI 转换设置为UI_Transform.此值表示名称标签的位置。
    • 比例乘数(Scale Multiplier) 设置为50.此值根据与摄像机的距离缩放名称标签。
    • 距离剔除范围(Distance Culling Range) 设置为20.当名称标签距离太远或在摄像机后面时,此值会剔除名称标签。
  4. 右键单击NPCGameObject,然后选择 3D Object > Capsule 以添加子 Capsule GameObject。然后将其 Y 位置设置为1这样它就不会夹在地板平面上。

  5. 将所需的材质添加到 Capsule 游戏对象,例如蓝色表面材质(如果有)。

  6. 右键单击NPCGameObject 并选择“创建空”以添加另一个空子游戏对象并将其命名UI_Transform.

  7. Inspector 窗口中UI_TransformGameObject,选择右上角的箭头图标,然后为其选择一个图标。此图标可让您轻松识别场景视图:您正在创建的世界的交互式视图。您可以使用场景视图来选择和定位场景、角色、摄像机、灯光和所有其他类型的游戏对象。更多信息
    请参阅术语表
    用于调试目的。

    设置UI_Transform图标

  8. Inspector 窗口中UI_TransformGameObject,将位置设置为(0, 3, 0).此值表示名称标签相对于 NPC 游戏对象的位置。

测试示例

复制 NPC 预制件以在场景中创建多个 NPC。名称标签跟随 NPC,并根据与摄像机的距离进行缩放。

  1. “层级”窗口中,右键单击NPCGameObject 并选择“复制”
  2. 重复前面的步骤几次,在场景中创建多个NPC。
  3. 将 NPC 移动到场景中的不同位置。
  4. 进入播放模式。名称标签跟随NPC,并根据与摄像机的距离进行缩放。
  5. 暂停游戏。
  6. 选择“窗口”>“UI 工具包>调试器”以打开“UI 工具包调试器”窗口。
  7. “UI 工具包调试器”窗口中,从“选择面板”列表中选择“面板设置”。
  8. 选择 PanelRootElement > TemplateContainer > VisualElement > TemplateContainer 以查看名称标记的样式属性。
  9. 取消暂停游戏。名称标签的翻译属性会随着 NPC 的移动而动态更新。

其他资源

UI 工具包运行时示例
使用文本