轉(zhuǎn)載請注明出處:
http://www.itdecent.cn/p/63b66c4a2cf9
說明
本文主要針對MMORPG這種、unity前端做好地形、如何給后端讀取并實(shí)現(xiàn)尋路等算法的使用。
因后端有可能采用各種語言,如C++也好、C#也好。。所以本文尋路這里、算法我直接寫在前端了,作為一個Unity導(dǎo)出工具使用。
因?yàn)橹饕壿媽懺诤蠖?,前端代碼沒有做優(yōu)化整理,只是急忙實(shí)現(xiàn)了下算法,所以會比較難看,只為說明思路用。(不過別擔(dān)心,有看到本文的放心copy+C使用即可,也歡迎留言提問)
具體思路
1目的:

2具體思路:
- 烘培出Navigation網(wǎng)格
- 讀取網(wǎng)格信息生成文件。
- --------傳給服務(wù)器--------
- 讀取文件生成地形
- 剔除不可行走部分。
- 計(jì)算網(wǎng)格間距(三角形之間)作為初步的A*尋路的G值(兩個相鄰格子移動消耗代價)使用。
- 尋路算法
2-1 我可以省略上面的1步驟嗎??
Yes,you can~~。If any萬 有 what 疑問, please 留言。
2-2 讀取網(wǎng)格信息生成文件
UnityEngine.AI.NavMesh.CalculateTriangulation();
這個函數(shù)會讀取地形,返回如下結(jié)構(gòu)體NavMeshTriangulation。
我們只關(guān)心結(jié)構(gòu)體中的vertices和areas變量。
vertices: 儲存了bake后的地形網(wǎng)格的所有點(diǎn)
areas: 儲存了由點(diǎn)生成三角網(wǎng)格的字典。
什么意思呢???????如下圖

這樣就清晰明了了。上圖共2個三角形,分別是ABC和ACD。
2-3--------傳給服務(wù)器--------
這個也略吧。。。就傳唄。。。Linux的話用FTP,電腦下載個FileZilla一拖。。。就到服務(wù)器了。。。(具體需要配置下)。不知道咋配置的也百度不到的。。。給我留言吧先。。。
2-4讀取文件生成地形
既然已知全地形所有三角形的點(diǎn)了,就可在字典中,如上圖三個一組讀取一次,這樣就會獲取到所有的三角形。
然后遍歷所有三角形,發(fā)現(xiàn)只要有2個點(diǎn)重合的,那么這兩個三角形就相鄰。(判斷重合最好不要用等號,最好是兩點(diǎn)間距離小于某個值。。。)
2-5剔除不可行走部分
那么畫說回來,這么多三角形,如何剔除不可行走部分呢???


如上圖,藍(lán)色劃掉的部分那個溝壑(嘿嘿嘿。。)明顯是地形之外的。那么如何去除呢???我們可以返回再看2-4讀取文件生成地形這一步,判斷三角形重合點(diǎn)。。那么問題來啦,第一個三角形和誰去重合?最起碼有個基礎(chǔ)三角形吧?。?!所以我們只需要指定一個基礎(chǔ)三角形,這個指定的三角形在你設(shè)計(jì)的可行走范圍內(nèi)隨便選一個即可,然后計(jì)算三角形重合時候會不斷的鋪開。生成的就是可行走區(qū)域。如下圖

我指定的是紅線黃線包圍的三角形。
2-6計(jì)算尋路代價
這里其實(shí)只需要計(jì)算相鄰三角形的中心距離即可。距離即為代價(除非你有分什么沙漠地形、泥巴地形之類的行走速度會受影響,需要重新定制)。
2-7尋路算法
1首先判斷角色屬于某個三角形
2判斷目標(biāo)屬于某個三角形
3根據(jù)A*計(jì)算尋路算法生成三角形上的行走路徑
4優(yōu)化行走路徑
A算法計(jì)算路徑就不再多說了,網(wǎng)上有很多講解的。這里主要講解A算法計(jì)算尋路后,路徑只是一排三角形中點(diǎn)的行進(jìn)路線,那么如何找出最優(yōu)路線呢???

