Unity3D :使用网格 API 创建径向进度指示器

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

使用网格 API 创建径向进度指示器

此示例演示如何使用网孔
用于将视觉内容绘制到一个视觉元素
.

注意:网格 API 是面向高级用户的工具。在版本 2022.1 及更高版本中,如果只想生成简单的几何图形,请改用矢量 API。有关详细信息,请参阅使用矢量 API 创建径向进度指示器。

示例概述

本示例创建一个显示进度的自定义控件,作为加载栏的替代方法。进度指示器在显示百分比的标签周围的部分填充环中显示进度值。它支持 0 到 100 之间的值,该值确定环的填充量。

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

先决条件

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

  • 用户界面生成器
  • 美国航空母舰
  • 处理事件
  • MeshGenerationContext

创建径向进度指示器及其自定义网格

创建两个 C# 文件,其中一个定义类,另一个定义自定义网格。在定义类的 C# 文件中,创建一个工厂类以向 UXML 和 UI 生成器公开控件。RadialProgressRadialProgress

  1. 使用任何模板创建 Unity 项目。
  2. 创建一个名为用于存储文件的文件夹。radial-progress
  3. 在该文件夹中,创建一个以以下内容命名的 C# 脚本:radial-progressRadialProgress.cs

using Unity.Collections;
using UnityEngine;
using UnityEngine.UIElements;

namespace MyUILibrary
{

// An element that displays progress inside a partially filled circle
public class RadialProgress : VisualElement
{
    public new class UxmlTraits : VisualElement.UxmlTraits
    {
        // The progress property is exposed to UXML.
        UxmlFloatAttributeDescription m_ProgressAttribute = new UxmlFloatAttributeDescription()
        {
            name = "progress"
        };

        // Use the Init method to assign the value of the progress UXML attribute to the C# progress property.
        public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
        {
            base.Init(ve, bag, cc);

            (ve as RadialProgress).progress = m_ProgressAttribute.GetValueFromBag(bag, cc);
        }
    }

    // Define a factory class to expose this control to UXML.
    public new class UxmlFactory : UxmlFactory<RadialProgress, UxmlTraits> { }

    // These are USS class names for the control overall and the label.
    public static readonly string ussClassName = "radial-progress";
    public static readonly string ussLabelClassName = "radial-progress__label";

    // These objects allow C# code to access custom USS properties.
    static CustomStyleProperty<Color> s_TrackColor = new CustomStyleProperty<Color>("--track-color");
    static CustomStyleProperty<Color> s_ProgressColor = new CustomStyleProperty<Color>("--progress-color");

    // These are the meshes this control uses.
    EllipseMesh m_TrackMesh;
    EllipseMesh m_ProgressMesh;

    // This is the label that displays the percentage.
    Label m_Label;

    // This is the number of outer vertices to generate the circle.
    const int k_NumSteps = 200;

    // This is the number that the Label displays as a percentage.
    float m_Progress;

    // A value between 0 and 100

    public float progress
    {
        // The progress property is exposed in C#.
        get => m_Progress;
        set
        {
            // Whenever the progress property changes, MarkDirtyRepaint() is named. This causes a call to the
            // generateVisualContents callback.
            m_Progress = value;
            m_Label.text = Mathf.Clamp(Mathf.Round(value), 0, 100) + "%";
            MarkDirtyRepaint();
        }
    }

    // This default constructor is RadialProgress's only constructor.
    public RadialProgress()
    {
        // Create a Label, add a USS class name, and add it to this visual tree.
        m_Label = new Label();
        m_Label.AddToClassList(ussLabelClassName);
        Add(m_Label);

        // Create meshes for the track and the progress.
        m_ProgressMesh = new EllipseMesh(k_NumSteps);
        m_TrackMesh = new EllipseMesh(k_NumSteps);

        // Add the USS class name for the overall control.
        AddToClassList(ussClassName);

        // Register a callback after custom style resolution.
        RegisterCallback<CustomStyleResolvedEvent>(evt => CustomStylesResolved(evt));

        // Register a callback to generate the visual content of the control.
        generateVisualContent += context => GenerateVisualContent(context);

        progress = 0.0f;
    }

    static void CustomStylesResolved(CustomStyleResolvedEvent evt)
    {
        RadialProgress element = (RadialProgress)evt.currentTarget;
        element.UpdateCustomStyles();
    }

