/******************************************************************************** * 文件名: ServerObstacle.cs * 全路径: \MLDJ\Editor\ServerObstacle.cs * 创建人: 李嘉 * 创建时间:2013-10-25 * * 功能说明:编辑器服务器阻挡生成选项 * 修改记录: *********************************************************************************/ using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; public class ServerObstacle : Editor { // 射线检测的layer private const int _raycastLayer = 2; private const byte _mNPath = 1; //可行走区域标识 private const float _rayLength = 1000f; //private static Vector3 m_TestRayOrigin = new Vector3(0, 1000.0f, 0); //进行射线检测的射线原点 //private static readonly Vector3 m_TestRayDirection = new Vector3(0, -1000.0f, 0); //进行射线检测的射线方向 private const float _rectDistance = 0.2f; private const string _displayRootName = "ServerObstacleRoot"; // Server文件夹路径 private static string rootPath { get { return Application.dataPath.MoveUp().MoveUp().Open("Public"); } //get { return @"F:\Mmo3d\Public"; } } private static Rect? GetNavMeshRect() { Rect? result = null; var triangulation = NavMesh.CalculateTriangulation(); if (triangulation.vertices.Length == 0) { Debug.LogError("无法获得当前地图的NavMesh!"); } else { // 计算实际NavMesh的范围 var point = triangulation.vertices[0]; var minX = point.x; var maxX = point.x; var minZ = point.z; var maxZ = point.z; for (var i = 1; i < triangulation.vertices.Length; i++) { point = triangulation.vertices[i]; minX = Mathf.Min(minX, point.x); maxX = Mathf.Max(maxX, point.x); minZ = Mathf.Min(minZ, point.z); maxZ = Mathf.Max(maxZ, point.z); } result = new Rect(minX, minZ, maxX - minX, maxZ - minZ); } return result; } /// /// 为配表上存在,实际文件都丢失的场景制作空碰撞区 /// public static void CreateEmptyObstacle(string sceneName) { var scenePath = GetSceneFilePath(sceneName); var dir = Path.GetDirectoryName(scenePath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir); using (var fs = File.Create(scenePath)) { using (var writer = new BinaryWriter(fs)) { writer.Write(0f); writer.Write(0f); writer.Write(0); writer.Write(0); } } } //private static Rect? GetSceneRect() //{ // Rect? result = null; // var filePath = rootPath.Open(@"PublicTables\SceneClass.txt"); // if (!File.Exists(filePath)) // Debug.LogError("SceneClass文件不存在!"); // else // { // var triangulation = UnityEngine.AI.NavMesh.CalculateTriangulation(); // if (triangulation.vertices.Length == 0) // Debug.LogError("无法获得当前地图的NavMesh!"); // else // { // var lines = File.ReadAllLines(filePath); // if (lines.Length == 0) // Debug.LogError("SceneClass文件损坏!"); // else // { // var segments = lines[0].Trim().Split('\t'); // var nameId = GetIndexOfTitle(segments, "ResName"); // var xMinId = GetIndexOfTitle(segments, "BasePosX"); // var zMinId = GetIndexOfTitle(segments, "BasePosZ"); // var widthId = GetIndexOfTitle(segments, "Length"); // var heightId = GetIndexOfTitle(segments, "Width"); // if (nameId >= 0 && // xMinId >= 0 && // zMinId >= 0 && // widthId >= 0 && // heightId >= 0) // { // var sceneName = Path.GetFileNameWithoutExtension(SceneManager.GetActiveScene().name); // string[] sceneLine = null; // for (var i = 1; i < lines.Length; i++) // { // segments = lines[i].Trim().Split('\t'); // if (segments.Length > nameId && // segments[nameId] == sceneName) // { // sceneLine = segments; // break; // } // } // if (sceneLine == null) // Debug.LogError(string.Format("SceneClass不包含{0}场景的数据!", sceneName)); // else // { // // 获得场景范围 // try // { // result = new Rect(float.Parse(segments[xMinId]), float.Parse(segments[zMinId]), float.Parse(segments[widthId]), float.Parse(segments[heightId])); // } // catch (Exception e) // { // Debug.LogError(e); // result = null; // } // if (result != null) // { // // 计算实际NavMesh的范围 // var point = triangulation.vertices[0]; // var minX = point.x; // var maxX = point.x; // var minZ = point.z; // var maxZ = point.z; // for (var i = 1; i < triangulation.vertices.Length; i++) // { // point = triangulation.vertices[i]; // minX = Mathf.Min(minX, point.x); // maxX = Mathf.Max(maxX, point.x); // minZ = Mathf.Min(minZ, point.z); // maxZ = Mathf.Max(maxZ, point.z); // } // if (minX < result.Value.xMin || // minZ < result.Value.yMin || // maxX > result.Value.xMax || // maxZ > result.Value.yMax) // { // var navRect = new Rect(minX, minZ, maxX - minX, maxZ - minZ); // Debug.LogError(string.Format("SceneClass定义{0}场景的范围(min {1}, max {2}),未完全包含实际行走区域(min {3}, max {4})", sceneName, result.Value.min, result.Value.max, navRect.min, navRect.max)); // result = null; // } // } // } // } // } // } // } // return result; //} //private static int GetIndexOfTitle(string[] segments, string title) //{ // var result = segments.FindIndex(a => a == title); // if (result < 0) // Debug.LogError(string.Format("SceneClass无法获得{0}标题位置", title)); // return result; //} ////////////////////////////////////////////////////////////////////////// // 生成当前场景服务器阻挡 ////////////////////////////////////////////////////////////////////////// [MenuItem("ProTool/ServerObstacle/Create")] public static void CreateObstacle() { // 试图获得通过NavMesh生成的Collider var navMeshColliderObj = GameObject.Find(NavMeshToCollider.navMeshColliderName); var navMeshColliderFind = navMeshColliderObj != null; if (!navMeshColliderFind) { NavMeshToCollider.ConvertNavMeshToCollider(); navMeshColliderObj = GameObject.Find(NavMeshToCollider.navMeshColliderName); } var success = false; var navMeshCollider = navMeshColliderObj.GetComponent(); if (navMeshCollider == null) { var scene = SceneManager.GetActiveScene(); var sceneName = Path.GetFileNameWithoutExtension(scene.name); CreateEmptyObstacle(sceneName); Debug.LogError("无法在场景中获得NavMeshCollider!"); } else { var originLayer = navMeshCollider.gameObject.layer; navMeshCollider.gameObject.layer = _raycastLayer; var rect = GetNavMeshRect(); if (rect == null) { CreateEmptyObstacle(GetCurrentSceneName()); Debug.LogError("无法在场景中获得NavMeshCollider!"); } else { //获得当前场景的实际长和宽 var width = Mathf.CeilToInt(rect.Value.width / _rectDistance); var height = Mathf.CeilToInt(rect.Value.height / _rectDistance); Debug.Log("场景尺寸:" + rect.Value + "; 像素尺寸:" + width + ", " + height); //初始化文件 var obstacleFilePath = GetSceneFilePath(); var dir = Path.GetDirectoryName(obstacleFilePath); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir); using (var fs = File.Create(obstacleFilePath)) { using (var writer = new BinaryWriter(fs)) { writer.Write(rect.Value.xMin); writer.Write(rect.Value.yMin); writer.Write(width); writer.Write(height); for (var i = 0; i < width; i++) for (var j = 0; j < height; j++) { var nState = GetScenePosPathState(i * _rectDistance + rect.Value.xMin, j * _rectDistance + rect.Value.yMin); writer.Write(nState); } } } // 如果是自己创建的额外碰撞体,则移除这个碰撞体 if (!navMeshColliderFind) DestroyImmediate(navMeshColliderObj); else navMeshCollider.gameObject.layer = originLayer; success = true; } } if (success) Debug.Log(string.Format("服务器碰撞输出成功,路径{0}", GetSceneFilePath())); else Debug.LogWarning(string.Format("场景{0}保存为空白文件!", GetCurrentSceneName())); } ////////////////////////////////////////////////////////////////////////// // 查看当前场景服务器阻挡 ////////////////////////////////////////////////////////////////////////// [MenuItem("ProTool/ServerObstacle/Show")] public static void ShowObstacle() { //初始化文件 var obstaclFilePath = GetSceneFilePath(); var obstacleRoot = CreateObstacleRoot().transform; if (!File.Exists(obstaclFilePath)) { Debug.LogError("无法找到文件 " + obstaclFilePath); return; } var error = false; //创建路径点模型 var pathObj = AssetDatabase.LoadAssetAtPath("Assets/Project3D/Tool/Editor/Path/Path.prefab", typeof(GameObject)) as GameObject; using (var fs = File.OpenRead(obstaclFilePath)) { using (var reader = new BinaryReader(fs)) { try { var xMin = reader.ReadSingle(); var zMin = reader.ReadSingle(); var width = reader.ReadInt32(); var height = reader.ReadInt32(); Debug.Log(string.Format("地图开始({0}, {1}),地图像素({2},{3})", xMin, zMin, width, height)); var lineList = new List(); ObstacleBlock current = null; // 合并线型碰撞区 for (var i = 0; i < width; i++) for (var j = 0; j < height; j++) { var state = reader.ReadByte(); if (state > 0) if (current == null || current.zMax != j - 1 || current.xMax != i) { current = new ObstacleBlock(i, j); lineList.Add(current); } else { current.zMax = j; } else current = null; } // 合并体型碰撞区 var rectList = new List(); current = null; while (lineList.Count > 0) { if (current == null) { current = lineList[0]; lineList.RemoveAt(0); rectList.Add(current); } var merge = -1; for (var i = 0; i < lineList.Count; i++) if (lineList[i].zMin == current.zMin && lineList[i].zMax == current.zMax && lineList[i].xMin == current.xMax + 1) merge = i; if (merge >= 0) { current.xMax = lineList[merge].xMax; lineList.RemoveAt(merge); } else { current = null; } } var maxHeight = 0f; var triangulation = NavMesh.CalculateTriangulation(); // 计算实际NavMesh的范围 for (var i = 0; i < triangulation.vertices.Length; i++) if (maxHeight < triangulation.vertices[i].y) maxHeight = triangulation.vertices[i].y; maxHeight += 5f; for (var i = 0; i < rectList.Count; i++) { var rect = rectList[i]; var pathInst = Instantiate(pathObj); var rectWidth = (rect.xMax - rect.xMin + 1) * _rectDistance; var rectHeight = (rect.zMax - rect.zMin + 1) * _rectDistance; var rectStartX = rect.xMin * _rectDistance + xMin; var rectStartZ = rect.zMin * _rectDistance + zMin; pathInst.transform.position = new Vector3(rectStartX + rectWidth * 0.5f, maxHeight, rectStartZ + rectHeight * 0.5f); pathInst.transform.localScale = new Vector3(rectWidth, 0f, rectHeight); pathInst.transform.SetParent(obstacleRoot); } } catch (Exception e) { Debug.LogError(e); error = true; } } } if (error) DestroyImmediate(obstacleRoot); else Debug.Log(string.Format("服务器碰撞加载成功,路径{0}", obstaclFilePath)); } ////////////////////////////////////////////////////////////////////////// // 隐藏当前场景服务器阻挡 ////////////////////////////////////////////////////////////////////////// [MenuItem("ProTool/ServerObstacle/Hide")] public static void HideObstacle() { CreateObstacleRoot(); Debug.Log("Server Obstacle Hide OK"); } private static string GetCurrentSceneName() { var scene = SceneManager.GetActiveScene(); return Path.GetFileNameWithoutExtension(scene.name); } //得到当前场景的阻挡文件全路径 private static string GetSceneFilePath() { return GetSceneFilePath(GetCurrentSceneName()); } private static string GetSceneFilePath(string sceneName) { return rootPath.Open(@"ServerRun\Scene").Open(sceneName + ".path"); } //创建Obstacle阻挡显示根节点,如果有则清空,没有则创建 private static GameObject CreateObstacleRoot() { var obRoot = GameObject.Find(_displayRootName); if (obRoot != null) DestroyImmediate(obRoot); obRoot = new GameObject(_displayRootName); return obRoot; } //关键函数,根据某个点获取当前点状态 private static byte GetScenePosPathState(float fX, float fZ) { //var info = new ObstacleInfo //{ // m_fX = fX, // m_fZ = fZ //}; byte state = 0; var rayOrigin = new Vector3(fX + 0.5f * _rectDistance, _rayLength * 0.5f, fZ + 0.5f * _rectDistance); //var originalLayer = collider.gameObject.layer; // Unity保留的无名Layer,应该不会有其他物体使用 // Boxcast不支持由collider调用,因此只能用这种保险度较低的方法检测 //const int boxcastLayer = 0; //collider.gameObject.layer = boxcastLayer; //if (Physics.Raycast(rayOrigin, Vector3.down, _rayLength * 0.5f, _raycastLayer.ToFlag())) //{ // state = _mNPath; //} if (Physics.BoxCast(rayOrigin, Vector3.one * _rectDistance * 0.5f, Vector3.down, Quaternion.identity, _rayLength, _raycastLayer.ToFlag())) state = _mNPath; //collider.gameObject.layer = originalLayer; //var ray = new Ray(m_TestRayOrigin, m_TestRayDirection); //RaycastHit hit; //if (collider.Raycast(ray, out hit, float.PositiveInfinity)) // info.m_Value = m_nPath; //for (int i = 0; i < m_ServerObstacleTestAgent.Count; ++i) //{ // m_ServerObstacleTestAgent[i].destination = pos; // if (m_ServerObstacleTestAgent[i].hasPath) // { // info.m_Value = m_nPath; // break; // } //} return state; } //private static float GetScenePosy(float fX, float fZ) //{ // var rayOrigin = new Vector3(fX, _rayLength * 0.5f, fZ); // var ray = new Ray(rayOrigin, Vector3.down); // RaycastHit hit; // return Physics.Raycast(ray, out hit, _rayLength) ? hit.point.y : 0f; // //for (int i = 0; i < m_ServerObstacleTestAgent.Count; ++i) // //{ // // m_ServerObstacleTestAgent[i].destination = pos; // // if (m_ServerObstacleTestAgent[i].hasPath) // // { // // info.m_Value = m_nPath; // // break; // // } // //} //} //struct与byte[]相互转换函数 //private static ObstacleInfo Byte2Struct(byte[] arr) //{ // var structSize = Marshal.SizeOf(typeof(ObstacleInfo)); // var ptemp = Marshal.AllocHGlobal(structSize); // Marshal.Copy(arr, 0, ptemp, structSize); // var rs = (ObstacleInfo) Marshal.PtrToStructure(ptemp, typeof(ObstacleInfo)); // Marshal.FreeHGlobal(ptemp); // return rs; //} //private static byte[] Struct2Byte(ObstacleInfo s) //{ // var structSize = Marshal.SizeOf(typeof(ObstacleInfo)); // var buffer = new byte[structSize]; // //分配结构体大小的内存空间 // var structPtr = Marshal.AllocHGlobal(structSize); // //将结构体拷到分配好的内存空间 // Marshal.StructureToPtr(s, structPtr, false); // //从内存空间拷到byte数组 // Marshal.Copy(structPtr, buffer, 0, structSize); // //释放内存空间 // Marshal.FreeHGlobal(structPtr); // return buffer; //} //private static List m_ServerObstacleTestAgent = new List(); //用以进行联通关系检测的NavAgent集合 ////保存的数据结构 //private struct ObstacleInfo //{ // public float m_fX; // public float m_fZ; // public byte m_nValue; //} private class ObstacleBlock { public int xMax; public readonly int xMin; public int zMax; public readonly int zMin; public ObstacleBlock(int x, int z) { xMin = x; xMax = x; zMin = z; zMax = z; } } }