首先我們想象人眼睛只要能目測到的地方(沒有被墻體阻擋視線)即可直線直接到達(dá))。
那么假設(shè)人眼能發(fā)射探照燈。第一次探照的結(jié)果如下圖:

黃色地方全都可以通過
再次探照如下圖

最后一次探照如下圖:

發(fā)現(xiàn)敵軍就在視野范圍內(nèi)?。?!在視野范圍內(nèi)說明啥??請往上翻,看到的第一個黑加粗字體。。。就是可直線直接到達(dá)的?。。?!于是??!我們直接過去?。】乘?!刀他!?。∫坏?99!??!極品屠龍,綠毒屠龍!紫毒屠龍!!哇!?。敲线_(dá)來收裝備啦?。?!就這樣。。。
那么我們一眼看不到呢???

很常見思路。。哪里擋住你視線了,走過去!!再看!!不就完了???如下圖。。。這個就是多邊形尋路。
(首先我們要明確再優(yōu)化路徑之前我們已經(jīng)找到大致的路線了如上圖黃點(diǎn))

移動到遮擋部分,重新進(jìn)行觀測即可。
所以具體思路:
- 從起點(diǎn)進(jìn)行觀測,每次根據(jù)A*的路徑移動一次,每次根據(jù)路徑取出“視野最小的”觀測范圍(如上圖第二次觀測)
- 如果觀測遇到障礙物徹底阻擋了視線,則阻擋的位置即為第一個目標(biāo)目的地,我們先走過去,然后重新進(jìn)行上一步的操作,不斷進(jìn)行觀測,知道發(fā)現(xiàn)敵人。
其他注意點(diǎn):
可能遇到如下路徑