    // After the custom colors are resolved, this method uses them to color the meshes and (if necessary) repaint
    // the control.
    void UpdateCustomStyles()
    {
        if (customStyle.TryGetValue(s_ProgressColor, out var progressColor))
        {
            m_ProgressMesh.color = progressColor;
        }

        if (customStyle.TryGetValue(s_TrackColor, out var trackColor))
        {
            m_TrackMesh.color = trackColor;
        }

        if (m_ProgressMesh.isDirty || m_TrackMesh.isDirty)
            MarkDirtyRepaint();
    }

    // The GenerateVisualContent() callback method calls DrawMeshes().
    static void GenerateVisualContent(MeshGenerationContext context)
    {
        RadialProgress element = (RadialProgress)context.visualElement;
        element.DrawMeshes(context);
    }

    // DrawMeshes() uses the EllipseMesh utility class to generate an array of vertices and indices, for both the
    // "track" ring (in grey) and the progress ring (in green). It then passes the geometry to the MeshWriteData
    // object, as returned by the MeshGenerationContext.Allocate() method. For the "progress" mesh, only a slice of
    // the index arrays is used to progressively reveal parts of the mesh.
    void DrawMeshes(MeshGenerationContext context)
    {
        float halfWidth = contentRect.width * 0.5f;
        float halfHeight = contentRect.height * 0.5f;

        if (halfWidth < 2.0f || halfHeight < 2.0f)
            return;

        m_ProgressMesh.width = halfWidth;
        m_ProgressMesh.height = halfHeight;
        m_ProgressMesh.borderSize = 10;
        m_ProgressMesh.UpdateMesh();

        m_TrackMesh.width = halfWidth;
        m_TrackMesh.height = halfHeight;
        m_TrackMesh.borderSize = 10;
        m_TrackMesh.UpdateMesh();

        // Draw track mesh first
        var trackMeshWriteData = context.Allocate(m_TrackMesh.vertices.Length, m_TrackMesh.indices.Length);
        trackMeshWriteData.SetAllVertices(m_TrackMesh.vertices);
        trackMeshWriteData.SetAllIndices(m_TrackMesh.indices);

        // Keep progress between 0 and 100
        float clampedProgress = Mathf.Clamp(m_Progress, 0.0f, 100.0f);

        // Determine how many triangles are used to depending on progress, to achieve a partially filled circle
        int sliceSize = Mathf.FloorToInt((k_NumSteps * clampedProgress) / 100.0f);

        if (sliceSize == 0)
            return;

        // Every step is 6 indices in the corresponding array
        sliceSize *= 6;

        var progressMeshWriteData = context.Allocate(m_ProgressMesh.vertices.Length, sliceSize);
        progressMeshWriteData.SetAllVertices(m_ProgressMesh.vertices);

        var tempIndicesArray = new NativeArray<ushort>(m_ProgressMesh.indices, Allocator.Temp);
        progressMeshWriteData.SetAllIndices(tempIndicesArray.Slice(0, sliceSize));
        tempIndicesArray.Dispose();
    }

}

}

4. 创建以以下内容命名的 C# 脚本:EllipseMesh.cs

using UnityEngine;
using UnityEngine.UIElements;

namespace MyUILibrary
{
public class EllipseMesh
{
int m_NumSteps;
float m_Width;
float m_Height;
Color m_Color;
float m_BorderSize;
bool m_IsDirty;
public Vertex[] vertices { get; private set; }
public ushort[] indices { get; private set; }

    public EllipseMesh(int numSteps)
    {
        m_NumSteps = numSteps;
        m_IsDirty = true;
    }

    public void UpdateMesh()
    {
        if (!m_IsDirty)
            return;

        int numVertices = numSteps * 2;
        int numIndices = numVertices * 6;

        if (vertices == null || vertices.Length != numVertices)
            vertices = new Vertex[numVertices];

        if (indices == null || indices.Length != numIndices)
            indices = new ushort[numIndices];

        float stepSize = 360.0f / (float)numSteps;
        float angle = -180.0f;

        for (int i = 0; i < numSteps; ++i)
        {
            angle -= stepSize;
            float radians = Mathf.Deg2Rad * angle;

            float outerX = Mathf.Sin(radians) * width;
            float outerY = Mathf.Cos(radians) * height;
            Vertex outerVertex = new Vertex();
            outerVertex.position = new Vector3(width + outerX, height + outerY, Vertex.nearZ);
            outerVertex.tint = color;
            vertices[i * 2] = outerVertex;

            float innerX = Mathf.Sin(radians) * (width - borderSize);
            float innerY = Mathf.Cos(radians) * (height - borderSize);
            Vertex innerVertex = new Vertex();
            innerVertex.position = new Vector3(width + innerX, height + innerY, Vertex.nearZ);
            innerVertex.tint = color;
            vertices[i * 2 + 1] = innerVertex;

            indices[i * 6] = (ushort)((i == 0) ? vertices.Length - 2 : (i - 1) * 2); // previous outer vertex
            indices[i * 6 + 1] = (ushort)(i * 2); // current outer vertex
            indices[i * 6 + 2] = (ushort)(i * 2 + 1); // current inner vertex

            indices[i * 6 + 3] = (ushort)((i == 0) ? vertices.Length - 2 : (i - 1) * 2); // previous outer vertex
            indices[i * 6 + 4] = (ushort)(i * 2 + 1); // current inner vertex
            indices[i * 6 + 5] = (ushort)((i == 0) ? vertices.Length - 1 : (i - 1) * 2 + 1); // previous inner vertex
        }

        m_IsDirty = false;
    }

