首先创建空物体命名AstarPath ,挂载Pathfinder组件,用来设置网格路径,目前只做了垂直和平面俩个面,实际可以根据需求创建多个面。主要用于在两点之间创建一条最优路线。设置参数:
框红的地方是需要注意的参数设置,包括当前网格的长宽,障碍物设置,可越过的最高坡度等等一些相关设置,会影响路径的计算,具体可以去参考下官方的参数文档。
然后可以找个空的节点或随意节点挂载seeker组件用于寻路的重要组件。
不用管seeker参数设置,默认的就好。
然后创建自定义脚本LineAroundObstacles.cs开始编写代码 这为了实现连线用到了MegaWire插件。
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Vectrosity;
using Pathfinding;
public class LineAroundObstacles : MonoBehaviour
{
[Header("线缆的材质")]
public Material wireMaterial;
[Header("线缆的半径")]
public float wireSize = 0.1f;
private bool isDrawing = false; // 绘制状态
private WireConnectionPoint startPoint; // 线缆的起始连接点
private Dictionary<MegaWire, List<WireConnectionPoint>> wireConnectionDatas = new Dictionary<MegaWire, List<WireConnectionPoint>>();// 存储当前激活的线缆
private List<WireConnectionPoint> wirePoints = new List<WireConnectionPoint>();
private VectorLine currentLine; // 当前正在拖拽的线条
private int indexSpanNum = -1; //记录当前线段下标
[Header("清空按钮")]
public Button clearLinesBtn;
[Header("确认连线按钮")]
public Button affirmButton;
[Header("透明按钮")]
public Button lucencyButton;
[Header("恢复按钮")]
public Button recoverButton;
[Header("返回按钮")]
public Button returnButton;
private List<GameObject> tempObjList = new List<GameObject>();
[Header("启用碰撞效果")]
public bool isCrash = false;
[Header("成功音效")]
public AudioClip succeedClip;
[Header("失败音效")]
public AudioClip failClip;
private AudioSource audioScource;
private HintPanel hintPanel;
public Light targetLight;
public Material targetLightMaterial;
public Material originalMaterial;
private Color originalColor;
private Seeker seeker;
private Path path;
void Start()
{
seeker = GetComponent<Seeker>();
//targetLight.gameObject.SetActive(false);
//targetLightMaterial.DisableKeyword("_EMISSION");
//if (originalMaterial != null)
//{
// originalColor = originalMaterial.color;
//}
wireConnectionDatas = new Dictionary<MegaWire, List<WireConnectionPoint>>(); // 初始化激活的线缆列表
wirePoints = new List<WireConnectionPoint>(FindObjectsOfType<WireConnectionPoint>());
//临时存十个空节点
for (int i = 0; i < 10; i++)
{
GameObject tempObj = new GameObject();
tempObj.name = i.ToString();
tempObjList.Add(tempObj);
}
}
void Update()
{
if (Input.GetMouseButtonDown(1))
{
// 获取当前鼠标点击的连接点
WireConnectionPoint clickedPoint = GetClickedConnectionPoint();
if (clickedPoint != null && !isDrawing)
{
startPoint = clickedPoint; // 设置起始点
// 创建新的 VectorLine
currentLine = new VectorLine("WireLine", new List<Vector3>(), wireSize, LineType.Continuous, Joins.Weld);
currentLine.material = wireMaterial;
// 添加起始点和一个初始的终点
currentLine.points3.Add(startPoint.transform.position); // 起始点
currentLine.points3.Add(startPoint.transform.position); // 添加一个默认的终点(与起点相同)
isDrawing = true;
}
else if (isDrawing)
{
// 如果已经在拖动,鼠标点击添加拐点
//Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
//if (Physics.Raycast(ray, out RaycastHit hit))
//{
Debug.Log("hit=>" + endClickPos);
currentLine.points3[currentLine.points3.Count - 1] = endClickPos;
currentLine.points3.Add(endClickPos);
currentLine.points3.Add(endClickPos);
//}
}
}
if (isDrawing)
{
UpdateWirePosition();
}
if (Input.GetMouseButtonDown(2))
{
WireConnectionPoint endPoint = GetClickedConnectionPoint();
if (endPoint != null && endPoint != startPoint)
{
currentLine.points3[currentLine.points3.Count - 1] = endPoint.transform.position;
CreateWire(currentLine.points3, endPoint, () =>
{
if (currentLine != null && currentLine.points3.Count > 0)
{
currentLine.points3.Clear();
VectorLine.Destroy(ref currentLine);
}
});
}
else
{
if (currentLine != null && currentLine.points3.Count > 0)
{
currentLine.points3.Clear();
VectorLine.Destroy(ref currentLine);
}
}
isDrawing = false;
}
if (Input.GetKeyDown(KeyCode.Z))
{
if (indexSpanNum >= 0)
{
//从最后一位删除
MegaWire lastKey = wireConnectionDatas.Keys.Last();
if (wireConnectionDatas[lastKey] != null)
{
foreach (WireConnectionPoint point in wireConnectionDatas[lastKey])
{
for (int i = 0; i < point.isConnect.Count; i++)
{
point.isConnect[i] = false;
}
}
}
DeleteWire(lastKey);
wireConnectionDatas.Remove(lastKey);
}
}
}
WireConnectionPoint GetClickedConnectionPoint()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
return hit.collider.GetComponent<WireConnectionPoint>();
}
return null;
}
Vector3 endClickPos;
void UpdateWirePosition()
{
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit))
{
Vector3 mousePos = hit.point;
Vector3 startPoint = currentLine.points3[currentLine.points3.Count - 2];
Vector3 constrainedPosition;
constrainedPosition = mousePos;
// 更新线条的终点
if (currentLine != null && currentLine.points3.Count > 0)
{
endClickPos = constrainedPosition;
currentLine.points3[currentLine.points3.Count - 1] = constrainedPosition;
currentLine.Draw3D();
}
}
}
//多面路径计算
List<Vector3> multipathList = new List<Vector3>();
void CreateWire(List<Vector3> posList, WireConnectionPoint endPoint, UnityAction callback)
{
indexSpanNum++;
HashSet<Vector3> seen = new HashSet<Vector3>();
posList.RemoveAll(item => !seen.Add(item));
List<Vector3> changePos = new List<Vector3>(seen);
while (tempObjList.Count < changePos.Count)
{
GameObject temp = new GameObject();
tempObjList.Add(temp);
}
while (tempObjList.Count > changePos.Count)
{
Destroy(tempObjList[tempObjList.Count - 1]);
tempObjList.RemoveAt(tempObjList.Count - 1);
}
for (int i = 0; i < changePos.Count; i++)
{
tempObjList[i].transform.position = changePos[i];
}
MegaWire wire = null;
// 执行扫描,更新图层
AstarPath.active.Scan();
// 获取起点和终点的最近节点
var startNode = AstarPath.active.GetNearest(startPoint.transform.position);
var endNode = AstarPath.active.GetNearest(endPoint.transform.position);
// 输出调试信息,确认起点和终点位置
Debug.Log("Start Node: " + startNode.node.position+" "+(startNode.node.Graph== endNode.node.Graph));
Debug.Log("End Node: " + endNode.node.position);
if (startNode.node.Graph == endNode.node.Graph)
{
seeker.StartPath(startPoint.transform.position, endPoint.transform.position, (p) =>
{
if (p.error)
{
Debug.LogError("Pathfinding error: " + p.errorLog);
return;
}
if (p.vectorPath.Count > 0)
{
Debug.Log("currentNodes==>" + p.vectorPath.Count); //A*的path不包含头和尾
tempObjList = new List<GameObject>();
GameObject startObj = new GameObject();
startObj.transform.position = startPoint.transform.position;
tempObjList.Add(startObj);
foreach (Vector3 pos in p.vectorPath)
{
GameObject obj = new GameObject();
obj.transform.position = pos;
tempObjList.Add(obj);
}
GameObject endObj = new GameObject();
endObj.transform.position = endPoint.transform.position;
tempObjList.Add(endObj);
}
wire = MegaWire.Create(wire, tempObjList, wireMaterial, "Wire", null, wireSize, 1.0f);
//设置物理效果
wire.Mass = 0.1f;
wire.stretch = 0.1f;
wire.massRand = 0.1f;
wire.doCollisions = isCrash;
wire.enabled = false;
//wire.hidespans = false;
//wire.doCollisions = true; //贴墙不能设置碰撞,会有问题
//activeWires.Add(wire);
if (startPoint.pointName == endPoint.pointName)
{
for (int i = 0; i < startPoint.isConnect.Count; i++)
{
if (!startPoint.isConnect[i])
{
startPoint.isConnect[i] = true;
}
}
for (int i = 0; i < endPoint.isConnect.Count; i++)
{
if (!endPoint.isConnect[i])
List<WireConnectionPoint> currentPoints = new List<WireConnectionPoint> { startPoint, endPoint };
wireConnectionDatas.Add(wire, currentPoints);
}
else
{
//多连接错误的线路处理
wireConnectionDatas.Add(wire, null);
}
//if (activeWires.Count>1)
//{
// activeWires[activeWires.Count - 2].enabled = false;
//}
wire.RebuildWire();
callback?.Invoke();
});
}
else {
multipathList = new List<Vector3>();
GridGraph graphStart = startNode.node.Graph as GridGraph;
GridGraph graphend = endNode.node.Graph as GridGraph;
// Step 1: 根据终点的位置,计算起点图层的边界点
Vector3 borderPoint1 = GetGraphBoundaryPoint(graphStart, startPoint.transform.position, endPoint.transform.position);
CalculatePath(startPoint.transform.position, borderPoint1, 0,endPoint,wire,callback); // 使用第一个图层
// Step 2: 根据起点的位置,计算终点图层的边界点
Vector3 borderPoint2 = GetGraphBoundaryPoint(graphend, endPoint.transform.position, startPoint.transform.position);
CalculatePath(borderPoint1, borderPoint2, 1, endPoint, wire, callback); // 使用第二个图层
// Step 3: 计算从第二个图层的边界点到终点的路径
CalculatePath(borderPoint2, endPoint.transform.position, 1, endPoint, wire, callback, true); // 使用第二个图层
}
}
// 计算路径
void CalculatePath(Vector3 start, Vector3 end, int graphIndex,WireConnectionPoint endPoint, MegaWire wire,UnityAction callback, bool calculatePath=false)
{
if (AstarPath.active != null && AstarPath.active.graphs.Length > graphIndex)
{
GridGraph targetGraph = AstarPath.active.graphs[graphIndex] as GridGraph;
if (targetGraph != null)
{
// 设置 Seeker 使用指定的图层来计算路径
seeker.StartPath(start, end, (p)=> {
if (p.error)
{
Debug.LogError("路径计算错误: " + p.errorLog);
return;
}
// 处理路径,比如让对象沿着路径移动等
Debug.Log("路径计算成功!");
multipathList.AddRange(p.vectorPath);
Debug.Log("currentNodes==>" + p.vectorPath.Count); //A*的path不包含头和尾
tempObjList = new List<GameObject>();
GameObject startObj = new GameObject();
startObj.transform.position = startPoint.transform.position;
tempObjList.Add(startObj);
foreach (Vector3 pos in p.vectorPath)
{
GameObject obj = new GameObject();
obj.transform.position = pos;
tempObjList.Add(obj);
}
GameObject endObj = new GameObject();
endObj.transform.position = endPoint.transform.position;
tempObjList.Add(endObj);
}
wire = MegaWire.Create(wire, tempObjList, wireMaterial, "Wire", null, wireSize, 1.0f);
//设置物理效果
wire.Mass = 0.1f;
wire.stretch = 0.1f;
wire.massRand = 0.1f;
wire.doCollisions = isCrash;
wire.enabled = false;
//wire.hidespans = false;
//wire.doCollisions = true; //贴墙不能设置碰撞,会有问题
//activeWires.Add(wire);
if (startPoint.pointName == endPoint.pointName)
{
for (int i = 0; i < startPoint.isConnect.Count; i++)
{
if (!startPoint.isConnect[i])
{
startPoint.isConnect[i] = true;
}
}
for (int i = 0; i < endPoint.isConnect.Count; i++)
{
if (!endPoint.isConnect[i])
{
endPoint.isConnect[i] = true;
}
}
List<WireConnectionPoint> currentPoints = new List<WireConnectionPoint> { startPoint, endPoint };
wireConnectionDatas.Add(wire, currentPoints);
}
else
{
//多连接错误的线路处理
wireConnectionDatas.Add(wire, null);
}
//if (activeWires.Count>1)
//{
// activeWires[activeWires.Count - 2].enabled = false;
//}
wire.RebuildWire();
callback?.Invoke();
});
}
}
}
// 根据终点的位置和起点的位置,计算图层的边界点
Vector3 GetGraphBoundaryPoint(GridGraph graph, Vector3 point, Vector3 oppositePoint)
{
// 获取终点或起点在图层的对应边界点
// 我们将选择离另一点最近的边界点作为连接点
Vector3 boundaryPoint = point;
float minDist = float.MaxValue;
// 计算图层的边界点
for (int x = 0; x < graph.width; x++)
{
for (int z = 0; z < graph.depth; z++)
{
// 获取每个网格节点的位置
Vector3 nodePosition = (Vector3)graph.GetNode(x, z).position;
// 计算与对方点之间的距离
float dist = Vector3.Distance(oppositePoint, nodePosition);
// 选择最接近对方点的图层边界点
if (dist < minDist)
{
minDist = dist;
boundaryPoint = nodePosition;
}
}
}
Debug.Log("boundaryPoint==>"+ boundaryPoint);
return boundaryPoint;
}
public void DeleteWire(MegaWire wireToDelete)
{
if (wireConnectionDatas.ContainsKey(wireToDelete))
{
indexSpanNum--;
wireToDelete.RemoveWire(); // 删除线缆
if (wireConnectionDatas[wireToDelete] != null)
{
foreach (WireConnectionPoint point in wireConnectionDatas[wireToDelete])
{
for (int i = 0; i < point.isConnect.Count; i++)
{
point.isConnect[i] = false;
}
}
}
wireConnectionDatas.Remove(wireToDelete);
}
}
//是否完成连线
private bool ConfirmConnection()
{
foreach (var point in wirePoints)
{
foreach (var item in point.isConnect)
{
if (!item)
{
return false;
}
}
}
foreach (var item in wireConnectionDatas.Values)
{
if (item == null)
{
return false;
}
}
return true;
}
// 将材质变为半透明
public void SetTransparent(float alpha)
{
if (originalMaterial != null)
{
// 设置渲染模式为透明
originalMaterial.SetFloat("_Mode", 3);
originalMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
originalMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
originalMaterial.SetInt("_ZWrite", 0);
originalMaterial.DisableKeyword("_ALPHATEST_ON");
originalMaterial.EnableKeyword("_ALPHABLEND_ON");
originalMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON");
originalMaterial.renderQueue = 3000;
// 设置透明度
Color color = originalMaterial.color;
color.a = alpha;
originalMaterial.color = color;
}
}
// 恢复材质为不透明
public void SetOpaque()
{
if (originalMaterial != null)
{
// 恢复渲染模式为不透明
originalMaterial.SetFloat("_Mode", 0);
originalMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
originalMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
originalMaterial.SetInt("_ZWrite", 1);
originalMaterial.DisableKeyword("_ALPHATEST_ON");
originalMaterial.DisableKeyword("_ALPHABLEND_ON");
originalMaterial.DisableKeyword("_ALPHAPREMULTIPLY_ON");
originalMaterial.renderQueue = -1;
// 恢复材质的原始颜色
originalMaterial.color = originalColor;
}
}
private void OnDestroy()
{
tempObjList.Clear();
}
}
代码里有很多冗余,如果只想了解获取路径只看CreateWire方法就好了。
有什么不理解的地方欢迎留言。