當(dāng)剛好之后某些路徑計(jì)算的時候,遇到拐點(diǎn),并且拐點(diǎn)在下一個路徑的一條邊上。因?yàn)槲覀円暰€觀測判斷阻擋,從眼睛射出的射線是要分左右的,但是此時你站在拐點(diǎn)處,是無法區(qū)分左右的(因?yàn)楹鸵粋€點(diǎn)重合了,此時判斷射線左右需要首先找到當(dāng)前你所屬的三角形,通過三角形的中點(diǎn)計(jì)算左右。
代碼:
代碼我混合導(dǎo)出地形和尋路計(jì)算到了一起,尋路等是為了先在前端演示一下效果,然后再移動到服務(wù)器的。
懶得理解了可以直接復(fù)制
但是服務(wù)器尋路還是要參照下的。
(我的是linuxC++,因?yàn)闆]有界面不直觀,所以先在前端寫了一下大致算法,就是下面的,然后再到服務(wù)器寫)
using UnityEditor;
using System.IO;
using UnityEngine;
using System.Text;
using UnityEngine.AI;
using System.Collections.Generic;
using ServerTools;
public class ExportNavMeshPathGround
{
private static string outFilePath = "path_ground.dat";
private static List<Triangle> listTriangle = new List<Triangle>();
private static int baseTag = 0;
private static float dis = 0.05f;
private static int startI = 0;
private static int endI = 113;
[MenuItem("Zhbd_Tools/Export_NavMeshData_Path_Ground")]
static void Export_NavMeshData_Path_Ground()
{
StringBuilder str = new StringBuilder();
FileStream outFile = new FileStream(Application.dataPath + "/" + outFilePath, FileMode.Create, FileAccess.ReadWrite);
listTriangle.Clear();
ServerPath.getInstance().listDraw.Clear();
NavMeshTriangulation nmt = UnityEngine.AI.NavMesh.CalculateTriangulation();
for (int i = 0; i < nmt.vertices.Length; ++i)
{
str.Append("x: " + nmt.vertices[i].x + ",y: " + nmt.vertices[i].y + ",z: " + nmt.vertices[i].z + "\n");
}
str.Append("\nindices:");
for (int i = 0; i < nmt.indices.Length; ++i)
{
str.Append(nmt.indices[i] + ",");
}
str.Append("\nareas:");
for (int i = 0; i < nmt.areas.Length; ++i)
{
str.Append(nmt.areas[i] + ",");
}
Debug.Log(str.ToString());
byte[] bytes = Encoding.UTF8.GetBytes(str.ToString());
outFile.Write(bytes, 0, bytes.Length);
outFile.Close();
for (int i = 0; i < nmt.indices.Length; i += 3)
{
Triangle t = new Triangle();
t.A = nmt.vertices[nmt.indices[i]];
t.B = nmt.vertices[nmt.indices[i + 1]];
t.C = nmt.vertices[nmt.indices[i + 2]];
listTriangle.Add(t);
}
ServerPath.getInstance().listDraw.Add(listTriangle[baseTag]);
listTriangle.RemoveAt(baseTag);
for (int i = 0; i < ServerPath.getInstance().listDraw.Count; ++i)
{
for (int j = 0; j < listTriangle.Count; ++j)
{
if ((ServerPath.getInstance().listDraw[i].A - listTriangle[j].A).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].A - listTriangle[j].B).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].A - listTriangle[j].C).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].B - listTriangle[j].A).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].B - listTriangle[j].B).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].B - listTriangle[j].C).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].C - listTriangle[j].A).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].C - listTriangle[j].B).magnitude < dis ||
(ServerPath.getInstance().listDraw[i].C - listTriangle[j].C).magnitude < dis)
{
ServerPath.getInstance().listDraw.Add(listTriangle[j]);
listTriangle.RemoveAt(j);
--j;
}
}
}
for (int i = 0; i < ServerPath.getInstance().listDraw.Count; ++i)
{
Debug.DrawLine(ServerPath.getInstance().listDraw[i].A, ServerPath.getInstance().listDraw[i].B, Color.green, 5);
Debug.DrawLine(ServerPath.getInstance().listDraw[i].A, ServerPath.getInstance().listDraw[i].C, Color.green, 5);
Debug.DrawLine(ServerPath.getInstance().listDraw[i].C, ServerPath.getInstance().listDraw[i].B, Color.green, 5);
}
Debug.DrawLine(ServerPath.getInstance().listDraw[0].A, ServerPath.getInstance().listDraw[0].B, Color.red, 5);
Debug.DrawLine(ServerPath.getInstance().listDraw[0].A, ServerPath.getInstance().listDraw[0].C, Color.red, 5);
Debug.DrawLine(ServerPath.getInstance().listDraw[0].C, ServerPath.getInstance().listDraw[0].B, Color.yellow, 5);
Debug.Log("AB和AC的夾角: " + Vector3.Angle((ServerPath.getInstance().listDraw[0].B - ServerPath.getInstance().listDraw[0].A),
(ServerPath.getInstance().listDraw[0].C - ServerPath.getInstance().listDraw[0].A)));
Debug.Log("AC和AB的夾角: " + Vector3.Angle((ServerPath.getInstance().listDraw[0].C - ServerPath.getInstance().listDraw[0].A),
(ServerPath.getInstance().listDraw[0].B - ServerPath.getInstance().listDraw[0].A)));
Debug.Log("AB和CA的夾角: " + Vector3.Angle((ServerPath.getInstance().listDraw[0].B - ServerPath.getInstance().listDraw[0].A),
(ServerPath.getInstance().listDraw[0].A - ServerPath.getInstance().listDraw[0].C)));
countG(ServerPath.getInstance().listDraw[startI]);
countG(ServerPath.getInstance().listDraw[endI]);
Debug.Log("listDraw.Count: " + ServerPath.getInstance().listDraw.Count);
ServerPath.getInstance().initNeighbour();
ServerPath.getInstance().countPath(0, endI);
}
public static void countG(Triangle t)
{
float my_a = (t.B - t.C).magnitude;
float my_b = (t.B - t.C).magnitude;
float my_c = (t.B - t.C).magnitude;
Vector3 myCenter = new Vector3((my_a * t.A.x + my_b * t.B.x + my_c * t.C.x) / (my_a + my_b + my_c),
(my_a * t.A.y + my_b * t.B.y + my_c * t.C.y) / (my_a + my_b + my_c),
(my_a * t.A.z + my_b * t.B.z + my_c * t.C.z) / (my_a + my_b + my_c));
Debug.DrawRay(myCenter, Vector3.up * 5, Color.red, 5);
}
}
using System.Collections.Generic;
using UnityEngine;
namespace ServerTools
{
public class Triangle
{
public Vector3 A;
public Vector3 B;
public Vector3 C;
public Triangle neighborA = null;
public Triangle neighborB = null;
public Triangle neighborC = null;
public int id;
public Triangle father = null;
public Triangle next = null;
public float F = 0f;
public float H = 0f;
public float G = 0f;
public bool finished = false;
public void reset()
{
F = 0f;
H = 0f;
G = 0f;
neighborA = null;
neighborB = null;
neighborC = null;
father = null;
next = null;
finished = false;
}
public Vector3 getCenterPosition()
{
float a = (B - C).magnitude;
float b = (B - C).magnitude;
float c = (B - C).magnitude;
Vector3 Center = new Vector3((a * A.x + b * B.x + c * C.x) / (a + b + c),
(a * A.y + b * B.y + c * C.y) / (a + b + c),
(a * A.z + b * B.z + c * C.z) / (a + b + c));
return Center;
}
}
public class ServerPath
{
private float countDis(Triangle TA, Triangle TB)
{
Vector3 ACenter = TA.getCenterPosition();
Vector3 BCenter = TB.getCenterPosition();
return (ACenter - BCenter).magnitude;
}
private ServerPath()
{
}
private static ServerPath instance = null;
public static ServerPath getInstance()
{
if(instance == null)
{
instance = new ServerPath();
}
return instance;
}
public List<Triangle> listDraw = new List<Triangle>();
private List<Triangle> listcheck = new List<Triangle>();
private List<Triangle> listfinish = new List<Triangle>();
public void initNeighbour()
{
for (int i = 0; i < listDraw.Count; ++i)
{
listDraw[i].reset();
}
for (int i = 0; i < listDraw.Count; ++i)
{
for (int j = 0; j < listDraw.Count; ++j)
{
if(i == j)
{
continue;
}
int bingleCount = 0;
bool hitA = false;
bool hitB = false;
bool hitC = false;
if ((listDraw[i].A - listDraw[j].A).magnitude < 0.05 ||
(listDraw[i].A - listDraw[j].B).magnitude < 0.05 ||
(listDraw[i].A - listDraw[j].C).magnitude < 0.05)
{
++bingleCount;
hitA = true;
}
if ((listDraw[i].B - listDraw[j].A).magnitude < 0.05 ||
(listDraw[i].B - listDraw[j].B).magnitude < 0.05 ||
(listDraw[i].B - listDraw[j].C).magnitude < 0.05)
{
++bingleCount;
hitB = true;
}
if ((listDraw[i].C - listDraw[j].A).magnitude < 0.05 ||
(listDraw[i].C - listDraw[j].B).magnitude < 0.05 ||
(listDraw[i].C - listDraw[j].C).magnitude < 0.05)
{
++bingleCount;
hitC = true;
}
if(bingleCount > 1)
{
tryToAddNeighbour(listDraw[i], listDraw[j], hitA, hitB, hitC);
}
}
}
}
public void countPath(int start, int end)
{
if(start >= listDraw.Count || end >= listDraw.Count)
{
return;
}
listcheck.Clear();
listfinish.Clear();
for(int i = 0; i < listDraw.Count; ++i)
{
listDraw[i].id = i;
}
Triangle nowTriangle;
tryToAddToListcheck(listDraw[start]);
int ci = 0;
while((nowTriangle = getPbByMinF()) != null)
{
++ci;
//Debug.Log(ci);
if(nowTriangle.neighborA == listDraw[end] ||
nowTriangle.neighborB == listDraw[end] ||
nowTriangle.neighborC == listDraw[end])
{
Triangle pbend = listDraw[end];
listfinish.Add(pbend);
pbend.father = nowTriangle;
while(pbend.father != null)
{
Debug.DrawLine(pbend.getCenterPosition(), pbend.father.getCenterPosition());
pbend.father.next = pbend;
pbend = pbend.father;
}
optimizationPath(pbend);
break;
}
if(countNeighbourF(nowTriangle, nowTriangle.neighborA))
{
nowTriangle.neighborA.H = (nowTriangle.neighborA.getCenterPosition() - listDraw[end].getCenterPosition()).magnitude;
nowTriangle.neighborA.F = nowTriangle.neighborA.H + nowTriangle.neighborA.G;
}
if(countNeighbourF(nowTriangle, nowTriangle.neighborB))
{
nowTriangle.neighborB.H = (nowTriangle.neighborB.getCenterPosition() - listDraw[end].getCenterPosition()).magnitude;
nowTriangle.neighborB.F = nowTriangle.neighborB.H + nowTriangle.neighborB.G;
}
if(countNeighbourF(nowTriangle, nowTriangle.neighborC))
{
nowTriangle.neighborC.H = (nowTriangle.neighborC.getCenterPosition() - listDraw[end].getCenterPosition()).magnitude;
nowTriangle.neighborC.F = nowTriangle.neighborC.H + nowTriangle.neighborC.G;
}
addToFinish(nowTriangle);
}
}
private Triangle getPbByMinF()
{
Triangle pb = null;
for (int i = 0; i < listcheck.Count; ++i)
{
if(pb == null || pb.F > listcheck[i].F)
{
pb = listcheck[i];
}
}
return pb;
}
private void tryToAddToListcheck(Triangle t)
{
for (int i = 0; i < listfinish.Count; ++i)
{
if (t.id == listfinish[i].id)
{
return;
}
}
for (int i = 0; i < listcheck.Count; ++i)
{
if(t.id == listcheck[i].id)
{
return;
}
}
listcheck.Add(t);
}
private void addToFinish(Triangle t)
{
t.finished = true;
listfinish.Add(t);
listcheck.Remove(t);
}
private bool countNeighbourF(Triangle A, Triangle Neighbour)
{
if(Neighbour == null || Neighbour.finished)
{
return false;
}
float G = countDis(A, Neighbour);
if (Neighbour.G == 0f || (A.G + G) < Neighbour.G)
{
Neighbour.G = A.G + G;
Neighbour.father = A;
tryToAddToListcheck(Neighbour);
return true;
}
return false;
}
private void tryToAddNeighbour(Triangle baseTriangle, Triangle neighbour, bool hitA, bool hitB, bool hitC)
{
if(hitA && hitB)
{
baseTriangle.neighborC = neighbour;
}
else if (hitA && hitC)
{
baseTriangle.neighborB = neighbour;
}
else if (hitC && hitB)
{
baseTriangle.neighborA = neighbour;
}
}
private void optimizationPath(Triangle start)
{
Vector3 v1 = new Vector3();
Vector3 v2 = new Vector3();
Vector3 v3 = new Vector3();
Vector3 v4 = new Vector3();
Vector3 p1 = new Vector3();
Vector3 p2 = new Vector3();
Vector3 p3 = new Vector3();
Vector3 p4 = new Vector3();
Triangle g1 = start;
Triangle g2 = start;
List<Vector3> listpath = new List<Vector3>();
Vector3 startPosition = start.getCenterPosition();
listpath.Add(startPosition);
Triangle tCount = start.next;
caculateV(startPosition, start, ref v1, ref v2, ref p1, ref p2);
int testi = 0;
while (tCount != null)
{
++testi;
caculateV(startPosition, tCount, ref v3, ref v4, ref p3, ref p4);
if (testi == 999)
{
Debug.DrawRay(startPosition + Vector3.up * 0.5f, v1, Color.cyan, 5);
Debug.DrawRay(startPosition + Vector3.up * 0.5f, v2, Color.cyan, 5);
Debug.DrawRay(startPosition, v3, Color.black, 5);
Debug.DrawRay(startPosition, v4, Color.black, 5);
tCount = null;
continue;
}
//左手坐標(biāo)系,所以小于等于
if (Vector3.Cross(new Vector3(v3.x, 0, v3.z), new Vector3(v2.x, 0, v2.z)).y <= 0)
{
Debug.DrawRay(p2, Vector3.up * 10, Color.yellow, 5);
startPosition = p2;
tCount = g2;
g2 = tCount.next;
caculateV(startPosition, tCount, ref v1, ref v2, ref p1, ref p2);
listpath.Add(startPosition);
this.countNext(ref listpath, ref tCount);
continue;
}
//左手坐標(biāo)系,所以大于等于
if (Vector3.Cross(new Vector3(v4.x, 0, v4.z), new Vector3(v1.x, 0, v1.z)).y >= 0)
{
Debug.DrawRay(p1, Vector3.up * 10, Color.yellow, 5);
startPosition = p1;
tCount = g1;
g1 = tCount.next;
caculateV(startPosition, tCount, ref v1, ref v2, ref p1, ref p2);
listpath.Add(startPosition);
this.countNext(ref listpath, ref tCount);
continue;
}
//左手坐標(biāo)系,所以小于等于
if (Vector3.Cross(new Vector3(v3.x, 0, v3.z), new Vector3(v1.x, 0, v1.z)).y <= 0)
{
v1 = v3;
p1 = p3;
g1 = tCount.next;
}
//左手坐標(biāo)系,所以小于等于
if (Vector3.Cross(new Vector3(v4.x, 0, v4.z), new Vector3(v2.x, 0, v2.z)).y >= 0)
{
v2 = v4;
p2 = p4;
g2 = tCount.next;
}
this.countNext(ref listpath, ref tCount);
}
for(int i = 0; i < listpath.Count - 1; ++i)
{
Debug.DrawLine(listpath[i], listpath[i + 1], Color.blue, 5);
}
}
private void countNext(ref List<Vector3> listpath, ref Triangle t)
{
if (t.next == null)
{
listpath.Add(t.getCenterPosition());
}
t = t.next;
}
private void caculateV(Vector3 startPosition, Triangle tCount, ref Vector3 v1, ref Vector3 v2, ref Vector3 p1, ref Vector3 p2)
{
if(tCount == null)
{
return;
}
if(tCount.next != null)
{
if (tCount.next == tCount.neighborA)
{
v1 = tCount.B - startPosition;
v2 = tCount.C - startPosition;
p1 = tCount.B;
p2 = tCount.C;
}
else if (tCount.next == tCount.neighborB)
{
v1 = tCount.A - startPosition;
v2 = tCount.C - startPosition;
p1 = tCount.A;
p2 = tCount.C;
}
else if (tCount.next == tCount.neighborC)
{
v1 = tCount.B - startPosition;
v2 = tCount.A - startPosition;
p1 = tCount.B;
p2 = tCount.A;
}
//start Position和另一個V,在同一個邊上則邊就確定了,從這個三角形中心找左右
if (Vector3.Cross(new Vector3(v1.x, 0, v1.z), new Vector3(v2.x, 0, v2.z)).y == 0)
{
Vector3 vcp_1 = p1 - tCount.getCenterPosition();
Vector3 vcp_2 = p2 - tCount.getCenterPosition();
if (Vector3.Cross(new Vector3(vcp_1.x, 0, vcp_1.z), new Vector3(vcp_2.x, 0, vcp_2.z)).y < 0)
{
v1 = p2 - startPosition;
v2 = p1 - startPosition;
Vector3 pswitch = p1;
p1 = p2;
p2 = pswitch;
}
else
{
v1 = p1 - startPosition;
v2 = p2 - startPosition;
}
}
//左手坐標(biāo)系,所以小于等于
else if (Vector3.Cross(new Vector3(v1.x, 0, v1.z), new Vector3(v2.x, 0, v2.z)).y < 0)
{
Vector3 vswitch = v1;
Vector3 pswitch = p1;
v1 = v2;
v2 = vswitch;
p1 = p2;
p2 = pswitch;
}
}
else
{
v1 = v2 = tCount.getCenterPosition() - startPosition;
p1 = p2 = tCount.getCenterPosition();
}
}
}
}
大致的注解(因?yàn)樽约河茫僖粋€很多代碼只是服務(wù)器代碼的一個預(yù)演,湊合寫下,所以注釋基本沒有,有幾個變量的設(shè)置直接隨意的放到里面,下圖我大約注解一下。。。

代碼Copy之后,如下圖
