3D寻路系统NavMesh-服务端篇
Posted 流子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3D寻路系统NavMesh-服务端篇相关的知识,希望对你有一定的参考价值。
上一节讲到的客户端使用Unity 自带的 NavMesh 来做寻路 3D寻路系统NavMesh-客户端篇。然而,怪物的刷新、移动,和AI是由服务器负责的,怪物的寻路是由服务器控制的,或者像SLG,大地图寻路在玩家离线的情况下要继续寻路,这必须要服务器来主导寻路。
那么,这怎么去实现呢?
我们服务器必须要有一份导航网格的寻路数据。
用ExportSceneToObj 工具导出场景,这个好像不依赖于烘焙出来的导航网格。
//TODO 贴一下源码:
第一份源码是不依赖于导航网格的生成工具:
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ExportScene : EditorWindow
private const string CUT_LB_OBJ_PATH = "export/bound_lb";
private const string CUT_RT_OBJ_PATH = "export/bound_rt";
private static float autoCutMinX = 1000;
private static float autoCutMaxX = 0;
private static float autoCutMinY = 1000;
private static float autoCutMaxY = 0;
private static float cutMinX = 0;
private static float cutMaxX = 0;
private static float cutMinY = 0;
private static float cutMaxY = 0;
private static long startTime = 0;
private static int totalCount = 0;
private static int count = 0;
private static int counter = 0;
private static int progressUpdateInterval = 10000;
[MenuItem("ExportScene/ExportSceneToObj")]
[MenuItem("GameObject/ExportScene/ExportSceneToObj")]
public static void Export()
ExportSceneToObj(false);
[MenuItem("ExportScene/ExportSceneToObj(AutoCut)")]
[MenuItem("GameObject/ExportScene/ExportSceneToObj(AutoCut)")]
public static void ExportAutoCut()
ExportSceneToObj(true);
[MenuItem("ExportScene/ExportSelectedObj")]
[MenuItem("GameObject/ExportScene/ExportSelectedObj", priority = 44)]
public static void ExportObj()
GameObject selectObj = Selection.activeGameObject;
if (selectObj == null)
Debug.LogWarning("Select a GameObject");
return;
string path = GetSavePath(false, selectObj);
if (string.IsNullOrEmpty(path)) return;
Terrain terrain = selectObj.GetComponent<Terrain>();
MeshFilter[] mfs = selectObj.GetComponentsInChildren<MeshFilter>();
SkinnedMeshRenderer[] smrs = selectObj.GetComponentsInChildren<SkinnedMeshRenderer>();
Debug.Log(mfs.Length + "," + smrs.Length);
ExportSceneToObj(path, terrain, mfs, smrs, false, false);
public static void ExportSceneToObj(bool autoCut)
string path = GetSavePath(autoCut, null);
if (string.IsNullOrEmpty(path)) return;
Terrain terrain = UnityEngine.Object.FindObjectOfType<Terrain>();
MeshFilter[] mfs = UnityEngine.Object.FindObjectsOfType<MeshFilter>();
SkinnedMeshRenderer[] smrs = UnityEngine.Object.FindObjectsOfType<SkinnedMeshRenderer>();
ExportSceneToObj(path, terrain, mfs, smrs, autoCut, true);
public static void ExportSceneToObj(string path, Terrain terrain, MeshFilter[] mfs,
SkinnedMeshRenderer[] smrs, bool autoCut, bool needCheckRect)
int vertexOffset = 0;
string title = "export GameObject to .obj ...";
StreamWriter writer = new StreamWriter(path);
startTime = GetMsTime();
UpdateCutRect(autoCut);
counter = count = 0;
progressUpdateInterval = 5;
totalCount = (mfs.Length + smrs.Length) / progressUpdateInterval;
foreach (var mf in mfs)
UpdateProgress(title);
if (mf.GetComponent<Renderer>() != null &&
(!needCheckRect || (needCheckRect && IsInCutRect(mf.gameObject))))
ExportMeshToObj(mf.gameObject, mf.sharedMesh, ref writer, ref vertexOffset);
foreach (var smr in smrs)
UpdateProgress(title);
if (!needCheckRect || (needCheckRect && IsInCutRect(smr.gameObject)))
ExportMeshToObj(smr.gameObject, smr.sharedMesh, ref writer, ref vertexOffset);
if (terrain)
ExportTerrianToObj(terrain.terrainData, terrain.GetPosition(),
ref writer, ref vertexOffset, autoCut);
writer.Close();
EditorUtility.ClearProgressBar();
long endTime = GetMsTime();
float time = (float)(endTime - startTime) / 1000;
Debug.Log("Export SUCCESS:" + path);
Debug.Log("Export Time:" + time + "s");
OpenDir(path);
private static void OpenDir(string path)
DirectoryInfo dir = Directory.GetParent(path);
int index = path.LastIndexOf("/");
OpenCmd("explorer.exe", dir.FullName);
private static void OpenCmd(string cmd, string args)
System.Diagnostics.Process.Start(cmd, args);
private static string GetSavePath(bool autoCut, GameObject selectObject)
string dataPath = Application.dataPath;
string dir = dataPath.Substring(0, dataPath.LastIndexOf("/"));
string sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
string defaultName = "";
if (selectObject == null)
defaultName = (autoCut ? sceneName + "(autoCut)" : sceneName);
else
defaultName = (autoCut ? selectObject.name + "(autoCut)" : selectObject.name);
return EditorUtility.SaveFilePanel("Export .obj file", dir, defaultName, "obj");
private static long GetMsTime()
return System.DateTime.Now.Ticks / 10000;
//return (System.DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000;
private static void UpdateCutRect(bool autoCut)
cutMinX = cutMaxX = cutMinY = cutMaxY = 0;
if (!autoCut)
Vector3 lbPos = GetObjPos(CUT_LB_OBJ_PATH);
Vector3 rtPos = GetObjPos(CUT_RT_OBJ_PATH);
cutMinX = lbPos.x;
cutMaxX = rtPos.x;
cutMinY = lbPos.z;
cutMaxY = rtPos.z;
private static void UpdateAutoCutRect(Vector3 v)
if (v.x < autoCutMinX) autoCutMinX = v.x;
if (v.x > autoCutMaxX) autoCutMaxX = v.x;
if (v.z < autoCutMinY) autoCutMinY = v.z;
if (v.z > autoCutMaxY) autoCutMaxY = v.z;
private static bool IsInCutRect(GameObject obj)
if (cutMinX == 0 && cutMaxX == 0 && cutMinY == 0 && cutMaxY == 0) return true;
Vector3 pos = obj.transform.position;
if (pos.x >= cutMinX && pos.x <= cutMaxX && pos.z >= cutMinY && pos.z <= cutMaxY)
return true;
else
return false;
private static void ExportMeshToObj(GameObject obj, Mesh mesh, ref StreamWriter writer, ref int vertexOffset)
Quaternion r = obj.transform.localRotation;
StringBuilder sb = new StringBuilder();
foreach (Vector3 vertice in mesh.vertices)
Vector3 v = obj.transform.TransformPoint(vertice);
UpdateAutoCutRect(v);
sb.AppendFormat("v 0 1 2\\n", -v.x, v.y, v.z);
foreach (Vector3 nn in mesh.normals)
Vector3 v = r * nn;
sb.AppendFormat("vn 0 1 2\\n", -v.x, -v.y, v.z);
foreach (Vector3 v in mesh.uv)
sb.AppendFormat("vt 0 1\\n", v.x, v.y);
for (int i = 0; i < mesh.subMeshCount; i++)
int[] triangles = mesh.GetTriangles(i);
for (int j = 0; j < triangles.Length; j += 3)
sb.AppendFormat("f 1 0 2\\n",
triangles[j] + 1 + vertexOffset,
triangles[j + 1] + 1 + vertexOffset,
triangles[j + 2] + 1 + vertexOffset);
vertexOffset += mesh.vertices.Length;
writer.Write(sb.ToString());
private static void ExportTerrianToObj(TerrainData terrain, Vector3 terrainPos,
ref StreamWriter writer, ref int vertexOffset, bool autoCut)
int tw = terrain.heightmapResolution;
int th = terrain.heightmapResolution;
Vector3 meshScale = terrain.size;
meshScale = new Vector3(meshScale.x / (tw - 1), meshScale.y, meshScale.z / (th - 1));
Vector2 uvScale = new Vector2(1.0f / (tw - 1), 1.0f / (th - 1));
Vector2 terrainBoundLB, terrainBoundRT;
if (autoCut)
terrainBoundLB = GetTerrainBoundPos(new Vector3(autoCutMinX, 0, autoCutMinY), terrain, terrainPos);
terrainBoundRT = GetTerrainBoundPos(new Vector3(autoCutMaxX, 0, autoCutMaxY), terrain, terrainPos);
else
terrainBoundLB = GetTerrainBoundPos(CUT_LB_OBJ_PATH, terrain, terrainPos);
terrainBoundRT = GetTerrainBoundPos(CUT_RT_OBJ_PATH, terrain, terrainPos);
int bw = (int)(terrainBoundRT.x - terrainBoundLB.x);
int bh = (int)(terrainBoundRT.y - terrainBoundLB.y);
int w = bh != 0 && bh < th ? bh : th;
int h = bw != 0 && bw < tw ? bw : tw;
int startX = (int)terrainBoundLB.y;
int startY = (int)terrainBoundLB.x;
if (startX < 0) startX = 0;
if (startY < 0) startY = 0;
Debug.Log(string.Format("Terrian:tw=0,th=1,sw=2,sh=3,startX=4,startY=5",
tw, th, bw, bh, startX, startY));
float[,] tData = terrain.GetHeights(0, 0, tw, th);
Vector3[] tVertices = new Vector3[w * h];
Vector2[] tUV = new Vector2[w * h];
int[] tPolys = new int[(w - 1) * (h - 1) * 6];
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++)
Vector3 pos = new Vector3(-(startY + y), tData[startX + x, startY 以上是关于3D寻路系统NavMesh-服务端篇的主要内容,如果未能解决你的问题,请参考以下文章