版本: 2023.2+
此示例演示如何使用MeshUnity 的主要图形基元。网格构成了 3D 世界的很大一部分。Unity 支持三角形或四边形多边形网格。Nurbs、Nurms、Subdiv 表面必须转换为多边形。 更多信息
查看 词汇表 API 在 视觉元素视觉树的节点,它实例化或派生自 C# VisualElement
类。可以为其设置外观、定义行为并在屏幕上显示为 UI 的一部分。 更多信息
查看 词汇表 上绘制视觉内容。
注意:Mesh API 是供高级用户使用的工具。在 2022.1 及更高版本中,如果您只想生成简单的几何图形,请使用 Vector API。有关更多信息,请参见 使用 Vector API 创建径向进度指示器。
此示例创建了一个自定义控件,它显示进度,作为进度条的替代方案。进度指示器在显示百分比的标签周围显示一个部分填充的环,以显示进度值。它支持 0 到 100 之间的值,该值决定填充了多少环。
您可以在此 GitHub 存储库 中找到此示例创建的完整文件。
本指南适用于熟悉 Unity 编辑器、UI(用户界面) 允许用户与您的应用程序交互。Unity 目前支持三种 UI 系统。 更多信息
查看 词汇表 工具包和 C# 脚本的开发人员。在开始之前,请熟悉以下内容
创建一个 C# 脚本以定义 RadialProgress
类,并将控件公开到 UXML 和 UI 生成器。
radial-progress
的文件夹来存储您的文件。radial-progress
文件夹中,使用以下内容创建一个名为 RadialProgress.cs
的 C# 脚本using Unity.Collections;
using UnityEngine;
using UnityEngine.UIElements;
namespace MyUILibrary
{
/// <summary>
/// An element that displays progress inside a partially filled circle
/// </summary>
[UxmlElement]
public partial class RadialProgress : VisualElement
{
// 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;
/// <summary>
/// A value between 0 and 100
/// </summary>
[UxmlAttribute]
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__The main graphics primitive of Unity. Meshes make up a large part of your 3D worlds. Unity supports triangulated or Quadrangulated polygon meshes. Nurbs, Nurms, Subdiv surfaces must be converted to polygons. [More info](comp-MeshGroup.html)<span class="tooltipGlossaryLink">See in [Glossary](Glossary.html#Mesh)</span>, 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 triangle 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();
}
}
}
使用以下内容创建一个名为 EllipseMesh.cs
的 C# 脚本
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);
}
}
}
使用以下内容创建一个名为 RadialProgress.uss
的 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);
}
使用 UI 生成器添加控件并应用 USS 样式表。使用不同的 Progress
值测试控件。
RadialProgressExample.uxml
的 UI 文档。RadialProgressExample.uxml
以在 UI 生成器中打开它。RadialProgress.uss
添加为现有的 USS。radial-progress
。创建一个 C# MonoBehaviour 脚本,以使用动态值(用于演示目的)更新控件的 Progress
属性。在 radial-progress
文件夹中,使用以下内容创建一个名为 RadialProgressComponent.cs
的 C# MonoBehaviour
using MyUILibrary;
using UnityEngine;
using UnityEngine.UIElements;
[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()
{
m_RadialProgress.progress = ((Mathf.Sin(Time.time) + 1.0f) / 2.0f) * 60.0f + 10.0f;
}
}