<think>我们将在Unity中使用Mesh来绘制圆头线路,以显示鼠标移动轨迹。基本思路如下:1.记录鼠标移动轨迹的点(世界坐标)。2.根据这些点生成一系列四边形(每个四边形由两个连续的点和给定的宽度构成,但为了圆头,我们需要在两端添加半圆)。3.圆头的实现:在轨迹的起点和终点分别添加半圆。对于中间的连接处,我们可以使用两个四边形之间的连接处自然过渡(或者也可以使用圆角,但这里要求圆头,所以两端处理为半圆)。然而,更常见的做法是:-对于轨迹中的每个线段,我们生成一个矩形(两个三角形组成)。-在起点和终点处,我们生成一个半圆形的盖子(由多个三角形组成)。步骤:1.创建一个空的GameObject,并添加MeshFilter和MeshRenderer组件。2.在脚本中,动态生成Mesh:顶点、三角形、UV等。3.实时更新:根据鼠标移动,添加新的点,并更新Mesh。具体实现:1.顶点生成:-对于轨迹中的每个点(除了最后一个),我们计算该点处的法线(垂直于线段方向)。-然后,在法线方向的两侧扩展,得到两个顶点(构成矩形的一条边)。-对于起点和终点,我们需要生成半圆。以起点为例,我们取起点处的法线,然后旋转从-90度到90度(或根据方向调整),生成多个顶点,形成半圆。2.三角形生成:-对于线段部分:每两个连续的点之间,我们生成两个三角形(一个四边形)。-对于起点半圆:从圆心(起点)开始,按角度生成多个三角形。-对于终点的半圆:同理。3.注意:为了平滑,半圆的顶点数需要足够。优化:我们也可以不使用半圆,而是使用多个线段来模拟半圆(比如用8个顶点来模拟半圆)。由于实时更新,我们需要考虑性能。实现步骤(简化版,这里我们先不考虑半圆,只考虑线段,然后单独处理两端):方案:我们可以将整个轨迹视为一个连续的条带,然后在两端添加半圆。但这样需要将半圆的顶点和线段部分的顶点连接起来。另一种常见做法:在每一端(起点和终点)额外添加一个点,使得起点和终点处的宽度扩展为半圆直径,然后使用圆形贴图(通过UV实现圆形)来显示圆头。但这里我们使用几何体来实现。我们采用以下方法:1.线段部分:每个线段生成一个矩形(两个三角形)。2.端点部分:起点和终点各生成一个半圆(由多个三角形组成)。步骤:1.记录鼠标位置(世界坐标)的列表。2.当列表中的点大于等于2时,开始生成线段。3.生成顶点:a.对于每个中间点(除了起点和终点),我们计算该点前后两个线段的方向,然后求平均法线,以获得平滑过渡(避免尖锐的转角)。但简单情况下,我们可以先不考虑平滑转角,只考虑垂直。b.更简单的方法:不考虑转角平滑,则每个线段的矩形都是垂直于该线段的。4.生成起点半圆:-设起点为p0,下一个点为p1,则方向为d=(p1-p0).normalized。-起点处的法线为垂直于d的向量(比如n0=newVector3(-d.y,d.x,0))。-半圆的圆心为p0,半径为线宽的一半(假设线宽为width,则半径为width/2)。-将半圆分为n份(例如n=8),角度从-90度到90度(相对于法线n0方向)。注意:这里我们是在法线旋转的平面内生成点。5.同理生成终点半圆。6.连接:起点半圆的最后一个顶点需要与第一个线段的第一个顶点连接,终点半圆的第一个顶点需要与最后一个线段的最后一个顶点连接。由于这是一个比较复杂的过程,我们逐步实现。注意:为了简化,我们可以先不考虑转角处的平滑(即每个线段都是独立的矩形,转角处会有一个缝隙,我们可以通过添加顶点来填充,但这里我们先实现圆头两端,转角暂时不处理圆角)。我们先实现一个版本,只考虑线段和两端的半圆,中间转角不做特殊处理(会有缝隙,但可以通过增加顶点来连接,但这样代码会复杂,因此我们也可以考虑另一种方法:使用LineRenderer?但这里要求用Mesh)。另一种思路:整个轨迹使用一个条带(strip),然后在两端用半圆盖住。这样中间部分就是连续的四边形条带,两端用半圆。实现步骤:1.初始化:-创建一个MeshFilter和MeshRenderer。-设置材质(可以使用简单的无光照材质,或者自定义shader)。2.动态添加点:-在Update中,将鼠标位置(转换为世界坐标)添加到列表中。注意:我们需要考虑深度,一般可以放在同一深度(比如z=0)。3.当点数量足够时(>=2),重新生成整个轨迹的Mesh。4.生成Mesh:a.计算轨迹的总顶点数(包括线段部分和两个半圆)。-线段部分:每个线段需要2个顶点(因为相邻线段共享顶点,所以实际上每增加一个点,增加2个顶点,但起点和终点需要额外处理?)。-半圆部分:每个半圆有n个顶点(n=圆的分段数,不包括圆心,因为圆心在条带中不共享,所以需要额外的顶点)。b.具体顶点数:-线段部分:有m个点,则生成(m-1)个四边形,每个四边形4个顶点?但为了形成连续的条带,我们可以使用顶点数组,每两个点形成一条边,则顶点数为2*m(条带方式)。-然后起点半圆:n个顶点(半圆上的点,不包括圆心),终点半圆:n个顶点。-另外,为了连接半圆和线段,我们需要将半圆的顶点与线段的端点连接,所以总顶点数=2*m+2*n(但这样不能直接连接,因为半圆需要额外的三角形)。实际上,我们需要分别构建:-线段部分:使用连续的三角形条带(TriangleStrip),顶点数=m*2(每个点生成两个顶点:左右各一)。但是,为了两端能连接半圆,我们需要将起点和终点的两个顶点分别与半圆连接。-半圆部分:起点半圆需要生成一个扇形(使用TriangleFan),终点半圆同理。然而,整个Mesh需要统一,所以我们可以这样:-顶点列表:先放起点半圆的顶点(n个),然后放线段部分的顶点(按顺序,每个点两个顶点:左侧和右侧),最后放终点半圆的顶点(n个)。-三角形索引:*起点半圆:以起点(线段部分的第一个顶点?)为圆心?但注意,我们线段部分没有圆心顶点。因此,我们需要在起点半圆中额外添加一个圆心顶点(起点位置),然后围绕这个圆心生成扇形。*同样,终点半圆也需要一个圆心顶点(终点位置)。因此,顶点结构:顶点0:起点圆心(用于起点半圆)顶点1~n:起点半圆上的点(n个)顶点n+1~n+2*m:线段部分的顶点(每两个顶点代表一个截面,顺序为:第一个截面的左侧顶点、右侧顶点、第二个截面的左侧顶点、右侧顶点...)顶点n+2*m+1:终点圆心(用于终点半圆)顶点n+2*m+2~n+2*m+1+n:终点半圆上的点(n个)这样总顶点数:1+n+2*m+1+n=2+2*n+2*m三角形索引:1.起点半圆:使用顶点0(圆心)和顶点1~n,以及一个额外的顶点(半圆结束点,即顶点n)和顶点1构成闭合。注意顺序(顺时针或逆时针)。-三角形:0,1,2;0,2,3;...0,n-1,n;0,n,1?不对,应该用:0,i,i+1(i从1到n-1)以及最后一个三角形0,n,1?不行,因为n和1不相邻。所以我们需要按顺序生成:从1开始,然后2,然后回到圆心0,再下一个。但这样会生成n个三角形。-具体:三角形0:0,1,2;三角形1:0,2,3;...三角形n-1:0,n,1?这样不对,因为最后一个三角形应该连接顶点n和1,但1和n之间没有直接连接。所以我们需要调整:半圆上的点按顺序排列,然后最后一个点(顶点n)和第一个点(顶点1)通过圆心连接?实际上,最后一个三角形应该是0,n,1。但这样三角形会翻转。所以我们需要将半圆上的点按顺序排列(顺时针或逆时针一致)。2.线段部分:使用三角形条带(TriangleStrip)。顶点顺序:起点半圆的最后一个顶点(即顶点n,它是起点截面的一侧)需要连接到线段部分的第一个顶点(即顶点n+1,它是第一个截面的左侧顶点?)。但是,我们线段部分的顶点顺序是:第一个截面的左侧(顶点n+1),右侧(顶点n+2),第二个截面的左侧(顶点n+3),右侧(顶点n+4)...-三角形条带:顶点n+1,n+2,n+3,n+4,...直到最后一个顶点(n+2*m)。-注意:我们需要将起点半圆的最后一个顶点(顶点n)连接到线段部分的第一个顶点(顶点n+1)?不,起点半圆已经独立生成,我们需要在起点半圆和线段部分之间建立连接。因此,在起点半圆结束后,我们需要额外两个三角形来连接:三角形1:顶点n(起点半圆结束点,假设是左侧),顶点n+1(线段第一个截面的左侧),顶点n+2(线段第一个截面的右侧)三角形2:顶点n(起点半圆结束点),顶点n+2(线段第一个截面的右侧),顶点(起点半圆结束点对应的另一侧?)——这里没有,因为我们起点半圆只生成了一侧的顶点?不对,半圆是完整的半圆,所以起点半圆上的点已经包含了从起点截面的一侧到另一侧的所有点。因此,我们不需要额外连接,因为起点半圆已经覆盖了整个起点截面。3.终点半圆:同理,需要连接线段部分的最后一个截面(顶点n+2*m-1和n+2*m)到终点半圆。终点半圆的圆心是顶点n+2*m+1,然后半圆上的点从n+2*m+2开始。4.终点半圆的三角形:与起点半圆类似。但是,这样实现非常复杂,且需要处理大量的顶点索引。因此,我们采用另一种更简单但效果稍差的方法:两端使用半圆,中间使用线段,但将整个轨迹视为一个连续的条带,然后在两端加上半圆形的盖子。这样,我们只需要在生成线段部分时,在起点和终点处扩展出半圆。简化实现(只生成线段部分和两个半圆,但不考虑中间的平滑连接):-线段部分:每个点生成两个顶点(左右),然后形成连续的条带(TriangleStrip)。-起点半圆:以起点为中心,以线宽为直径,生成一个半圆(多个三角形),方向与第一个线段垂直。-终点半圆:同理。我们分别生成三个部分,然后合并到一个Mesh中。代码步骤:1.初始化顶点列表和三角形列表。2.生成线段部分(连续的四边形条带):for(inti=0;i<points.Count;i++){Vector3point=points[i];Vector3direction;if(i==0)direction=points[1]-points[0];elseif(i==points.Count-1)direction=points[points.Count-1]-points[points.Count-2];elsedirection=(points[i+1]-points[i-1]).normalized;//使用前后点的方向来平滑Vector3normal=newVector3(-direction.y,direction.x,0).normalized;//左右顶点vertices.Add(point+normal*width*0.5f);vertices.Add(point-normal*width*0.5f);}//然后生成线段部分的三角形(使用TriangleStrip方式,顶点顺序:0,1,2,3,...)for(inti=0;i<points.Count-1;i++){intstart=i*2;triangles.Add(start);triangles.Add(start+1);triangles.Add(start+2);triangles.Add(start+2);triangles.Add(start+1);triangles.Add(start+3);}3.生成起点半圆://圆心:points[0]//半径:width/2//方向:第一个线段的法线(即上面计算的第一点的normal)Vector3startNormal=(points[1]-points[0]).normalized;Vector3startPerp=newVector3(-startNormal.y,startNormal.x,0).normalized;intcenterIndex=vertices.Count;//圆心索引vertices.Add(points[0]);//圆心//生成半圆上的点(在垂直于方向,并且从-startPerp旋转到startPerp,旋转180度?不对,半圆应该是180度,但我们的线段法线是startPerp,所以半圆应该是从-startPerp到startPerp旋转180度?)//注意:半圆应该是在线段的一侧到另一侧,即旋转180度,但这里我们只需要半圆(180度)的顶点,分成n段。intsegmentCount=8;//半圆分段数List<Vector3>startCapVertices=newList<Vector3>();startCapVertices.Add(points[0]);//圆心for(inti=0;i<=segmentCount;i++){floatangle=-90+180*i/(float)segmentCount;//从-90度到90度(相对于法线方向,因为法线是垂直于线段向量的)floatrad=angle*Mathf.Deg2Rad;Vector3dir=startPerp*Mathf.Cos(rad)+startNormal*Mathf.Sin(rad);//注意:这里我们是在法线平面内旋转,但旋转轴是Z轴(2D情况下)//因为startNormal和startPerp是正交基vertices.Add(points[0]+dir*width*0.5f);}//生成起点半圆的三角形(扇形)for(inti=0;i<segmentCount;i++){triangles.Add(centerIndex);triangles.Add(centerIndex+i+1);triangles.Add(centerIndex+i+2);}4.生成终点半圆:类似。但是,这样生成的半圆和线段部分是分离的,我们需要将它们连接起来吗?不需要,因为半圆已经覆盖了起点截面。但是,线段部分的起点截面两个顶点(vertices[0]和vertices[1])与半圆的边缘顶点(centerIndex+1和centerIndex+segmentCount+1)应该是重合的(或者接近)。所以,我们可以在生成半圆的时候,确保半圆的第一个顶点(i=0)对应线段截面的左侧顶点(vertices[0]),最后一个顶点(i=segmentCount)对应线段截面的右侧顶点(vertices[1])。这样,它们就会重合,看起来是连接的。但是,由于我们生成半圆的方向和线段截面法线方向一致,所以应该是重合的。然而,这样生成的Mesh在起点和终点处会有重叠,但不会出现缝隙。注意:在生成半圆时,我们旋转的角度范围是-90度到90度,这正好覆盖了从线段截面的一侧到另一侧。5.合并:将线段部分、起点半圆、终点半圆的顶点和三角形都合并到一个Mesh中。但是,这样生成的Mesh在中间线段部分可能会有重叠(当线段转折时),所以我们需要在生成每个点的法线时使用前后点的平均方向,以避免尖锐转折处的收窄。改进:在生成线段部分时,使用前后点的平均方向来计算法线(除了起点和终点)。具体代码实现:由于代码较长,我们逐步实现。注意:在Unity中,鼠标位置需要转换为世界坐标,并且考虑深度。我们将创建一个脚本,挂在摄像机或一个空物体上,动态生成轨迹Mesh。步骤:1.创建一个GameObject,并添加MeshFilter和MeshRenderer。2.编写脚本,在Update中记录鼠标位置(转换为世界坐标,假设在XY平面,Z=0)。3.当鼠标移动一定距离(避免点过于密集)才添加新点。4.当点数变化时,重新生成Mesh。注意:性能问题,当点很多时,每次更新整个Mesh可能会影响性能。因此,我们可以使用动态Mesh,只追加点,但这样会复杂一些。我们先实现每次更新整个Mesh。代码结构:publicclassMouseTrail:MonoBehaviour{publicfloatwidth=0.1f;publicintcapSegmentCount=8;publicCameratargetCamera;publicMaterialmaterial;privateMeshmesh;privateList<Vector3>points=newList<Vector3>();privateVector3lastMousePos;voidStart(){//初始化Meshmesh=newMesh();GetComponent<MeshFilter>().mesh=mesh;GetComponent<MeshRenderer>().material=material;}voidUpdate(){Vector3mousePos=Input.mousePosition;mousePos.z=10;//假设距离相机10个单位Vector3worldPos=targetCamera.ScreenToWorldPoint(mousePos);worldPos.z=0;//如果移动距离大于阈值,则添加新点if(points.Count==0||Vector3.Distance(worldPos,points[points.Count-1])>0.1f){points.Add(worldPos);UpdateTrailMesh();}}voidUpdateTrailMesh(){if(points.Count<2){mesh.Clear();return;}List<Vector3>vertices=newList<Vector3>();List<int>triangles=newList<int>();//1.生成线段部分//计算每个点的法线(前后点的平均方向)Vector3[]directions=newVector3[points.Count];for(inti=0;i<points.Count;i++){if(i==0)directions[i]=(points[1]-points[0]).normalized;elseif(i==points.Count-1)directions[i]=(points[points.Count-1]-points[points.Count-2]).normalized;elsedirections[i]=((points[i+1]-points[i]).normalized+(points[i]-points[i-1]).normalized).normalized;}//生成线段部分的顶点for(inti=0;i<points.Count;i++){Vector3normal=newVector3(-directions[i].y,directions[i].x,0).normalized;vertices.Add(points[i]+normal*width*0.5f);//左侧vertices.Add(points[i]-normal*width*0.5f);//右侧}//生成线段部分的三角形for(inti=0;i<points.Count-1;i++){intstartIndex=i*2;//第一个三角形triangles.Add(startIndex);triangles.Add(startIndex+1);triangles.Add(startIndex+2);//第二个三角形triangles.Add(startIndex+2);triangles.Add(startIndex+1);triangles.Add(startIndex+3);}//2.生成起点半圆intstartCapCenterIndex=vertices.Count;vertices.Add(points[0]);//圆心//起点处的法线(第一个点的法线)Vector3startNormal=newVector3(-directions[0].y,directions[0].x,0).normalized;//起点处的方向(第一个线段的方向)Vector3startDir=directions[0];//生成半圆上的点for(inti=0;i<=capSegmentCount;i++){floatangle=-90+180*i/(float)capSegmentCount;//角度从-90到90度floatrad=angle*Mathf.Deg2Rad;Vector3dir=startNormal*Mathf.Cos(rad)+startDir*Mathf.Sin(rad);//注意:这里我们旋转的是在由startNormal和startDir构成的平面内(正交基)//因为startNormal和startDir是正交的(startDir是切线方向,startNormal是法线方向)vertices.Add(points[0]+dir*width*0.5f);}//生成起点半圆的三角形for(inti=0;i<capSegmentCount;i++){triangles.Add(startCapCenterIndex);//圆心triangles.Add(startCapCenterIndex+i+1);triangles.Add(startCapCenterIndex+i+2);}//3.生成终点半圆intendCapCenterIndex=vertices.Count;vertices.Add(points[points.Count-1]);//圆心//终点处的法线(最后一个点的法线)Vector3endNormal=newVector3(-directions[points.Count-1].y,directions[points.Count-1].x,0).normalized;Vector3endDir=directions[points.Count-1];//生成半圆上的点for(inti=0;i<=capSegmentCount;i++){floatangle=90+180*i/(float)capSegmentCount;//注意:终点半圆的方向应该与起点相反?因为终点处的法线方向与起点处定义相同,但线段方向是反的。//调整:我们想要的是从线段截面的左侧到右侧(与起点相同),所以旋转角度应该是从-90度到90度,但是以终点的法线为基准。//但注意:终点处的线段方向是endDir,它指向轨迹的末尾,而起点处的startDir指向轨迹的下一个点。所以在终点处,我们的半圆应该是在终点截面的后面(与起点对称)。//因此,我们仍然使用从-90度到90度。floatangle2=-90+180*i/(float)capSegmentCount;floatrad=angle2*Mathf.Deg2Rad;Vector3dir=endNormal*Mathf.Cos(rad)+endDir*Mathf.Sin(rad);vertices.Add(points[points.Count-1]+dir*width*0.5f);}//生成终点半圆的三角形for(inti=0;i<capSegmentCount;i++){triangles.Add(endCapCenterIndex);//圆心triangles.Add(endCapCenterIndex+i+2);//注意:这里顺序要逆时针,否则会背面triangles.Add(endCapCenterIndex+i+1);}//设置到Meshmesh.Clear();mesh.vertices=vertices.ToArray();mesh.triangles=triangles.ToArray();mesh.RecalculateNormals();}}注意:上述代码中,终点半圆的三角形顺序是逆时针,因为起点半圆是顺时针(取决于你的生成顺序和视角)。如果发现背面,可以调整三角形顺序(三个顶点的顺序)。另外,上述代码没有考虑UV,所以如果需要贴图,还需要生成UV坐标。由于是2D,我们可以简单设置UV,但这里暂不实现。此外,这种方法在转角处可能不会平滑(特别是锐角转角),因为每个点处的法线是前后方向的平均,所以锐角处会变宽。如果要解决,可以使用更复杂的算法(如miterjoint),但这里我们只要求圆头,所以两端圆头,中间部分即使有缝隙也可以接受。测试:将脚本挂在一个有MeshFilter和MeshRenderer的空物体上,并设置材质(比如简单的白色材质)。注意:需要设置targetCamera(主摄像机)。由于是实时更新,当点很多时,性能可能下降。可以考虑限制点的数量(比如只保留最近50个点)。以上是一个基本实现,可能需要调试和优化。由于时间关系,这里只提供核心思路和代码框架,实际应用中可能需要根据具体需求调整。</think>在Unity中使用Mesh绘制圆头鼠标移动轨迹,可以通过动态生成带有半圆端点的线状网格实现。以下是详细实现步骤和代码:
### 实现思路
1. **记录轨迹点**:存储鼠标移动的世界坐标点
2. **动态生成网格**:
- 主体部分:两点间生成矩形(两个三角形)
- 端点处理:起点/终点添加半圆帽(多个三角形)
3. **法线计算**:确保线段宽度一致
4. **实时更新**:根据新点更新网格数据
### 完整代码实现
```csharp
using UnityEngine;
using System.Collections.Generic;
public class RoundedTrailRenderer : MonoBehaviour
{
public Camera targetCamera;
public float width = 0.1f;
public int capSegments = 8; // 半圆细分度
public int maxPoints = 50; // 最大轨迹点数
private Mesh mesh;
private List<Vector3> points = new List<Vector3>();
private Vector3 lastMousePos;
void Start()
{
// 初始化网格组件
MeshFilter mf = gameObject.AddComponent<MeshFilter>();
MeshRenderer mr = gameObject.AddComponent<MeshRenderer>();
mr.material = new Material(Shader.Find("Standard"));
mesh = new Mesh();
mf.mesh = mesh;
}
void Update()
{
// 获取鼠标世界坐标(假设在XY平面)
Vector3 mousePos = Input.mousePosition;
mousePos.z = 10; // 相机前方距离
Vector3 worldPos = targetCamera.ScreenToWorldPoint(mousePos);
worldPos.z = 0; // 锁定Z轴
// 添加新点(移动距离足够时)
if (points.Count == 0 || Vector3.Distance(worldPos, lastMousePos) > 0.1f)
{
points.Add(worldPos);
lastMousePos = worldPos;
// 限制点数
if (points.Count > maxPoints)
points.RemoveAt(0);
GenerateMesh();
}
}
void GenerateMesh()
{
if (points.Count < 2)
{
mesh.Clear();
return;
}
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
// 1. 生成线段主体
for (int i = 0; i < points.Count; i++)
{
Vector3 direction = GetDirection(i);
Vector3 normal = new Vector3(-direction.y, direction.x).normalized;
// 添加左右顶点
vertices.Add(points[i] + normal * width * 0.5f);
vertices.Add(points[i] - normal * width * 0.5f);
}
// 生成主体三角形
for (int i = 0; i < points.Count - 1; i++)
{
int start = i * 2;
triangles.Add(start);
triangles.Add(start + 1);
triangles.Add(start + 2);
triangles.Add(start + 2);
triangles.Add(start + 1);
triangles.Add(start + 3);
}
// 2. 生成端点圆帽
GenerateStartCap(vertices, triangles);
GenerateEndCap(vertices, triangles);
// 应用网格数据
mesh.Clear();
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
}
// 获取点的朝向(平滑处理)
Vector3 GetDirection(int index)
{
if (points.Count <= 1) return Vector3.right;
if (index == 0) // 起点
return (points[1] - points[0]).normalized;
if (index == points.Count - 1) // 终点
return (points[index] - points[index - 1]).normalized;
// 中间点:前后方向平均
return ((points[index] - points[index - 1]) +
(points[index + 1] - points[index])).normalized;
}
void GenerateStartCap(List<Vector3> verts, List<int> tris)
{
int centerIndex = verts.Count;
verts.Add(points[0]); // 圆心
Vector3 startDir = GetDirection(0);
Vector3 startNormal = new Vector3(-startDir.y, startDir.x).normalized;
// 生成半圆顶点
for (int i = 0; i <= capSegments; i++)
{
float angle = Mathf.Lerp(-90f, 90f, i / (float)capSegments);
Quaternion rot = Quaternion.AngleAxis(angle, Vector3.forward);
Vector3 dir = rot * startNormal;
verts.Add(points[0] + dir * width * 0.5f);
}
// 生成半圆三角形
for (int i = 0; i < capSegments; i++)
{
tris.Add(centerIndex);
tris.Add(centerIndex + i + 1);
tris.Add(centerIndex + i + 2);
}
}
void GenerateEndCap(List<Vector3> verts, List<int> tris)
{
int centerIndex = verts.Count;
verts.Add(points[points.Count - 1]); // 圆心
Vector3 endDir = -GetDirection(points.Count - 1); // 反向
Vector3 endNormal = new Vector3(-endDir.y, endDir.x).normalized;
// 生成半圆顶点
for (int i = 0; i <= capSegments; i++)
{
float angle = Mathf.Lerp(-90f, 90f, i / (float)capSegments);
Quaternion rot = Quaternion.AngleAxis(angle, Vector3.forward);
Vector3 dir = rot * endNormal;
verts.Add(points[points.Count - 1] + dir * width * 0.5f);
}
// 生成半圆三角形(逆时针)
for (int i = 0; i < capSegments; i++)
{
tris.Add(centerIndex);
tris.Add(centerIndex + i + 2);
tris.Add(centerIndex + i + 1);
}
}
}
```
### 使用说明
1. **组件挂载**:
- 创建空游戏对象
- 添加此脚本
- 在Inspector中设置:
- Target Camera:主摄像机
- Width:轨迹宽度
- Cap Segments:圆帽平滑度(值越高越圆滑)
2. **运行效果**:
- 移动鼠标生成彩色轨迹
- 轨迹两端自动生成半圆端点
- 旧点自动移除保持轨迹长度
### 优化建议
1. **性能优化**:
- 使用对象池复用网格
- 减少实时顶点计算频率
- 添加距离阈值检测(`Vector3.Distance`)
2. **视觉效果增强**:
```csharp
// 在GenerateMesh()末尾添加:
mesh.RecalculateBounds();
mesh.Optimize();
// 使用渐变材质
public Material gradientMaterial;
// 在Start()中:
GetComponent<Renderer>().material = gradientMaterial;
```
3. **3D空间适配**:
```csharp
// 修改GetDirection()中的法线计算:
Vector3 normal = Vector3.Cross(direction, Vector3.forward).normalized;
```
### 技术要点
1. **动态网格构建**:
- 使用`List<Vector3>`动态存储顶点
- 三角形索引顺序遵循顺时针/逆时针规则
- 每帧更新`mesh.vertices`和`mesh.triangles`
2. **圆帽算法**:
- 起点/终点作为圆心
- 通过角度插值计算半圆顶点
- 使用`Quaternion.AngleAxis`旋转法线方向
3. **方向平滑处理**:
- 中间点采用前后方向平均值
- 避免尖锐转角处的网格扭曲
- 特殊处理起点/终点方向
此实现相比Unity内置LineRenderer的优势在于完全可控的网格生成逻辑,特别适合需要特殊顶点处理的定制化轨迹效果[^1]。
[^1]: 基于Unity网格系统的自定义几何体生成原理。