udpate 0.6.3.3

This commit is contained in:
2026-07-20 11:08:12 +08:00
parent acaa1cd57a
commit 603f5a24c2
71 changed files with 57665 additions and 2183 deletions
@@ -0,0 +1,534 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
#if ENABLE_BURST
using Unity.Jobs;
using Unity.Collections;
#endif
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 基于缓存结构的线段渲染器基类
/// 数据由 NativeArray 管理,支持脏标记范围更新和 Jobs 并行生成
/// </summary>
[RequireComponent(typeof(CanvasRenderer))]
public abstract class CacheLineRendererBase : MaskableGraphic
{
#region 数据缓存
/// <summary>
/// 所有线段端点的扁平缓存
/// </summary>
protected List<LineVertexData> m_VertexData = new List<LineVertexData>();
/// <summary>
/// 线段范围数组
/// </summary>
protected List<LineSegmentRange> m_SegmentRanges = new List<LineSegmentRange>();
/// <summary>
/// 待添加的端点(Begin/End API 模式用)
/// </summary>
protected List<LineVertexData> m_PendingVertices = new List<LineVertexData>();
/// <summary>
/// 当前 Begin/End 模式的 uvMode
/// </summary>
protected UVMode m_PendingUVMode = UVMode.ByDistance;
/// <summary>
/// 是否正在 Begin/End 会话中
/// </summary>
protected bool m_IsBuilding;
/// <summary>
/// 模型顶点缓存 (segmentCount × 5)
/// </summary>
protected List<UIVertex> m_MeshVertexCache = new List<UIVertex>();
/// <summary>
/// 三角形索引缓存
/// </summary>
protected List<int> m_MeshIndexCache = new List<int>();
/// <summary>
/// 脏标记范围起点(线段范围索引),-1 表示无脏标记
/// </summary>
protected int m_DirtyStartSegment = -1;
/// <summary>
/// 脏标记范围终点(线段范围索引)
/// </summary>
protected int m_DirtyEndSegment = -1;
/// <summary>
/// 是否使用 Jobs 模式
/// </summary>
protected static bool UseJobs =>
#if ENABLE_BURST
SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.Null &&
Application.platform != RuntimePlatform.WebGLPlayer;
#else
false;
#endif
#endregion
#region 线段添加
/// <summary>
/// 批量添加一根完整线段
/// </summary>
/// <param name="vertices">线段端点列表(至少2个)</param>
/// <param name="uvMode">UV1 均分模式</param>
/// <returns>线段范围索引</returns>
public int AddLine(LineVertexData[] vertices, UVMode uvMode = UVMode.ByDistance)
{
if (vertices == null || vertices.Length < 2)
return -1;
int startIndex = m_VertexData.Count;
m_VertexData.AddRange(vertices);
var range = new LineSegmentRange
{
startVertexIndex = startIndex,
vertexCount = vertices.Length,
uvMode = uvMode
};
int rangeIndex = m_SegmentRanges.Count;
m_SegmentRanges.Add(range);
MarkDirty(rangeIndex);
return rangeIndex;
}
/// <summary>
/// 开始画线(记录模式)
/// </summary>
public void BeginLine(UVMode uvMode = UVMode.ByDistance)
{
m_IsBuilding = true;
m_PendingUVMode = uvMode;
m_PendingVertices.Clear();
}
/// <summary>
/// 添加画线节点到当前记录
/// </summary>
public void AddLineNode(Vector2 position, Color32 color, float thickness, Vector4 userData = default)
{
if (!m_IsBuilding) return;
m_PendingVertices.Add(new LineVertexData
{
position = position,
color = color,
thickness = thickness,
userData = userData
});
}
/// <summary>
/// 添加画线节点
/// </summary>
public void AddLineNode(LineVertexData node)
{
if (!m_IsBuilding) return;
m_PendingVertices.Add(node);
}
/// <summary>
/// 结束画线,将挂起列表写入缓存
/// </summary>
public void EndLine()
{
if (!m_IsBuilding) return;
m_IsBuilding = false;
if (m_PendingVertices.Count < 2)
{
m_PendingVertices.Clear();
return;
}
int startIndex = m_VertexData.Count;
m_VertexData.AddRange(m_PendingVertices);
var range = new LineSegmentRange
{
startVertexIndex = startIndex,
vertexCount = m_PendingVertices.Count,
uvMode = m_PendingUVMode
};
int rangeIndex = m_SegmentRanges.Count;
m_SegmentRanges.Add(range);
m_PendingVertices.Clear();
MarkDirty(rangeIndex);
}
#endregion
#region 线段管理
/// <summary>
/// 移除指定线段
/// </summary>
public void RemoveLine(int segmentRangeIndex)
{
if (segmentRangeIndex < 0 || segmentRangeIndex >= m_SegmentRanges.Count)
return;
m_SegmentRanges.RemoveAt(segmentRangeIndex);
// 移除后后续所有线段索引发生变化,需完全重建
RebuildAll();
}
/// <summary>
/// 清空所有线段
/// </summary>
public void ClearAll()
{
m_VertexData.Clear();
m_SegmentRanges.Clear();
m_MeshVertexCache.Clear();
m_MeshIndexCache.Clear();
m_DirtyStartSegment = -1;
m_DirtyEndSegment = -1;
SetVerticesDirty();
}
/// <summary>
/// 标记指定线段为脏(需要重建)
/// </summary>
public void SetSegmentDirty(int segmentRangeIndex)
{
MarkDirty(segmentRangeIndex);
}
#endregion
#region 脏标记缓存
/// <summary>
/// 标记脏范围(合并到现有范围)
/// </summary>
protected void MarkDirty(int segmentIndex)
{
if (m_DirtyStartSegment < 0 || segmentIndex < m_DirtyStartSegment)
m_DirtyStartSegment = segmentIndex;
if (m_DirtyEndSegment < 0 || segmentIndex > m_DirtyEndSegment)
m_DirtyEndSegment = segmentIndex;
}
/// <summary>
/// 标记所有线段为脏(下次 LateUpdate 全量重绘)
/// 适用于:更新顶点数据后,不改变元素数量的重绘
/// </summary>
public void RebuildAll()
{
m_DirtyStartSegment = 0;
m_DirtyEndSegment = m_SegmentRanges.Count - 1;
if (m_DirtyEndSegment < 0)
m_DirtyStartSegment = -1;
}
#endregion
#region 顶点生成
protected virtual void LateUpdate()
{
if (m_DirtyStartSegment < 0)
return;
// 1) 统计脏范围内所有子段数(一个 range 有 vertexCount-1 个子段)
int totalDirtySegments = 0;
for (int ri = m_DirtyStartSegment; ri <= m_DirtyEndSegment; ri++)
totalDirtySegments += m_SegmentRanges[ri].vertexCount - 1;
if (totalDirtySegments == 0)
{
m_DirtyStartSegment = -1;
m_DirtyEndSegment = -1;
return;
}
// 2) 确保 mesh 顶点缓存足够大
int totalSegments = GetTotalSegmentCount();
int neededVertices = totalSegments * LineMeshGenerator.VerticesPerSegment;
if (m_MeshVertexCache.Count < neededVertices)
{
int growBy = neededVertices - m_MeshVertexCache.Count;
for (int i = 0; i < growBy; i++)
m_MeshVertexCache.Add(new UIVertex());
}
m_MeshIndexCache.Clear();
// 3) 按子段构建 SegmentJobInput(同时计算 ByDistance 的 totalDist 缓存)
int globalSegBase = GetGlobalSegmentOffset(m_DirtyStartSegment);
var inputs = new List<SegmentJobInput>(totalDirtySegments);
int inSegIdx = 0;
for (int ri = m_DirtyStartSegment; ri <= m_DirtyEndSegment; ri++)
{
var range = m_SegmentRanges[ri];
float cachedTotalDist = (range.uvMode == UVMode.ByDistance)
? CalculateRangeTotalDistance(range)
: -1f;
int subSegCount = range.vertexCount - 1;
for (int si = 0; si < subSegCount; si++)
{
inputs.Add(BuildSegmentInput(range, si,
globalSegBase + inSegIdx, totalSegments, cachedTotalDist));
inSegIdx++;
}
}
// 4) 生成顶点(串行或 Jobs)
if (UseJobs)
{
#if ENABLE_BURST
GenerateMeshJobs(inputs, globalSegBase);
#endif
}
else
{
GenerateMeshSerial(inputs, globalSegBase);
}
// 5) 生成三角形索引
GenerateIndicesForRange(m_DirtyStartSegment, m_DirtyEndSegment,
globalSegBase);
// 清除脏标记
SetVerticesDirty();
m_DirtyStartSegment = -1;
m_DirtyEndSegment = -1;
}
/// <summary>
/// 串行生成顶点
/// </summary>
private void GenerateMeshSerial(List<SegmentJobInput> inputs, int globalSegBase)
{
var tempVerts = new UIVertex[LineMeshGenerator.VerticesPerSegment];
for (int i = 0; i < inputs.Count; i++)
{
int meshVertStart = (globalSegBase + i) * LineMeshGenerator.VerticesPerSegment;
LineMeshGenerator.GenerateSingleSegment(inputs[i], tempVerts, 0);
for (int v = 0; v < LineMeshGenerator.VerticesPerSegment; v++)
{
int cacheIdx = meshVertStart + v;
if (cacheIdx < m_MeshVertexCache.Count)
m_MeshVertexCache[cacheIdx] = tempVerts[v];
}
}
}
#if ENABLE_BURST
/// <summary>
/// Jobs 并行生成顶点
/// </summary>
private void GenerateMeshJobs(List<SegmentJobInput> inputs, int globalSegBase)
{
int count = inputs.Count;
int outputSize = count * LineMeshGenerator.VerticesPerSegment;
var nativeInputs = new NativeArray<SegmentJobInput>(count, Allocator.TempJob);
var nativeOutputs = new NativeArray<UIVertex>(outputSize, Allocator.TempJob);
for (int i = 0; i < count; i++)
nativeInputs[i] = inputs[i];
var handle = LineMeshGenerator.GenerateSegmentsJobs(nativeInputs, nativeOutputs);
handle.Complete();
for (int i = 0; i < count; i++)
{
int srcStart = i * LineMeshGenerator.VerticesPerSegment;
int dstStart = (globalSegBase + i) * LineMeshGenerator.VerticesPerSegment;
for (int v = 0; v < LineMeshGenerator.VerticesPerSegment; v++)
{
if (dstStart + v < m_MeshVertexCache.Count)
m_MeshVertexCache[dstStart + v] = nativeOutputs[srcStart + v];
}
}
nativeInputs.Dispose();
nativeOutputs.Dispose();
}
#endif
/// <summary>
/// 生成指定 range 范围中所有子段的三角形索引
/// 每条线的首个子段不生成转角连接三角形
/// </summary>
private void GenerateIndicesForRange(int startRange, int endRange,
int globalSegBase)
{
int segIdx = globalSegBase;
for (int ri = startRange; ri <= endRange; ri++)
{
int subSegCount = m_SegmentRanges[ri].vertexCount - 1;
for (int si = 0; si < subSegCount; si++)
{
// 子段 si == 0 是该条线的首段 → 无前驱同线段 → hasPrevInLine = false
bool hasPrevInLine = (si > 0);
LineMeshGenerator.GenerateSegmentIndices(segIdx, hasPrevInLine, m_MeshIndexCache);
segIdx++;
}
}
}
/// <summary>
/// 计算某条线段包含的段数(端点数量 - 1)
/// </summary>
protected static int GetSegmentCountForRange(in LineSegmentRange range) => range.vertexCount - 1;
/// <summary>
/// 获取总段数(所有线段累加)
/// </summary>
protected int GetTotalSegmentCount()
{
int count = 0;
for (int i = 0; i < m_SegmentRanges.Count; i++)
count += GetSegmentCountForRange(m_SegmentRanges[i]);
return count;
}
/// <summary>
/// 获取指定线段范围的全局段偏移
/// </summary>
protected int GetGlobalSegmentOffset(int rangeIndex)
{
int offset = 0;
for (int i = 0; i < rangeIndex; i++)
offset += GetSegmentCountForRange(m_SegmentRanges[i]);
return offset;
}
/// <summary>
/// 计算指定 range 的总距离(用于 ByDistance UV 模式)
/// </summary>
private float CalculateRangeTotalDistance(in LineSegmentRange range)
{
float total = 0f;
int segCount = range.vertexCount - 1;
for (int i = 0; i < segCount; i++)
{
int idx = range.startVertexIndex + i;
total += Vector2.Distance(m_VertexData[idx].position,
m_VertexData[idx + 1].position);
}
return total;
}
/// <summary>
/// 构建指定范围内第 inSegmentIndex 段的输入参数
/// </summary>
protected SegmentJobInput BuildSegmentInput(
in LineSegmentRange range, int inSegmentIndex,
int globalSegmentIndex, int totalSegments,
float cachedTotalDist = -1f)
{
int startIdx = range.startVertexIndex + inSegmentIndex;
var start = m_VertexData[startIdx];
var end = m_VertexData[startIdx + 1];
var input = new SegmentJobInput
{
start = start,
end = end,
segmentIndex = globalSegmentIndex,
totalSegmentCount = totalSegments,
uvMode = range.uvMode,
hasPrev = inSegmentIndex > 0 ? (byte)1 : (byte)0,
hasNext = (inSegmentIndex < range.vertexCount - 2) ? (byte)1 : (byte)0
};
if (input.hasPrev == 1)
input.prevEnd = m_VertexData[startIdx - 1];
if (input.hasNext == 1)
input.nextStart = m_VertexData[startIdx + 2];
// 计算 startLineUV / endLineUV
int segCount = GetSegmentCountForRange(range);
if (range.uvMode == UVMode.BySegment)
{
float t0 = (float)inSegmentIndex / segCount;
float t1 = (float)(inSegmentIndex + 1) / segCount;
input.startLineUV = t0;
input.endLineUV = t1;
}
else // ByDistance
{
float totalDist = cachedTotalDist >= 0f
? cachedTotalDist
: CalculateRangeTotalDistance(range);
if (totalDist < float.Epsilon)
{
input.startLineUV = (float)inSegmentIndex / segCount;
input.endLineUV = (float)(inSegmentIndex + 1) / segCount;
}
else
{
float distSoFar = 0f;
for (int i = 0; i < inSegmentIndex; i++)
{
int idx = range.startVertexIndex + i;
distSoFar += Vector2.Distance(m_VertexData[idx].position,
m_VertexData[idx + 1].position);
}
float segDist = Vector2.Distance(start.position, end.position);
input.startLineUV = distSoFar / totalDist;
input.endLineUV = (distSoFar + segDist) / totalDist;
}
}
return input;
}
#endregion
#region OnPopulateMesh
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
if (m_MeshVertexCache.Count == 0 || m_MeshIndexCache.Count == 0)
return;
// 将缓存的顶点和索引写入 VertexHelper
vh.AddUIVertexStream(m_MeshVertexCache, m_MeshIndexCache);
}
#endregion
#region 生命周期
protected override void OnEnable()
{
base.OnEnable();
RebuildAll();
}
protected override void OnDisable()
{
base.OnDisable();
}
protected override void OnDestroy()
{
base.OnDestroy();
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad4b412c6e5cfe14194c22997929cdee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,274 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.UI;
#if ENABLE_BURST
using Unity.Burst;
using Unity.Jobs;
using Unity.Collections;
#endif
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 线段→模型顶点生成器
/// 核心算法:每段线生成 5 个模型顶点 + 3 个三角形
/// </summary>
public static class LineMeshGenerator
{
/// <summary>
/// 每段线的模型顶点数
/// </summary>
public const int VerticesPerSegment = 5;
/// <summary>
/// 每段线的三角形索引数(3 个三角形 × 3 索引)
/// </summary>
public const int IndicesPerSegment = 9;
/// <summary>
/// 计算单段线段对应的 5 个模型顶点(串行 &amp; Job 共用)
/// </summary>
/// <param name="input">线段生成参数</param>
/// <param name="outputVertices">输出顶点数组,须预留 VerticesPerSegment 个位置</param>
/// <param name="outputStart">写入 outputVertices 的起始偏移</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void GenerateSingleSegment(in SegmentJobInput input,
UIVertex[] outputVertices, int outputStart = 0)
{
Vector2 startPos = input.start.position;
Vector2 endPos = input.end.position;
Vector2 dir = endPos - startPos;
float length = dir.magnitude;
if (length < float.Epsilon)
{
// 退化为零长度线段,生成空顶点
for (int i = 0; i < VerticesPerSegment; i++)
{
outputVertices[outputStart + i] = new UIVertex
{
position = startPos,
color = Color.clear,
uv0 = Vector2.zero,
uv1 = Vector2.zero,
uv2 = Vector4.zero,
uv3 = Vector4.zero
};
}
return;
}
Vector2 normal = dir / length;
Vector2 subTangent = Vector2.Perpendicular(normal);
// 起点和终点的实际宽度(两端可不同)
float halfW0 = input.start.thickness * 0.5f;
float halfW1 = input.end.thickness * 0.5f;
// 5 顶点布局
// V0 = start + normal * -halfW (左下)
// V1 = start + normal + halfW (左上)
// V2 = end + normal * -halfW (右下)
// V3 = end + normal + halfW (右上)
// V4 = end (中心 → 转角连接)
Vector3 v0 = startPos - subTangent * halfW0;
Vector3 v1 = startPos + subTangent * halfW0;
Vector3 v2 = endPos - subTangent * halfW1;
Vector3 v3 = endPos + subTangent * halfW1;
Vector3 v4 = endPos;
Color32 c0 = Color32.Lerp(input.start.color, input.end.color, 0f);
Color32 c1 = c0;
Color32 c2 = Color32.Lerp(input.start.color, input.end.color, 1f);
Color32 c3 = c2;
Color32 c4 = c2;
float uvSX = 0f;
float uvEX = 1f;
float uvSY = input.startLineUV;
float uvEY = input.endLineUV;
UIVertex vert = new UIVertex
{
normal = Vector3.back,
tangent = new Vector4(1f, 0f, 0f, -1f)
};
Vector4 userDataStart = input.start.userData;
Vector4 userDataEnd = input.end.userData;
// V0
vert.position = v0;
vert.color = c0;
vert.uv0 = new Vector2(uvSX, uvSY);
vert.uv1 = new Vector2(0f, uvSY); // 整线UV (u=0 表示线的起点侧)
vert.uv2 = userDataStart;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 0] = vert;
// V1
vert.position = v1;
vert.color = c1;
vert.uv0 = new Vector2(uvEX, uvSY);
vert.uv1 = new Vector2(1f, uvSY); // 整线UV (u=1 表示线的终点侧)
vert.uv2 = userDataStart;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 1] = vert;
// V2
vert.position = v2;
vert.color = c2;
vert.uv0 = new Vector2(uvSX, uvEY);
vert.uv1 = new Vector2(0f, uvEY);
vert.uv2 = userDataEnd;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 2] = vert;
// V3
vert.position = v3;
vert.color = c3;
vert.uv0 = new Vector2(uvEX, uvEY);
vert.uv1 = new Vector2(1f, uvEY);
vert.uv2 = userDataEnd;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 3] = vert;
// V4
vert.position = v4;
vert.color = c4;
vert.uv0 = new Vector2(0.5f, uvEY);
vert.uv1 = new Vector2(0.5f, uvEY);
vert.uv2 = userDataEnd;
vert.uv3 = Vector4.zero;
outputVertices[outputStart + 4] = vert;
}
/// <summary>
/// 生成单段线段的三角形索引(写入 indexList)
/// </summary>
/// <param name="segmentIndex">子段全局索引</param>
/// <param name="hasPrevInLine">上一子段是否属于同一条线(控制转角连接三角形)</param>
/// <param name="indexList">输出的三角形索引列表</param>
public static void GenerateSegmentIndices(int segmentIndex, bool hasPrevInLine, List<int> indexList)
{
int baseVert = segmentIndex * VerticesPerSegment;
// 三角形 1: V0-V1-V3 (矩形上半)
indexList.Add(baseVert + 0);
indexList.Add(baseVert + 1);
indexList.Add(baseVert + 3);
// 三角形 2: V3-V2-V0 (矩形下半)
indexList.Add(baseVert + 3);
indexList.Add(baseVert + 2);
indexList.Add(baseVert + 0);
// 三角形 3: 转角连接三角形
// 仅在「同一折线上的相邻子段」之间绘制
// 连接上一段的 V4(上一段终点中心)到当前段的 V0, V1
if (hasPrevInLine)
{
int prevV4 = (segmentIndex - 1) * VerticesPerSegment + 4;
indexList.Add(prevV4);
indexList.Add(baseVert + 0);
indexList.Add(baseVert + 1);
}
}
#region Jobs 模式
#if ENABLE_BURST
/// <summary>
/// Burst 编译的 IJobParallelFor:并行生成各段线的模型顶点
/// </summary>
[BurstCompile]
public struct GenerateVerticesJob : IJobParallelFor
{
[ReadOnly] public NativeArray<SegmentJobInput> Inputs;
[WriteOnly] public NativeArray<UIVertex> OutputVertices;
public void Execute(int index)
{
int start = index * VerticesPerSegment;
var input = Inputs[index];
Vector2 startPos = input.start.position;
Vector2 endPos = input.end.position;
Vector2 dir = endPos - startPos;
float length = dir.magnitude;
if (length < float.Epsilon)
{
for (int i = 0; i < VerticesPerSegment; i++)
OutputVertices[start + i] = new UIVertex();
return;
}
Vector2 normal = dir / length;
Vector2 subTangent = new Vector2(-normal.y, normal.x);
float halfW0 = input.start.thickness * 0.5f;
float halfW1 = input.end.thickness * 0.5f;
Vector3 v0 = startPos - subTangent * halfW0;
Vector3 v1 = startPos + subTangent * halfW0;
Vector3 v2 = endPos - subTangent * halfW1;
Vector3 v3 = endPos + subTangent * halfW1;
Vector3 v4 = endPos;
Color32 c0 = Color32.Lerp(input.start.color, input.end.color, 0f);
Color32 c2 = Color32.Lerp(input.start.color, input.end.color, 1f);
float uvSY = input.startLineUV;
float uvEY = input.endLineUV;
Vector4 userDataStart = input.start.userData;
Vector4 userDataEnd = input.end.userData;
UIVertex vert = new UIVertex
{
normal = Vector3.back,
tangent = new Vector4(1f, 0f, 0f, -1f)
};
vert.position = v0; vert.color = c0;
vert.uv0 = new Vector2(0f, uvSY); vert.uv1 = new Vector2(0f, uvSY); vert.uv2 = userDataStart;
OutputVertices[start + 0] = vert;
vert.position = v1; vert.color = c0;
vert.uv0 = new Vector2(1f, uvSY); vert.uv1 = new Vector2(1f, uvSY); vert.uv2 = userDataStart;
OutputVertices[start + 1] = vert;
vert.position = v2; vert.color = c2;
vert.uv0 = new Vector2(0f, uvEY); vert.uv1 = new Vector2(0f, uvEY); vert.uv2 = userDataEnd;
OutputVertices[start + 2] = vert;
vert.position = v3; vert.color = c2;
vert.uv0 = new Vector2(1f, uvEY); vert.uv1 = new Vector2(1f, uvEY); vert.uv2 = userDataEnd;
OutputVertices[start + 3] = vert;
vert.position = v4; vert.color = c2;
vert.uv0 = new Vector2(0.5f, uvEY); vert.uv1 = new Vector2(0.5f, uvEY); vert.uv2 = userDataEnd;
OutputVertices[start + 4] = vert;
}
}
/// <summary>
/// 使用 Jobs 并行生成线段顶点
/// </summary>
public static JobHandle GenerateSegmentsJobs(
NativeArray<SegmentJobInput> inputs,
NativeArray<UIVertex> outputVertices,
JobHandle dependsOn = default)
{
var job = new GenerateVerticesJob
{
Inputs = inputs,
OutputVertices = outputVertices
};
return job.Schedule(inputs.Length, 1, dependsOn);
}
#endif
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24c8f213d27953c419e17ce974bbf594
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,58 @@
using System;
using UnityEngine;
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 线段端点数据(所有字段需为 blittable 类型以支持 NativeArray/Job)
/// </summary>
[Serializable]
public struct LineVertexData
{
public Vector2 position; // 端点位置(局部坐标)
public Color32 color; // 端点颜色
public float thickness; // 端点宽度
public Vector4 userData; // 自定义扩展数据 → 映射到 UV2
}
/// <summary>
/// UV1 均分模式(每条线段独立)
/// </summary>
public enum UVMode : byte
{
/// <summary>按实际线路距离均分</summary>
ByDistance,
/// <summary>按线段数量均分</summary>
BySegment,
}
/// <summary>
/// 线段范围记录
/// </summary>
[Serializable]
public struct LineSegmentRange
{
public int startVertexIndex; // 在 vertexData 中的起始索引
public int vertexCount; // 该线段端点数量(>= 2)
public UVMode uvMode; // UV1 均分模式
}
/// <summary>
/// Job 输入:单段线段的顶点生成参数
/// </summary>
[Serializable]
public struct SegmentJobInput
{
public LineVertexData start;
public LineVertexData end;
public LineVertexData prevEnd; // 上个线段终点(仅 hasPrev=1 时有效)
public LineVertexData nextStart; // 下个线段起点(仅 hasNext=1 时有效)
public byte hasPrev; // 0=无效, 1=有效
public byte hasNext; // 0=无效, 1=有效
public int segmentIndex;
public int totalSegmentCount;
public float startLineUV;
public float endLineUV;
public UVMode uvMode;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba184a89add023d42b93dbc715acc1c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+115
View File
@@ -0,0 +1,115 @@
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace XericLibrary.Runtime.UIGraph
{
/// <summary>
/// 新一代线段渲染器组件
/// 基于 CacheLineRendererBase 的缓存 + Jobs 架构
/// </summary>
[AddComponentMenu("Xeric Library/UI/UILineRenderer V2", 15)]
public class UILineRendererV2 : CacheLineRendererBase
{
[Header("全局默认设置")]
[Range(0.01f, 200f)]
[Tooltip("默认线段宽度")]
public float defaultThickness = 10f;
[Tooltip("默认线段颜色")]
public Color defaultColor = Color.white;
[Tooltip("默认 UV1 均分模式")]
public UVMode defaultUVMode = UVMode.ByDistance;
/// <summary>
/// 绘制一条简单线段(2个端点)
/// </summary>
public void DrawLine(Vector2 from, Vector2 to)
{
DrawLine(from, to, defaultColor, defaultThickness, defaultUVMode);
}
/// <summary>
/// 绘制一条简单线段(指定颜色和宽度)
/// </summary>
public void DrawLine(Vector2 from, Vector2 to, Color color, float thickness, UVMode uvMode = UVMode.ByDistance)
{
BeginLine(uvMode);
AddLineNode(from, color, thickness);
AddLineNode(to, color, thickness);
EndLine();
}
/// <summary>
/// 绘制折线(多点连接)
/// </summary>
public void DrawPolyline(Vector2[] points, Color color, float thickness, UVMode uvMode = UVMode.ByDistance)
{
if (points == null || points.Length < 2) return;
LineVertexData[] vertices = new LineVertexData[points.Length];
for (int i = 0; i < points.Length; i++)
{
vertices[i] = new LineVertexData
{
position = points[i],
color = color,
thickness = thickness,
userData = Vector4.zero
};
}
AddLine(vertices, uvMode);
}
/// <summary>
/// 绘制折线(每个端点独立颜色/宽度)
/// </summary>
public void DrawPolyline(LineVertexData[] vertices, UVMode uvMode = UVMode.ByDistance)
{
if (vertices == null || vertices.Length < 2) return;
AddLine(vertices, uvMode);
}
/// <summary>
/// 简单绘制矩形框
/// </summary>
public void DrawRect(Rect rect, Color color, float thickness)
{
Vector2 center = rect.center;
float hw = rect.width * 0.5f;
float hh = rect.height * 0.5f;
var pts = new Vector2[]
{
new Vector2(center.x - hw, center.y - hh),
new Vector2(center.x + hw, center.y - hh),
new Vector2(center.x + hw, center.y + hh),
new Vector2(center.x - hw, center.y + hh),
new Vector2(center.x - hw, center.y - hh),
};
DrawPolyline(pts, color, thickness, UVMode.ByDistance);
}
#if UNITY_EDITOR
protected override void OnValidate()
{
base.OnValidate();
if (defaultThickness < 0.01f)
defaultThickness = 0.01f;
}
[MenuItem("GameObject/Xeric Library/UI/UILineRenderer V2", false, 10)]
private static void CreateGameObject()
{
var go = new GameObject(nameof(UILineRendererV2), typeof(UILineRendererV2));
if (Selection.activeGameObject != null)
go.transform.SetParent(Selection.activeGameObject.transform);
Undo.RegisterCreatedObjectUndo(go, $"Create {nameof(UILineRendererV2)}");
Selection.activeObject = go;
}
#endif
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d43989c17d4c7d640988acb51713390a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: