diff --git a/Runtime/Sample.unity b/Runtime/Sample.unity index 2e8bbd0..c0b1160 100644 --- a/Runtime/Sample.unity +++ b/Runtime/Sample.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 705507994} - m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641275, b: 0.5748172, a: 1} + m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -104,7 +104,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 3 + serializedVersion: 2 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +117,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - buildHeightMesh: 0 + accuratePlacement: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -154,6 +154,7 @@ RectTransform: m_ConstrainProportionsScale: 1 m_Children: [] m_Father: {fileID: 387643621} + m_RootOrder: -2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -172,10 +173,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 5b33b2e663e78774c9f0c9af55018725, type: 3} m_Name: m_EditorClassIdentifier: - m_Material: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} + m_Material: {fileID: 2100000, guid: 13dbba63516e8bc469b99fae7247fd98, type: 2} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] points: - {x: 0, y: 0} - {x: 58.7, y: 138.4} @@ -272,7 +277,6 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 - m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 @@ -293,6 +297,7 @@ RectTransform: - {fileID: 1231462426} - {fileID: 246206082} m_Father: {fileID: 0} + m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -309,6 +314,7 @@ GameObject: m_Component: - component: {fileID: 705507995} - component: {fileID: 705507994} + - component: {fileID: 705507996} m_Layer: 0 m_Name: Directional Light m_TagString: Untagged @@ -385,14 +391,34 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 705507993} - serializedVersion: 2 m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} m_LocalPosition: {x: 0, y: 3, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} + m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &705507996 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 1 + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_LightLayerMask: 1 + m_CustomShadowLayers: 0 + m_ShadowLayerMask: 1 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} --- !u!1 &963194225 GameObject: m_ObjectHideFlags: 0 @@ -433,17 +459,9 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 - m_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -477,13 +495,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 963194225} - serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 1, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} + m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1231462425 GameObject: @@ -516,6 +534,7 @@ RectTransform: m_ConstrainProportionsScale: 1 m_Children: [] m_Father: {fileID: 387643621} + m_RootOrder: -2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -590,7 +609,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} m_Name: m_EditorClassIdentifier: - m_SendPointerHoverToParent: 1 m_HorizontalAxis: Horizontal m_VerticalAxis: Vertical m_SubmitButton: Submit @@ -620,19 +638,11 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1721653935} - serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} + m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1660057539 &9223372036854775807 -SceneRoots: - m_ObjectHideFlags: 0 - m_Roots: - - {fileID: 963194228} - - {fileID: 705507995} - - {fileID: 387643621} - - {fileID: 1721653938} diff --git a/Runtime/UILineRender.mat b/Runtime/UILineRender.mat new file mode 100644 index 0000000..65bf9e0 --- /dev/null +++ b/Runtime/UILineRender.mat @@ -0,0 +1,41 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: UILineRender + m_Shader: {fileID: 4800000, guid: 753025f901250db45b87f87c632c0bb0, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _ColorMask: 15 + - _Stencil: 0 + - _StencilComp: 8 + - _StencilOp: 0 + - _StencilReadMask: 255 + - _StencilWriteMask: 255 + - _UseUIAlphaClip: 0 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] diff --git a/Runtime/UILineRender.mat.meta b/Runtime/UILineRender.mat.meta new file mode 100644 index 0000000..130381f --- /dev/null +++ b/Runtime/UILineRender.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 13dbba63516e8bc469b99fae7247fd98 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UILineRender.shader b/Runtime/UILineRender.shader new file mode 100644 index 0000000..380e9ae --- /dev/null +++ b/Runtime/UILineRender.shader @@ -0,0 +1,166 @@ +// Made with Amplify Shader Editor v1.9.9.1 +// Available at the Unity Asset Store - http://u3d.as/y3X +Shader "UILineRender" +{ + Properties + { + [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {} + _Color ("Tint", Color) = (1,1,1,1) + + _StencilComp ("Stencil Comparison", Float) = 8 + _Stencil ("Stencil ID", Float) = 0 + _StencilOp ("Stencil Operation", Float) = 0 + _StencilWriteMask ("Stencil Write Mask", Float) = 255 + _StencilReadMask ("Stencil Read Mask", Float) = 255 + + _ColorMask ("Color Mask", Float) = 15 + + [Toggle(UNITY_UI_ALPHACLIP)] _UseUIAlphaClip ("Use Alpha Clip", Float) = 0 + + + } + + SubShader + { + LOD 0 + + Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" "CanUseSpriteAtlas"="True" } + + Stencil + { + Ref [_Stencil] + ReadMask [_StencilReadMask] + WriteMask [_StencilWriteMask] + Comp [_StencilComp] + Pass [_StencilOp] + } + + + Cull Off + Lighting Off + ZWrite Off + ZTest [unity_GUIZTestMode] + Blend One OneMinusSrcAlpha + ColorMask [_ColorMask] + + + Pass + { + Name "Default" + CGPROGRAM + #define ASE_VERSION 19901 + + #pragma vertex vert + #pragma fragment frag + #pragma target 3.5 + + #include "UnityCG.cginc" + #include "UnityUI.cginc" + + #pragma multi_compile_local _ UNITY_UI_CLIP_RECT + #pragma multi_compile_local _ UNITY_UI_ALPHACLIP + + #define ASE_NEEDS_TEXTURE_COORDINATES0 + #define ASE_NEEDS_FRAG_TEXTURE_COORDINATES0 + + + struct appdata_t + { + float4 vertex : POSITION; + float4 color : COLOR; + float2 texcoord : TEXCOORD0; + UNITY_VERTEX_INPUT_INSTANCE_ID + + }; + + struct v2f + { + float4 vertex : SV_POSITION; + fixed4 color : COLOR; + float2 texcoord : TEXCOORD0; + float4 worldPosition : TEXCOORD1; + float4 mask : TEXCOORD2; + UNITY_VERTEX_OUTPUT_STEREO + + }; + + sampler2D _MainTex; + fixed4 _Color; + fixed4 _TextureSampleAdd; + float4 _ClipRect; + float4 _MainTex_ST; + float _UIMaskSoftnessX; + float _UIMaskSoftnessY; + + + + v2f vert(appdata_t v ) + { + v2f OUT; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(OUT); + + + + v.vertex.xyz += float3( 0, 0, 0 ) ; + + float4 vPosition = UnityObjectToClipPos(v.vertex); + OUT.worldPosition = v.vertex; + OUT.vertex = vPosition; + + float2 pixelSize = vPosition.w; + pixelSize /= float2(1, 1) * abs(mul((float2x2)UNITY_MATRIX_P, _ScreenParams.xy)); + + float4 clampedRect = clamp(_ClipRect, -2e10, 2e10); + float2 maskUV = (v.vertex.xy - clampedRect.xy) / (clampedRect.zw - clampedRect.xy); + OUT.texcoord = v.texcoord; + OUT.mask = float4(v.vertex.xy * 2 - clampedRect.xy - clampedRect.zw, 0.25 / (0.25 * half2(_UIMaskSoftnessX, _UIMaskSoftnessY) + abs(pixelSize.xy))); + + OUT.color = v.color * _Color; + return OUT; + } + + fixed4 frag(v2f IN ) : SV_Target + { + //Round up the alpha color coming from the interpolator (to 1.0/256.0 steps) + //The incoming alpha could have numerical instability, which makes it very sensible to + //HDR color transparency blend, when it blends with the world's texture. + const half alphaPrecision = half(0xff); + const half invAlphaPrecision = half(1.0/alphaPrecision); + IN.color.a = round(IN.color.a * alphaPrecision)*invAlphaPrecision; + + float2 texCoord1 = IN.texcoord.xy * float2( 1,1 ) + float2( 0,0 ); + float4 appendResult3 = (float4(texCoord1 , 0.0 , 1.0)); + + + half4 color = appendResult3; + + #ifdef UNITY_UI_CLIP_RECT + half2 m = saturate((_ClipRect.zw - _ClipRect.xy - abs(IN.mask.xy)) * IN.mask.zw); + color.a *= m.x * m.y; + #endif + + #ifdef UNITY_UI_ALPHACLIP + clip (color.a - 0.001); + #endif + + color.rgb *= color.a; + + return color; + } + ENDCG + } + } + CustomEditor "AmplifyShaderEditor.MaterialInspector" + + Fallback Off +} +/*ASEBEGIN +Version=19901 +Node;AmplifyShaderEditor.TextureCoordinatesNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;1;-432.087,-103.4147;Inherit;False;0;-1;2;3;2;SAMPLER2D;;False;0;FLOAT2;1,1;False;1;FLOAT2;0,0;False;5;FLOAT2;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4 +Node;AmplifyShaderEditor.DynamicAppendNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;3;-192,-112;Inherit;False;FLOAT4;4;0;FLOAT2;0,0;False;1;FLOAT;0;False;2;FLOAT;0;False;3;FLOAT;1;False;1;FLOAT4;0 +Node;AmplifyShaderEditor.TemplateMultiPassMasterNode, AmplifyShaderEditor, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null;0;0,0;Float;False;True;-1;3;AmplifyShaderEditor.MaterialInspector;0;3;UILineRender;5056123faa0c79b47ab6ad7e8bf059a4;True;Default;0;0;Default;2;False;True;3;1;False;;10;False;;0;1;False;;0;False;;False;False;False;False;False;False;False;False;False;False;False;False;True;2;False;;False;True;True;True;True;True;0;True;_ColorMask;False;False;False;False;False;False;False;True;True;0;True;_Stencil;255;True;_StencilReadMask;255;True;_StencilWriteMask;0;True;_StencilComp;0;True;_StencilOp;0;False;;0;False;;0;False;;0;False;;0;False;;0;False;;False;True;2;False;;True;0;True;unity_GUIZTestMode;False;True;5;Queue=Transparent=Queue=0;IgnoreProjector=True;RenderType=Transparent=RenderType;PreviewType=Plane;CanUseSpriteAtlas=True;False;False;0;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;3;False;0;;0;0;Standard;0;0;1;True;False;;False;0 +WireConnection;3;0;1;0 +WireConnection;0;0;3;0 +ASEEND*/ +//CHKSM=7125D87DAE9E1D51798A36D24871C6DA03114C88 \ No newline at end of file diff --git a/Runtime/UILineRender.shader.meta b/Runtime/UILineRender.shader.meta new file mode 100644 index 0000000..766b93a --- /dev/null +++ b/Runtime/UILineRender.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 753025f901250db45b87f87c632c0bb0 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/UILineRenderer.cs b/Runtime/UILineRenderer.cs index fdf003e..44fcde6 100644 --- a/Runtime/UILineRenderer.cs +++ b/Runtime/UILineRenderer.cs @@ -3,35 +3,69 @@ using UnityEngine.UI; namespace XericLibrary { + /// + /// UILineRenderer 是一个用于在 UI 上绘制连续线条的组件 + /// 支持设置线条厚度、居中显示,并为线段之间添加斜角边缘 + /// 继承自 MaskableGraphic,可与 UI 遮罩系统配合使用 + /// [RequireComponent(typeof(CanvasRenderer))] public class UILineRenderer : MaskableGraphic { + /// + /// 线条的顶点数组,至少需要 2 个点才能绘制线条 + /// 点的坐标基于本地空间,中心点由 center 属性控制 + /// public Vector2[] points; + /// + /// 线条的厚度,单位为像素 + /// public float thickness = 10f; + + /// + /// 是否将线条居中显示 + /// true: 线条将以组件中心为原点 + /// false: 线条将以组件左下角为原点 + /// public bool center = true; + /// + /// 重写 MaskableGraphic 的方法,用于生成线条的网格 + /// 当 UI 需要重建时会自动调用此方法 + /// + /// 用于构建网格的 VertexHelper 对象 protected override void OnPopulateMesh(VertexHelper vh) { + // 清空现有的网格数据 vh.Clear(); + // 至少需要 2 个点才能绘制线条 if (points.Length < 2) return; + // 计算线路总长度 + float totalLength = UILineRendererUtils.CalculateTotalLength(points); + float currentLength = 0f; + + // 遍历所有点,创建线段并添加到网格中 for (int i = 0; i < points.Length-1; i++) { - // Create a line segment between the next two points - CreateLineSegment(points[i], points[i+1], vh); + // 使用静态工具方法创建当前点与下一个点之间的线段 + // 传递当前累积长度和总长度用于UV坐标计算 + UILineRendererUtils.CreateLineSegment(points[i], points[i+1], thickness, center, rectTransform.sizeDelta, color, vh, currentLength, totalLength); + // 更新累积长度 + currentLength += Vector2.Distance(points[i], points[i+1]); + + // 计算当前线段的顶点索引(每个线段包含 5 个顶点) int index = i * 5; - // Add the line segment to the triangles array + // 添加线段的两个三角形(构成矩形线段) vh.AddTriangle(index, index+1, index+3); vh.AddTriangle(index+3, index+2, index); - // These two triangles create the beveled edges - // between line segments using the end point of - // the last line segment and the start points of this one + // 为线段之间添加斜角边缘 + // 使用上一个线段的终点和当前线段的起点创建过渡三角形 if (i != 0) { vh.AddTriangle(index, index-1, index-3); @@ -39,53 +73,5 @@ namespace XericLibrary } } } - - /// - /// Creates a rect from two points that acts as a line segment - /// - /// The starting point of the segment - /// The endint point of the segment - /// The vertex helper that the segment is added to - private void CreateLineSegment(Vector3 point1, Vector3 point2, VertexHelper vh) - { - Vector3 offset = center ? (rectTransform.sizeDelta / 2) : Vector2.zero; - - // Create vertex template - UIVertex vertex = UIVertex.simpleVert; - vertex.color = color; - - // Create the start of the segment - Quaternion point1Rotation = Quaternion.Euler(0, 0, RotatePointTowards(point1, point2) + 90); - vertex.position = point1Rotation * new Vector3(-thickness / 2, 0); - vertex.position += point1 - offset; - vh.AddVert(vertex); - vertex.position = point1Rotation * new Vector3(thickness / 2, 0); - vertex.position += point1 - offset; - vh.AddVert(vertex); - - // Create the end of the segment - Quaternion point2Rotation = Quaternion.Euler(0, 0, RotatePointTowards(point2, point1) - 90); - vertex.position = point2Rotation * new Vector3(-thickness / 2, 0); - vertex.position += point2 - offset; - vh.AddVert(vertex); - vertex.position = point2Rotation * new Vector3(thickness / 2, 0); - vertex.position += point2 - offset; - vh.AddVert(vertex); - - // Also add the end point - vertex.position = point2 - offset; - vh.AddVert(vertex); - } - - /// - /// Gets the angle that a vertex needs to rotate to face target vertex - /// - /// The vertex being rotated - /// The vertex to rotate towards - /// The angle required to rotate vertex towards target - private float RotatePointTowards(Vector2 vertex, Vector2 target) - { - return (float)(Mathf.Atan2(target.y - vertex.y, target.x - vertex.x) * (180 / Mathf.PI)); - } } -} +} \ No newline at end of file diff --git a/Runtime/UILineRendererUtils.cs b/Runtime/UILineRendererUtils.cs new file mode 100644 index 0000000..0cd45fa --- /dev/null +++ b/Runtime/UILineRendererUtils.cs @@ -0,0 +1,110 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace XericLibrary +{ + /// + /// UILineRenderer 的工具类,提供静态方法用于线条渲染相关的操作 + /// + public static class UILineRendererUtils + { + /// + /// 计算线路的总长度 + /// + /// 线路的顶点数组 + /// 线路的总长度 + public static float CalculateTotalLength(Vector2[] points) + { + if (points.Length < 2) + return 0f; + + float totalLength = 0f; + for (int i = 0; i < points.Length - 1; i++) + { + totalLength += Vector2.Distance(points[i], points[i + 1]); + } + return totalLength; + } + + /// + /// 创建线段的顶点数据 + /// 每个线段由 5 个顶点组成: + /// - 起点的左右两个顶点 + /// - 终点的左右两个顶点 + /// - 终点的中心点(用于线段连接) + /// + /// 线段的起点 + /// 线段的终点 + /// 线条的厚度 + /// 是否居中显示 + /// 矩形变换的尺寸 + /// 线条的颜色 + /// 用于添加顶点的 VertexHelper 对象 + /// 当前线段起点在线路中的累积长度 + /// 线路的总长度 + public static void CreateLineSegment(Vector3 point1, Vector3 point2, float thickness, bool center, Vector2 rectSize, Color color, VertexHelper vh, float currentLength, float totalLength) + { + // 计算偏移量:如果需要居中显示,则偏移到组件中心 + Vector3 offset = center ? (rectSize / 2) : Vector2.zero; + + // 创建顶点模板,设置颜色为组件的当前颜色 + UIVertex vertex = UIVertex.simpleVert; + vertex.color = color; + + // 计算线段长度 + float segmentLength = Vector2.Distance(point1, point2); + + // 计算起点和终点的UV坐标Y值 + float startUVY = totalLength > 0 ? currentLength / totalLength : 0f; + float endUVY = totalLength > 0 ? (currentLength + segmentLength) / totalLength : 0f; + + // 计算起点的旋转角度,使线条垂直于线段方向 + Quaternion point1Rotation = Quaternion.Euler(0, 0, RotatePointTowards(point1, point2) + 90); + + // 添加起点的左侧顶点 + vertex.position = point1Rotation * new Vector3(-thickness / 2, 0); + vertex.position += point1 - offset; + vertex.uv0 = new Vector2(0f, startUVY); // 左侧顶点的UV + vh.AddVert(vertex); + + // 添加起点的右侧顶点 + vertex.position = point1Rotation * new Vector3(thickness / 2, 0); + vertex.position += point1 - offset; + vertex.uv0 = new Vector2(1f, startUVY); // 右侧顶点的UV + vh.AddVert(vertex); + + // 计算终点的旋转角度,使线条垂直于线段方向 + Quaternion point2Rotation = Quaternion.Euler(0, 0, RotatePointTowards(point2, point1) - 90); + + // 添加终点的左侧顶点 + vertex.position = point2Rotation * new Vector3(-thickness / 2, 0); + vertex.position += point2 - offset; + vertex.uv0 = new Vector2(0f, endUVY); // 左侧顶点的UV + vh.AddVert(vertex); + + // 添加终点的右侧顶点 + vertex.position = point2Rotation * new Vector3(thickness / 2, 0); + vertex.position += point2 - offset; + vertex.uv0 = new Vector2(1f, endUVY); // 右侧顶点的UV + vh.AddVert(vertex); + + // 添加终点的中心点,用于连接后续线段 + vertex.position = point2 - offset; + vertex.uv0 = new Vector2(0.5f, endUVY); // 中心点的UV + vh.AddVert(vertex); + } + + /// + /// 计算一个点指向另一个点的角度(以度为单位) + /// + /// 起始点 + /// 目标点 + /// 旋转角度,范围为 -180 到 180 度 + public static float RotatePointTowards(Vector2 vertex, Vector2 target) + { + // 使用反正切函数计算两点之间的角度 + // 将弧度转换为角度返回 + return (float)(Mathf.Atan2(target.y - vertex.y, target.x - vertex.x) * (180 / Mathf.PI)); + } + } +} \ No newline at end of file diff --git a/Runtime/UILineRendererUtils.cs.meta b/Runtime/UILineRendererUtils.cs.meta new file mode 100644 index 0000000..99b5424 --- /dev/null +++ b/Runtime/UILineRendererUtils.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 117633a298088ab4fb9fa0edac2bd141 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: