COPY NAV导航网格寻路(4) -- 生成nav网格

本文介绍了一种任意多边形的三角化算法,用于将游戏地图中的可行走区域转化为nav网,以便在游戏中实现寻路功能。算法通过确定多边形内部的DT点来生成Delaunay三角网。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NAV导航网格寻路(4) -- 生成nav网格 - 竹石 - 游戏...

假设上图是一个游戏地图,红色的区域是不可行走的区域,浅灰色区域是可行走区域,要想在游戏中实现nav寻路,必须将可行走区域转化为nav网格并保存为一种固定形式的数据,如下图浅红色的三角形。

NAV导航网格寻路(4) -- 生成nav网格 - 竹石 - 游戏...

nav网格必须是凸多边形,这里使用三角型,当然也可以使用4边形。下面介绍一种任意多边形的三角化算法。算法来自论文《平面多边形域的快速约束 三角化》作者:曾薇 孟祥旭 杨承磊 杨义军。详细内容请参考该论文。

先来看几个定义:

A. 我们称点 p3 为直线 p1p2 的可见点,其必须满足下面三个条件:

(1) p3 在边 p1p2 的右侧 (顶点顺序为顺时针);

(2) p3 与 p1 可见,即 p1p3 不与任何一个约束边相交;

(3) p3 与 p2 可见

B. DT点

在一个约束Delaunay三角形中,其中与一条边相对的顶点称为该边的DT点。

确定 DT 点的过程如下:

Step1.  构造 Δp1p2p3 的外接圆 C(p1,p2,p3)及其网格包围盒 B(C(p1,p2,p3))

Step2. 依次访问网格包围盒内的每个网格单元:

            对未作当前趟数标记的网格单元进行搜索,并将其标记为当前趟数

           若某个网格单元中存在可见点 p, 并且 ∠p1pp2 > ∠p1p3p2,则令 p3=p1,转Step1;否则,转Step3.

Step3.  若当前网格包围盒内所有网格单元都已被标记为当前趟数,也即C(p1,p2,p3)内无可见点,则 p3 为的 p1p2 的 DT 点

生成Delaunay三角网格算法如下:

Step2. 取任意一条外边界边 p1p2 .

Step3.  计算 DT 点 p3,构成约束 Delaunay 三角形 Δp1p2p3 .

Step4. 如果新生成的边 p1p3 不是约束边,若已经在堆栈中,则将其从中删除;否则,将其放入堆栈;类似地,可处理 p3p2 .

Step5. 若堆栈不空,则从中取出一条边,转Step3;否则,算法停止 .

程序实现该算法(AS3语言)

1。数据结构