    public bool isDirty => m_IsDirty;

    void CompareAndWrite(ref float field, float newValue)
    {
        if (Mathf.Abs(field - newValue) > float.Epsilon)
        {
            m_IsDirty = true;
            field = newValue;
        }
    }

    public int numSteps
    {
        get => m_NumSteps;
        set
        {
            m_IsDirty = value != m_NumSteps;
            m_NumSteps = value;
        }
    }

    public float width
    {
        get => m_Width;
        set => CompareAndWrite(ref m_Width, value);
    }

    public float height
    {
        get => m_Height;
        set => CompareAndWrite(ref m_Height, value);
    }

    public Color color
    {
        get => m_Color;
        set
        {
            m_IsDirty = value != m_Color;
            m_Color = value;
        }
    }

    public float borderSize
    {
        get => m_BorderSize;
        set => CompareAndWrite(ref m_BorderSize, value);
    }

}

}

在 UI 文档中使用自定义控件并进行测试

创建一个 USS 文件以设置径向进度指示器自定义控件的样式。使用 UI 生成器添加控件并应用 USS 样式表。使用不同的值测试控件。Progress

  1. 创建一个以以下内容命名的 USS 文件:RadialProgress.uss

.radial-progress {
min-width: 26px;
min-height: 20px;
--track-color: rgb(130, 130, 130);
--progress-color: rgb(46, 132, 24);
--percentage-color: white;
margin-left: 5px;
margin-right: 5px;
margin-top: 5px;
margin-bottom: 5px;
flex-direction: row;
justify-content: center;
width: 100px;
height: 100px;
}

.radial-progress__label {
-unity-text-align: middle-left;
color: var(--percentage-color);
}

2. 创建名为 的 UI 文档。RadialProgressExample.uxml

3. 双击以在 UI 生成器中将其打开。RadialProgressExample.uxml

4. 在“库”窗口中,选择“项目>自定义控件”>“MyUILibrary”。

5. 将“径向进度”拖到“层次结构”窗口中。

6. 在 UI 生成器的样式表部分中,添加为现有 USS。RadialProgress.uss

7. 在“层次结构”窗口中,选择“径向进度”。

8. 在检查员
窗口中,在“名称”框中输入。radial-progress

9. 在“检查器”窗口中,在“进度”框中输入不同的值。百分比视窗
更改,绿色进度环将调整大小。

在运行时使用进度指示器

现场
,并创建一个 C# MonoBehavior 脚本,以使用动态值更新控件的属性,以便进行演示。Progress

  1. 在该文件夹中,创建一个以以下内容命名的 C# 脚本:radial-progressRadialProgressComponent.cs

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

[RequireComponent(typeof(UIDocument))]
public class RadialProgressComponent : MonoBehaviour
{

RadialProgress m_RadialProgress;

void Start()
{
    var root = GetComponent<UIDocument>().rootVisualElement;

    m_RadialProgress = new RadialProgress() {
        style = {
            position = Position.Absolute,
            left = 20, top = 20, width = 200, height = 200
        }
    };

    root.Add(m_RadialProgress);
}

void Update()
{
    // For demo purpose, give the progress property dynamic values.
    m_RadialProgress.progress = ((Mathf.Sin(Time.time) + 1.0f) / 2.0f) * 60.0f + 10.0f;
}

}

2. 在 Unity 中,选择“游戏对象”> UI 工具包>“UI 文档”。

3. 在“层次结构”窗口中选择 UIDocument

4. 添加 RadialProgressComponent.cs 作为 UIDocument 的组件游戏对象

5. 进入播放模式。进度指示器出现在场景中,进度环和值动态变化。

3D建模学习工作室整理翻译,转载请注明出处!

上一篇:Unity3D :创建滑动切换自定义控件 (mvrlink.com)

下一篇:Unity3D :创建可绑定的自定义控件 (mvrlink.com)

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号