519 lines
21 KiB
C#
519 lines
21 KiB
C#
/********************************************************************************
|
||
* 文件名: 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为配表上存在,实际文件都丢失的场景制作空碰撞区
|
||
/// </summary>
|
||
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<MeshCollider>();
|
||
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>();
|
||
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<ObstacleBlock>();
|
||
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<NavMeshAgent> m_ServerObstacleTestAgent = new List<NavMeshAgent>(); //用以进行联通关系检测的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;
|
||
}
|
||
}
|
||
} |