不难看出,要想实现该算法首先要确定一些基础对象,如点、线、三角型和多边形等对象。(才发现这个blog中竟然没有代码的格式NAV导航网格寻路(4) -- 生成nav网格 - 竹石 - 游戏...

二维点的定义 public class Vector2f {}  包括矢量加减、叉积等方法。

线的定义  public class Line2D {} 包括下面方法:

    classifyPoint(point:Vector2f, epsilon:Number = 0.000001):int
                 判断点与直线的关系,假设你站在a点朝向b点, 则输入点与直线的关系分为:Left, Right or Centered on the line
           equals(line:Line2D):Boolean
                 线段是否相等 (忽略方向)
           getDirection():Vector2f
                  直线方向
           intersection(other:Line2D, pIntersectPoint:Vector2f = null):int
                  判断两个直线关系 this line A = x0, y0 and B = x1, y1 other is A = x2, y2 and B = x3, y3
           length():Number
                  直线长度
           signedDistance(point:Vector2f):Number
                  给定点到直线的带符号距离,从a点朝向b点,右向为正,左向为负 

三角型定义 public class Triangle:

    getSide(sideIndex:int):Line2D
                  取得指定索引的边(从0开始,顺时针)
            getVertex(i:int):Vector2f
                   根据i返回顶点
            isPointIn(testPoint:Vector2f):Boolean
                   测试给定点是否在三角型中
            setVertex(i:int, point:Vector2f):void
                   根据i指定的索引设置三角形的顶点  

多边形定义  public class Polygon:

     public vertexV : Vector.<Vector2f>   //顶点列表,按顺时针方向排序

     cw():void 
                 将多边形的顶点按逆时针排序
            isCW():Boolean
                 将多边形的顶点按顺时针排序
            isSimplicity():Boolean
                 是否是简单多边形
            rectangle():Rectangle
                  返回矩形包围盒  Polygon 
            union(polygon:Polygon):Vector.<Polygon>
                  合并两个多边形(Weiler-Athenton算法) 

三角化的完整代码,注释比较全,就不再详细解释了 
/*
* @author 白连忱 
* date Jan 22, 2010
*/
package org.blch.geom
{
    import flash.display.Sprite;
    import flash.geom.Rectangle;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    
    /**
     * Delaunay
     * @langversion ActionScript 3.0
     * @playerversion Flash 10.0
     */
    public class Delaunay
    {
        public function Delaunay()
        {
        }
        
        private static const EPSILON:Number = 0.000001;    //精度
        
        private var polygonV:Vector.<Polygon>;        //所有多边形,第0个元素为区域外边界 (输入数据)
        
        private var vertexV:Vector.<Vector2f>;        //所有顶点列表, 前outEdgeVecNmu个为外边界顶点
        private var edgeV:Vector.<Line2D>;            //所有约束边
        
        private var outEdgeVecNmu:int;            //区域外边界顶点数
        
        private  var lineV:Vector.<Line2D>;    //线段堆栈
        
        private var triangleV:Vector.<Triangle>;     //生成的Delaunay三角形
        
        public function createDelaunay(polyV:Vector.<Polygon>):Vector.<Triangle> {
            //Step1.     建立单元大小为 E*E 的均匀网格,并将多边形的顶点和边放入其中.
            //            其中 E=sqrt(w*h/n),w 和 h 分别为多边形域包围盒的宽度、高度,n 为多边形域的顶点数 .
            initData(polyV);
            
            //Step2.    取任意一条外边界边 p1p2 .
            var initEdge:Line2D = getInitOutEdge();
            lineV.push(initEdge);
            
            var edge:Line2D;
            do {
                //Step3.     计算 DT 点 p3,构成约束 Delaunay 三角形 Δp1p2p3 .
                edge = lineV.pop();
//                trace("开始处理edge###########:", edge);
                var p3:Vector2f = findDT(edge);
                if (p3 == null) continue;
                var line13:Line2D = new Line2D(edge.getPointA(), p3);
                var line32:Line2D = new Line2D(p3, edge.getPointB());
                
                //Delaunay三角形放入输出数组
                var trg:Triangle = new Triangle(edge.getPointA(), edge.getPointB(), p3);
                
//                trace("DT 点p3", p3);
//                trace("Triangle", trg);
                triangleV.push(trg);
                
                //Step4.    如果新生成的边 p1p3 不是约束边,若已经在堆栈中,
                //            则将其从中删除;否则,将其放入堆栈;类似地,可处理 p3p2 .
                var index:int;
                if (indexOfVector(line13, this.edgeV) < 0) {
                    index = indexOfVector(line13, lineV);
                    if (index > -1) {
                        lineV.splice(index, 1);
                    } else {
                        lineV.push(line13);
                    }
                }
                if (indexOfVector(line32, this.edgeV) < 0) {
                    index = indexOfVector(line32, lineV);
                    if (index > -1) {
                        lineV.splice(index, 1);
                    } else {
                        lineV.push(line32);
                    }
                }
            
            //Step5.    若堆栈不空,则从中取出一条边,转Step3;否则,算法停止 .
//                trace("处理结束edge###########\n");
            } while (lineV.length > 0);
                
            return triangleV;
        }
        
        /**
         * 初始化数据
         * @param polyV
         */        
        private function initData(polyV:Vector.<Polygon>):void {
            //填充顶点和线列表
            vertexV = new Vector.<Vector2f>();
            edgeV = new Vector.<Line2D>();
            var poly:Polygon;
            for (var i:int=0; i<polyV.length; i++) {
                poly = polyV[i];
                putVertex(vertexV, poly.vertexV);
                putEdge(edgeV, poly.vertexV);
            }
            
            outEdgeVecNmu = polyV[0].vertexNmu;
            
            lineV = new Vector.<Line2D>();
            triangleV = new Vector.<Triangle>();
        }
        
        /**
         * 获取初始外边界
         * @return 
         */        
        private function getInitOutEdge():Line2D {
            var initEdge:Line2D = edgeV[0];
            //检查是否有顶点p在该边上,如果有则换一个外边界
            var loopSign:Boolean;
            var loopIdx:int = 0;
            do {
                loopSign = false;
                loopIdx++;
                for each (var testV:Vector2f in this.vertexV) {
                    if ( testV.equals(initEdge.getPointA()) || testV.equals(initEdge.getPointB()) ) continue;
                    if (initEdge.classifyPoint(testV, EPSILON) == PointClassification.ON_LINE) {
                        loopSign = true;
                        initEdge = edgeV[loopIdx];
                        break;
                    }
                }
            } while (loopSign && loopIdx<outEdgeVecNmu-1);    //只取外边界
            return initEdge;
        }
        
        /**
         * 将srcV中的点放入dstV
         * @param dstV
         * @param srcV
         */        
        private function putVertex(dstV:Vector.<Vector2f>, srcV:Vector.<Vector2f>):void {
            for each (var item:Vector2f in srcV) {
                dstV.push(item);
            }
        }
        
        /**
         * 根据srcV中的点生成多边形线段,并放入dstV
         * @param dstV
         * @param srcV
         */        
        private function putEdge(dstV:Vector.<Line2D>, srcV:Vector.<Vector2f>):void {
            if (srcV.length < 3) return;    //不是一个多边形
            
            var p1:Vector2f = srcV[0];
            var p2:Vector2f;
            for (var i:int=1; i<srcV.length; i++) {
                p2 = srcV[i];
                dstV.push(new Line2D(p1, p2));
                p1 = p2;
            }
            p2 = srcV[0];
            dstV.push(new Line2D(p1, p2));
        }
        
        /**
         * 判断线段是否是约束边
         * @param line
         * @return 线段的索引,如果没有找到,返回-1
         */        
        private function indexOfVector(line:Line2D, vector:Vector.<Line2D>):int {
            var lt:Line2D;
            for (var i:int=0; i<vector.length; i++) {
                lt = vector[i];
                if (lt.equals(line)) return i;
            }
            return -1;
        }
        
        /**
         * 计算 DT 点
         * @param line
         * @return 
         */        
        private function findDT(line:Line2D):Vector2f {
            var p1:Vector2f = line.getPointA();
            var p2:Vector2f = line.getPointB();
            
            //搜索所有可见点             TODO 按y方向搜索距线段终点最近的点
            var allVPoint:Vector.<Vector2f> = new Vector.<Vector2f>();        // line的所有可见点
            for each (var vt:Vector2f in this.vertexV) {
                if (isVisiblePointOfLine(vt, line)) {
                    allVPoint.push(vt);
                }
            }

            if (allVPoint.length == 0) return null;
            
            var p3:Vector2f = allVPoint[0];   

            var loopSign:Boolean = false;
            do {
                loopSign = false;
                
                //Step1. 构造 Δp1p2p3 的外接圆 C(p1,p2,p3)及其网格包围盒 B(C(p1,p2,p3))
                var circle:Circle = this.circumCircle(p1, p2, p3);
                var boundsBox:Rectangle = this.circleBounds(circle);
                
                //Step2. 依次访问网格包围盒内的每个网格单元:
                //         若某个网格单元中存在可见点 p, 并且 ∠p1pp2 > ∠p1p3p2,则令 p3=p,转Step1;否则,转Step3.
                var angle132:Number = Math.abs(lineAngle(p1, p3, p2));    // ∠p1p3p2
                for each (var vec:Vector2f in allVPoint) {
                    if ( vec.equals(p1) || vec.equals(p2) || vec.equals(p3) ) {
                        continue;
                    }
                    //不在包围盒中
                    if (boundsBox.contains(vec.x, vec.y) == false) {
                        continue;
                    }
                    
                    //夹角
                    var a1:Number = Math.abs(lineAngle(p1, vec, p2));
                    if (a1 > angle132) {
                        /////转Step1
                        p3 = vec;
                        loopSign = true;
                        break;
                    }
                }
                ///////转Step3
            } while (loopSign); 
            
            //Step3. 若当前网格包围盒内所有网格单元都已被处理完,
            //         也即C(p1,p2,p3)内无可见点,则 p3 为的 p1p2 的 DT 点
            return p3;
        }
        
        /**
         * 返回顶角在o点,起始边为os,终止边为oe的夹角, 即∠soe (单位:弧度) 
         * 角度小于pi,返回正值;   角度大于pi,返回负值 
         */        
        private function lineAngle(s:Vector2f, o:Vector2f, e:Vector2f):Number 
        { 
            var cosfi:Number, fi:Number, norm:Number; 
            var dsx:Number = s.x - o.x; 
            var dsy:Number = s.y - o.y; 
            var dex:Number = e.x - o.x; 
            var dey:Number = e.y - o.y; 
            
            cosfi = dsx*dex + dsy*dey; 
            norm = (dsx*dsx + dsy*dsy) * (dex*dex + dey*dey); 
            cosfi /= Math.sqrt( norm ); 
            
            if (cosfi >=  1.0 ) return 0; 
            if (cosfi <= -1.0 ) return -Math.PI; 
            
            fi = Math.acos(cosfi); 
            if (dsx*dey - dsy*dex > 0) return fi;      // 说明矢量os 在矢量 oe的顺时针方向 
            return -fi; 
        } 
        
        /**
         * 返回圆的包围盒
         * @param c
         * @return 
         */        
        private function circleBounds(c:Circle):Rectangle {
            return new Rectangle(c.center.x-c.r, c.center.y-c.r, c.r*2, c.r*2);
        }
        
        /**
         * 返回三角形的外接圆
         * @param p1
         * @param p2
         * @param p3
         * @return 
         */        
        private function circumCircle(p1:Vector2f, p2:Vector2f, p3:Vector2f):Circle {
            var m1:Number,m2:Number,mx1:Number,mx2:Number,my1:Number,my2:Number;
            var dx:Number,dy:Number,rsqr:Number,drsqr:Number;
            var xc:Number, yc:Number, r:Number;
            
            /* Check for coincident points */
            
            if ( Math.abs(p1.y-p2.y) < EPSILON && Math.abs(p2.y-p3.y) < EPSILON )
            {
                trace("CircumCircle: Points are coincident.");
                return null;
            }
            
            m1 = - (p2.x - p1.x) / (p2.y - p1.y);
            m2 = - (p3.x-p2.x) / (p3.y-p2.y);
            mx1 = (p1.x + p2.x) / 2.0;
            mx2 = (p2.x + p3.x) / 2.0;
            my1 = (p1.y + p2.y) / 2.0;
            my2 = (p2.y + p3.y) / 2.0;
            
            if ( Math.abs(p2.y-p1.y) < EPSILON ) {
                xc = (p2.x + p1.x) / 2.0;
                yc = m2 * (xc - mx2) + my2;
            } else if ( Math.abs(p3.y - p2.y) < EPSILON ) {
                xc = (p3.x + p2.x) / 2.0;
                yc = m1 * (xc - mx1) + my1;    
            } else {
                xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
                yc = m1 * (xc - mx1) + my1;
            }
            
            dx = p2.x - xc;
            dy = p2.y - yc;
            rsqr = dx*dx + dy*dy;
            r = Math.sqrt(rsqr);
            
            return new Circle(new Vector2f(xc, yc), r);
        }
        
        /**
         * 判断点vec是否为line的可见点
         * @param vec
         * @param line
         * @return true:vec是line的可见点
         */        
        private function isVisiblePointOfLine(vec:Vector2f, line:Line2D):Boolean {
            if (vec.equals(line.getPointA()) || vec.equals(line.getPointB())) {
                return false;
            }
            
            //(1) p3 在边 p1p2 的右侧 (多边形顶点顺序为顺时针);
            if (line.classifyPoint(vec, EPSILON) != PointClassification.RIGHT_SIDE)
            {
                return false;
            }
            
            //(2) p3 与 p1 可见,即 p1p3 不与任何一个约束边相交;
            if (isVisibleIn2Point(line.getPointA(), vec) == false) {
                return false;
            }
            
            //(3) p3 与 p2 可见
            if (isVisibleIn2Point(line.getPointB(), vec) == false) {
                return false;
            }
            
            return true;
        }
        
        /**
         * 点pa和pb是否可见(pa和pb构成的线段不与任何约束边相交,不包括顶点)
         * @param pa
         * @param pb
         * @return 
         */
        private function isVisibleIn2Point(pa:Vector2f, pb:Vector2f):Boolean {
            var linepapb:Line2D = new Line2D(pa, pb);
            var interscetVector:Vector2f = new Vector2f();        //线段交点
            for each (var lineTmp:Line2D in this.edgeV) {
                //两线段相交
                if (linepapb.intersection(lineTmp, interscetVector) == LineClassification.SEGMENTS_INTERSECT) {
                    //交点是不是端点
                    if ( !pa.equals(interscetVector) && !pb.equals(interscetVector) ) {
                        return false;
                    }
                }
            }
            return true;
        }
        
    }
}

import org.blch.geom.Vector2f;
/**
 * 圆
 * @author blc
 */
class Circle {
    public var center:Vector2f;        //圆心
    public var r:Number;            //半径
        
    public function Circle(cen:Vector2f, r:Number) {
        this.center = cen;
        this.r = r;
    }
}

using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.AI; using TMPro; using static ControlTheSceneXF; public class PathManager : MonoBehaviour { [Header("寻路设置")] public GameObject mainArrowPrefab; // 主箭头预制体 public GameObject secondaryArrowPrefab; // 辅助箭头预制体 public float moveSpeed = 3.5f; // 箭头移动速度 public float arrivalThreshold = 1f; // 到达点的阈值距离 public float pathHeight = 0.5f; // 路径离地面的高度 private bool isUpdate = false; [Header("路径查找设置")] public float secondaryPathRange = 2.0f; // 辅助路径查找范围 public int maxJunctionsPerSegment = 3; // 每段路径最多添加的路口点数量 [Header("寻路标签设置")] public string pathPointTag = "PathPoint"; // 路径点标签 public string junctionPointTag = "JunctionPoint"; // 路口点标签 public LayerMask pathPointLayer; // 路径点Layer public bool useTagInsteadOfLayer = true; // 使用标签而不是Layer [Header("路径可视化")] public bool showPathsInGame = true; // 是否显示路径 public Material mainPathMaterial; // 主路径材质 public Material secondaryPathMaterial; // 辅助路径材质 public float pathWidth = 0.2f; // 路径宽度 [Header("显示调试Gizmos")] public bool showDebugGizmos = true; // 显示调试Gizmos public bool logDebugInfo = true; // 打印调试信息 public float navMeshSampleDistance = 5f; // 导航网格采样距离 // 路径数据 private List<Transform> pathPoints = new List<Transform>(); private List<ArrowController> activeArrows = new List<ArrowController>(); private Dictionary<ArrowController, LineRenderer> arrowPathRenderers = new Dictionary<ArrowController, LineRenderer>(); private Dictionary<Vector3, List<Vector3>> junctionConnections = new Dictionary<Vector3, List<Vector3>>(); // 主路径和辅助路径 private List<Vector3> mainPath = new List<Vector3>(); private Dictionary<Vector3, List<List<Vector3>>> secondaryPaths = new Dictionary<Vector3, List<List<Vector3>>>(); private ArrowController mainArrow; void Update() { // 更新所有箭头 foreach (var arrow in activeArrows.ToArray()) { if (arrow != null) { arrow.UpdateMovement(); // 更新路径渲染 if (showPathsInGame && arrowPathRenderers.ContainsKey(arrow)) { UpdatePathRenderer(arrow, arrowPathRenderers[arrow]); } // 检查主箭头是否到达路径点 if (arrow == mainArrow && mainPath.Count > 0) { int currentIndex = arrow.CurrentPathIndex; if (currentIndex >= 0 && currentIndex < mainPath.Count) { Vector3 pathPoint = mainPath[currentIndex]; float distance = Vector3.Distance(arrow.transform.position, pathPoint); // 当接近路径点时检查辅助路径 if (distance < arrivalThreshold * 1.5f) { // 检查并激活辅助路径 CheckAndActivateSecondaryPaths(pathPoint); } } } } else { if (arrowPathRenderers.ContainsKey(arrow)) { Destroy(arrowPathRenderers[arrow].gameObject); arrowPathRenderers.Remove(arrow); } activeArrows.Remove(arrow); } } } /// <summary> /// 更新路径渲染器,逐步绘制路径 /// </summary> void UpdatePathRenderer(ArrowController arrow, LineRenderer lineRenderer) { if (arrow == null || lineRenderer == null) return; // 获取箭头已经经过的路径点 int pointsToShow = Mathf.Min(arrow.CurrentPathIndex + 1, arrow.PathPoints.Count); if (pointsToShow < 2) return; // 创建临时列表存储要显示的点 List<Vector3> points = new List<Vector3>(); points.AddRange(arrow.PathPoints.GetRange(0, pointsToShow)); // 添加当前箭头位置作为最后一个点 points.Add(arrow.transform.position); // 更新LineRenderer lineRenderer.positionCount = points.Count; lineRenderer.SetPositions(points.ToArray()); } /// <summary> /// 收集所有路径点并按时间排序 /// </summary> void CollectPathPoints() { pathPoints.Clear(); List<Transform> allPathPoints = FindAllPathPoints(); if (allPathPoints.Count == 0) { Debug.LogError("没有找到路径点!"); enabled = false; return; } // 按时间排序路径点 pathPoints = allPathPoints .Where(p => p.GetComponent<GjImagePrefab>() != null) .OrderBy(p => p.GetComponent<GjImagePrefab>().GetTimeInMinutes()) .ToList(); } /// <summary> /// 查找场景中所有路径点 /// </summary> List<Transform> FindAllPathPoints() { List<Transform> points = new List<Transform>(); if (useTagInsteadOfLayer) { // 通过标签查找路径点 GameObject[] taggedObjects = GameObject.FindGameObjectsWithTag(pathPointTag); foreach (GameObject obj in taggedObjects) { points.Add(obj.transform); ValidatePointPosition(obj.transform); } // 通过标签查找路口点并构建连接图 GameObject[] junctionObjects = GameObject.FindGameObjectsWithTag(junctionPointTag); BuildJunctionConnectionGraph(junctionObjects); } else { // 通过Layer查找(略) } return points; } /// <summary> /// 构建路口连接图 /// </summary> void BuildJunctionConnectionGraph(GameObject[] junctions) { junctionConnections.Clear(); // 初始化连接图 foreach (var junction in junctions) { Vector3 pos = junction.transform.position; if (!junctionConnections.ContainsKey(pos)) { junctionConnections.Add(pos, new List<Vector3>()); } } // 添加连接 foreach (var junction in junctions) { JunctionPoint junctionComp = junction.GetComponent<JunctionPoint>(); if (junctionComp != null && junctionComp.connectedPoints != null) { Vector3 startPos = junction.transform.position; foreach (var connectedPoint in junctionComp.connectedPoints) { if (connectedPoint != null) { Vector3 endPos = connectedPoint.position; if (!junctionConnections[startPos].Contains(endPos)) { junctionConnections[startPos].Add(endPos); } // 确保双向连接 if (junctionConnections.ContainsKey(endPos) && !junctionConnections[endPos].Contains(startPos)) { junctionConnections[endPos].Add(startPos); } } } } } } /// <summary> /// 验证路径点是否在导航网格上 /// </summary> void ValidatePointPosition(Transform point) { NavMeshHit hit; if (!NavMesh.SamplePosition(point.position, out hit, navMeshSampleDistance, NavMesh.AllAreas)) { Debug.LogWarning($"路径点 '{point.name}' 不在导航网格上! 位置: {point.position}"); if (NavMesh.FindClosestEdge(point.position, out hit, NavMesh.AllAreas)) { point.position = hit.position; Debug.Log($"调整 '{point.name}' 到最近的导航网格点: {hit.position}"); } } } /// <summary> /// 计算主路径 /// </summary> public void CalculateMainPath() { mainPath.Clear(); secondaryPaths.Clear(); if (pathPoints.Count < 2) { Debug.LogWarning("路径点数不足"); return; } // 1. 计算基础路径 - 直接连接所有PathPoint的最短路径 mainPath = CalculatePathThroughAllPoints(pathPoints); if (mainPath == null || mainPath.Count < 2) { Debug.LogError("主路径计算失败!"); return; } // 2. 在关键路口点预计算辅助路径 PrecalculateSecondaryPaths(); } /// <summary> /// 预计算所有可能的分支路径(核心修改) /// </summary> void PrecalculateSecondaryPaths() { secondaryPaths.Clear(); // 遍历主路径上的每一段(从当前点到下一个点) for (int i = 0; i < mainPath.Count - 1; i++) { Vector3 currentPoint = mainPath[i]; Vector3 nextPoint = mainPath[i + 1]; // 计算当前段的直径范围 Vector3 segmentCenter = (currentPoint + nextPoint) / 2f; float segmentDiameter = Vector3.Distance(currentPoint, nextPoint); // 查找范围内的所有路口点 List<Vector3> nearbyJunctions = FindNearbyJunctions(segmentCenter, segmentDiameter * secondaryPathRange); if (logDebugInfo) Debug.Log($"主路径段 {i}: {currentPoint} -> {nextPoint}, 找到{nearbyJunctions.Count}个附近路口点"); List<List<Vector3>> alternatives = new List<List<Vector3>>(); // 尝试为每个路口点创建替代路径 foreach (var junction in nearbyJunctions) { // 跳过主路径点本身 if (junction == currentPoint || junction == nextPoint) continue; // 计算替代路径:当前点 -> 路口点 -> 下一个点 List<Vector3> altPath = CalculateAlternativePath(currentPoint, junction, nextPoint); if (altPath != null && altPath.Count > 1) { alternatives.Add(altPath); if (logDebugInfo) Debug.Log($"创建替代路径: {currentPoint} -> {junction} -> {nextPoint}, 点数: {altPath.Count}"); // 限制每段路径的最大分支数 if (alternatives.Count >= maxJunctionsPerSegment) break; } } if (alternatives.Count > 0) { // 使用当前点作为键存储替代路径 secondaryPaths[currentPoint] = alternatives; if (logDebugInfo) Debug.Log($"为点 {currentPoint} 添加 {alternatives.Count} 条替代路径"); } } } /// <summary> /// 查找范围内的路口点 /// </summary> List<Vector3> FindNearbyJunctions(Vector3 center, float radius) { List<Vector3> nearby = new List<Vector3>(); foreach (var junction in junctionConnections.Keys) { if (Vector3.Distance(junction, center) <= radius) { nearby.Add(junction); } } return nearby; } /// <summary> /// 计算替代路径(当前点 -> 路口点 -> 目标点) /// </summary> List<Vector3> CalculateAlternativePath(Vector3 start, Vector3 junction, Vector3 end) { // 计算三段路径 List<Vector3> pathToJunction = CalculatePath(start, junction); List<Vector3> pathFromJunctionToEnd = CalculatePath(junction, end); if (pathToJunction == null || pathToJunction.Count == 0 || pathFromJunctionToEnd == null || pathFromJunctionToEnd.Count == 0) { return null; } // 合并路径 List<Vector3> fullPath = new List<Vector3>(); fullPath.AddRange(pathToJunction); fullPath.AddRange(pathFromJunctionToEnd.Skip(1)); // 跳过重复点 return fullPath; } /// <summary> /// 检查并激活辅助路径 /// </summary> void CheckAndActivateSecondaryPaths(Vector3 junctionPoint) { if (!secondaryPaths.ContainsKey(junctionPoint)) { if (logDebugInfo) Debug.Log($"点 {junctionPoint} 没有预计算的辅助路径"); return; } // 获取该点的所有辅助路径 List<List<Vector3>> paths = secondaryPaths[junctionPoint]; if (logDebugInfo) Debug.Log($"在点 {junctionPoint} 找到 {paths.Count} 条辅助路径"); foreach (var path in paths) { if (path == null || path.Count < 2) { if (logDebugInfo) Debug.LogWarning("跳过无效的辅助路径"); continue; } // 检查是否已存在相同路径的箭头 bool pathExists = false; foreach (var arrow1 in activeArrows) { if (arrow1 == null || arrow1.IsMainArrow) continue; // 简单比较路径起点和终点 if (Vector3.Distance(arrow1.StartPosition, path[0]) < 0.1f && Vector3.Distance(arrow1.EndPosition, path[path.Count - 1]) < 0.1f) { pathExists = true; break; } } if (pathExists) { if (logDebugInfo) Debug.Log($"辅助路径已存在: {path[0]} -> {path[path.Count - 1]}"); continue; } // 创建辅助箭头 GameObject arrowObj = Instantiate(secondaryArrowPrefab, path[0], Quaternion.identity); ArrowController arrow = arrowObj.GetComponent<ArrowController>(); if (arrow == null) { arrow = arrowObj.AddComponent<ArrowController>(); } arrow.Initialize(this, path, false); // 设置箭头材质 var renderer = arrowObj.GetComponent<MeshRenderer>(); if (renderer != null && secondaryPathMaterial != null) { renderer.material = secondaryPathMaterial; } activeArrows.Add(arrow); // 创建辅助路径渲染器 if (showPathsInGame) { CreatePathRenderer(arrow, path, secondaryPathMaterial); } if (logDebugInfo) Debug.Log($"创建辅助路径箭头: {path[0]} -> {path[path.Count - 1]}, 点数: {path.Count}"); } } /// <summary> /// 计算经过所有路径点的完整路径 /// </summary> List<Vector3> CalculatePathThroughAllPoints(List<Transform> points) { if (points.Count < 2) return null; List<Vector3> fullPath = new List<Vector3>(); for (int i = 0; i < points.Count - 1; i++) { Vector3 start = points[i].position; Vector3 end = points[i + 1].position; // 计算两点之间的路径 List<Vector3> segment = CalculatePath(start, end); if (segment == null || segment.Count == 0) { Debug.LogWarning($"路径段 {i} 计算失败,在 {points[i].name} 和 {points[i + 1].name} 之间"); return null; } // 如果是第一个段,添加全部点 if (i == 0) { fullPath.AddRange(segment); } else { // 移除重复点(上一段的终点) if (fullPath.Count > 0) fullPath.RemoveAt(fullPath.Count - 1); fullPath.AddRange(segment); } } return fullPath; } /// <summary> /// 计算两点之间的导航路径 /// </summary> List<Vector3> CalculatePath(Vector3 startPos, Vector3 endPos) { NavMeshHit hitStart, hitEnd; if (!NavMesh.SamplePosition(startPos, out hitStart, navMeshSampleDistance, NavMesh.AllAreas) || !NavMesh.SamplePosition(endPos, out hitEnd, navMeshSampleDistance, NavMesh.AllAreas)) { Debug.LogError($"点不在导航网格上!起点: {startPos}, 终点: {endPos}"); return null; } NavMeshPath navPath = new NavMeshPath(); if (!NavMesh.CalculatePath(hitStart.position, hitEnd.position, NavMesh.AllAreas, navPath)) { Debug.LogError($"路径计算失败!起点: {startPos}, 终点: {endPos}"); return null; } // 调整路径高度 List<Vector3> adjustedPath = new List<Vector3>(); foreach (Vector3 point in navPath.corners) { adjustedPath.Add(new Vector3(point.x, point.y + pathHeight, point.z)); } return adjustedPath; } /// <summary> /// 创建主箭头 /// </summary> void CreateMainArrow() { if (mainArrowPrefab == null) { Debug.LogError("未设置主箭头预制体!"); return; } // 清除现有箭头 foreach (var arrow in activeArrows) if (arrow != null) Destroy(arrow.gameObject); activeArrows.Clear(); foreach (var renderer1 in arrowPathRenderers.Values) if (renderer1 != null) Destroy(renderer1.gameObject); arrowPathRenderers.Clear(); if (mainPath == null || mainPath.Count == 0) return; // 创建主箭头 GameObject arrowObj = Instantiate(mainArrowPrefab, mainPath[0], Quaternion.identity); mainArrow = arrowObj.GetComponent<ArrowController>(); if (mainArrow == null) { mainArrow = arrowObj.AddComponent<ArrowController>(); } mainArrow.Initialize(this, mainPath, true); // 设置箭头材质 var renderer = arrowObj.GetComponent<MeshRenderer>(); if (renderer != null && mainPathMaterial != null) { renderer.material = mainPathMaterial; } activeArrows.Add(mainArrow); // 创建主路径渲染器 if (showPathsInGame) { CreatePathRenderer(mainArrow, mainPath, mainPathMaterial); } } /// <summary> /// 创建路径渲染器 /// </summary> void CreatePathRenderer(ArrowController arrow, List<Vector3> path, Material material) { if (path == null || path.Count < 2) return; GameObject pathObj = new GameObject("PathRenderer"); pathObj.transform.SetParent(transform); LineRenderer lineRenderer = pathObj.AddComponent<LineRenderer>(); lineRenderer.material = material; lineRenderer.startWidth = pathWidth; lineRenderer.endWidth = pathWidth; lineRenderer.useWorldSpace = true; // 初始只设置第一个点 lineRenderer.positionCount = 1; lineRenderer.SetPosition(0, path[0]); arrowPathRenderers[arrow] = lineRenderer; } /// <summary> /// 手动重新计算路径 /// </summary> public void RecalculatePaths() { isUpdate = true; ClearAllPaths(); CollectPathPoints(); CalculateMainPath(); CreateMainArrow(); } /// <summary> /// 清除所有路径和箭头 /// </summary> public void ClearAllPaths() { isUpdate = false; foreach (var arrow in activeArrows) if (arrow != null) Destroy(arrow.gameObject); activeArrows.Clear(); pathPoints.Clear(); junctionConnections.Clear(); mainPath.Clear(); secondaryPaths.Clear(); } void OnDrawGizmos() { if (!showDebugGizmos) return; // 绘制路径点 Gizmos.color = Color.cyan; foreach (Transform point in pathPoints) { Gizmos.DrawSphere(point.position, 0.5f); Gizmos.DrawWireSphere(point.position, navMeshSampleDistance); } // 绘制路口点 Gizmos.color = Color.magenta; foreach (var junction in junctionConnections.Keys) { Gizmos.DrawSphere(junction, 0.3f); // 绘制路口连接 if (junctionConnections.ContainsKey(junction)) { foreach (var connectedPoint in junctionConnections[junction]) { Gizmos.DrawLine(junction, connectedPoint); } } } // 绘制主路径 if (mainPath.Count > 1) { Gizmos.color = Color.green; for (int i = 1; i < mainPath.Count; i++) { Gizmos.DrawLine(mainPath[i - 1], mainPath[i]); } } // 绘制辅助路径 Gizmos.color = Color.yellow; foreach (var kvp in secondaryPaths) { foreach (var path in kvp.Value) { for (int i = 1; i < path.Count; i++) { Gizmos.DrawLine(path[i - 1], path[i]); } } } } } using System.Collections.Generic; using TMPro; using UnityEngine; public class ArrowController : MonoBehaviour { private List<Vector3> currentPath; private int currentCornerIndex = 0; private float moveSpeed; private float arrivalThreshold; private bool isMainArrow; private PathManager pathManager; private List<Vector3> pathPoints; // Public properties for path checking public bool IsMainArrow => isMainArrow; public Vector3 StartPosition => pathPoints != null && pathPoints.Count > 0 ? pathPoints[0] : Vector3.zero; public Vector3 EndPosition => pathPoints != null && pathPoints.Count > 0 ? pathPoints[pathPoints.Count - 1] : Vector3.zero; public List<Vector3> PathPoints => pathPoints; public int CurrentPathIndex => currentCornerIndex; public void Initialize(PathManager manager, List<Vector3> path, bool isMain) { pathPoints = new List<Vector3>(path); // Store a copy of the path points pathManager = manager; moveSpeed = manager.moveSpeed; arrivalThreshold = manager.arrivalThreshold; isMainArrow = isMain; SetPath(path); } public void SetPath(List<Vector3> path) { currentPath = path; currentCornerIndex = 0; if (currentPath != null && currentPath.Count > 0) transform.position = currentPath[0]; } public void UpdateMovement() { if (currentPath == null || currentPath.Count == 0) return; // Reached end of path if (currentCornerIndex >= currentPath.Count) { // Secondary arrows disappear when reaching the end if (!isMainArrow) { Destroy(gameObject); } else { // Main arrow loops currentCornerIndex = 0; transform.position = currentPath[0]; } return; } Vector3 targetPoint = currentPath[currentCornerIndex]; transform.position = Vector3.MoveTowards(transform.position, targetPoint, moveSpeed * Time.deltaTime); // Update arrow direction if (transform.position != targetPoint) { Vector3 direction = -(targetPoint - transform.position).normalized; if (direction != Vector3.zero) { transform.rotation = Quaternion.LookRotation(direction); } } // Check if reached current waypoint if (Vector3.Distance(transform.position, targetPoint) <= arrivalThreshold) currentCornerIndex++; } } 这两个脚本你给我修改一下,首先是主路径一定要结合navigation,先看从起点到终点的最短路线(这个不需要划线),然后把这条路线左右一定范围内的路口点和途径点用线条连接起来作为主路径线条(划线要)不论是主路径线条还是辅助路径线条都要结合navigation;另外当路径开始规划的时候,辅助路线不能走除了主路径的当前点和下一个点,辅助路径也不能走主路径已经走过的路口点的一定范围,也就是说,除了去到主路径的下一个路口点的周围外,主路径线条的左右一定范围内是没有辅助路径的,你给我修改一下,完整的脚本代码和中文注释给我
最新发布
06-07
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值