using System.Collections.Generic; using System.IO; using System.Text; using Games.Scene; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.AI; using UnityEngine.SceneManagement; public class ServerPathNodeGenerator : EditorWindow { private MaterialPropertyBlock _materialProperty; private const float _defaultLinkLength = 6f; private float linkLengthSqr { get { return _linkLength > 0 ? _linkLength * _linkLength : float.PositiveInfinity; } } private const string _scenePathNodeDir = "/../../Public/ServerRun/Scene/"; private const string _line2Text = "#MAX_ID=4999;MAX_RECORD=5000;TableType=Hash;"; private const string _distanceKey = "ServerPathNodeIDist"; private const string _sphereRootName = "ServerPathNode"; private const float _navMeshOffsetMax = 1f; private const float _navMeshSampleDist = 2f; private Scene _currentScene; private List> _links; private Vector2 _pointPos; private Vector2 _scrollViewPoint; private bool _pointError; private FloatSetting _linkLength; private Material _material; private Transform _root; [MenuItem("Scene/生成服务器寻路点")] public static void ShowEditorWindow() { GetWindow(); } private void Awake() { SceneManager.sceneUnloaded += OnSceneUnloaded; SceneView.onSceneGUIDelegate += OnSceneGui; _linkLength = new FloatSetting(_distanceKey, _defaultLinkLength); _material = new Material(Shader.Find("Unlit/Color")); _materialProperty = new MaterialPropertyBlock(); OnSceneChange(); } private void OnDestroy() { if (_material != null) DestroyImmediate(_material); if (_root != null) DestroyImmediate(_root.gameObject); SceneManager.sceneUnloaded -= OnSceneUnloaded; SceneView.onSceneGUIDelegate -= OnSceneGui; } private void OnGUI() { // Add Points _pointPos = EditorGUILayout.Vector2Field("点位置:", _pointPos, GUILayout.Width(200f)); var linkLength = EditorGUILayout.FloatField("最大连接距离", _linkLength); if (linkLength != _linkLength) _linkLength.Set(linkLength); // Add Point GUILayout.Space(20f); GUILayout.Label("添加点位"); if (GUILayout.Button("添加 于指定位置", GUILayout.Width(100f))) { if (_root != null) { var point = _pointPos.InsertY(); CreateSphereAt(_root.childCount, point); } } GUILayout.Space(20f); if (GUILayout.Button("整理路径点")) { if (_root != null) { var sphereList = GetSphereList(); var posList = new Vector3[sphereList.Count]; for (var i = 0; i < posList.Length; i++) { posList[i] = sphereList[i].position; DestroyImmediate(sphereList[i].gameObject); } for (var i = 0; i < posList.Length; i++) { CreateSphereAt(i, posList[i]); } } } GUILayout.Space(20f); if (_pointError) EditorGUILayout.HelpBox("上次生成时,有路径点不在NavMesh上!", MessageType.Error); else GUILayout.Label("生成和保存Txt"); if (GUILayout.Button("预览链接")) { _pointError = CheckAllPointPos(); if (!_pointError) BuildLinks(); } GUILayout.Space(5f); if (GUILayout.Button("保存为Txt")) { _pointError = CheckAllPointPos(); if (!_pointError) { BuildLinks(); WriteFileRecord(); } } } private void OnSceneGui(SceneView sceneView) { Handles.color = Color.magenta; for (var i = 0; i < _links.Count; i++) { var source = _links[i].first; var target = _links[i].second; if (source != null && target != null) Handles.DrawLine(source.position, target.position); } } private void OnSceneUnloaded(Scene scene) { if (scene.buildIndex == _currentScene.buildIndex) OnSceneChange(); } private void OnSceneChange() { _links = new List>(); _currentScene = SceneManager.GetActiveScene(); ReadFileRecord(); } private string GetFileName() { return Application.dataPath + _scenePathNodeDir + _currentScene.name + "RoadPath.txt"; } private void ReadFileRecord() { var rootObj = GameObject.Find(_sphereRootName); if (rootObj != null) DestroyImmediate(rootObj); rootObj = new GameObject(_sphereRootName); _root = rootObj.transform; var fileName = GetFileName(); if (File.Exists(fileName)) { var lines = File.ReadAllLines(fileName); if (lines.Length > 4) { var pathNodeList = new List(); var id = 0; for (var i = 4; i < lines.Length; i++) { var lineSuccess = false; var segments = lines[i].Split('\t'); if (segments.Length > 3) { int index; int posX; int posZ; if (int.TryParse(segments[0], out index) && int.TryParse(segments[2], out posX) && int.TryParse(segments[3], out posZ)) { var point = new Vector3(posX * 0.01f, 0f, posZ * 0.01f); var sphere = CreateSphereAt(id, point); MarkPointPosition(sphere); var pathNode = new PathNodeData() { index = index, sphere = sphere, linkTo = new List()}; pathNodeList.Add(pathNode); for (var j = 4; j < segments.Length; j++) { int linkTo; if (int.TryParse(segments[j], out linkTo) && linkTo > -1) pathNode.linkTo.Add(linkTo); else break; } lineSuccess = true; } id++; } if (!lineSuccess) Debug.LogError(string.Format("第{0}行数据读取失败,已经跳过!", i + 1)); } // 重新构造链接 for (var i = 0; i < pathNodeList.Count; i++) { var source = pathNodeList[i]; for (var j = 0; j < source.linkTo.Count; j++) { var target = pathNodeList.Find(a => a.index == source.linkTo[j]); if (target != null) { if (_links.Find(a => a.first == source.sphere && a.second == target.sphere) == null && _links.Find(a => a.first == target.sphere && a.second == source.sphere) == null) { _links.Add(new MyTuple(source.sphere, target.sphere)); } } } } } } } private void WriteFileRecord() { var sphereList = GetSphereList(); var maxNodeCount = 0; for (var i = 0; i < sphereList.Count; i++) { var sphere = sphereList[i]; var nodeCount = 0; for (var j = 0; j < _links.Count; j++) { var link = _links[j]; if (link.first != null && link.second != null && (link.first == sphere || link.second == sphere)) nodeCount++; } maxNodeCount = Mathf.Max(maxNodeCount, nodeCount); } var builder = new StringBuilder(); // Line 0 builder.Append("Id"); builder.Append('\t'); builder.Append("Desc"); builder.Append('\t'); builder.Append("PosX"); builder.Append('\t'); builder.Append("PosZ"); for (var i = 0; i < maxNodeCount; i++) { builder.Append('\t'); builder.Append("Node"); builder.Append(i + 1); } // Line 1 builder.Append('\r'); builder.Append("INT"); builder.Append('\t'); builder.Append("STRING"); builder.Append('\t'); builder.Append("INT"); builder.Append('\t'); builder.Append("INT"); for (var i = 0; i < maxNodeCount; i++) { builder.Append('\t'); builder.Append("INT"); } // Line 2 builder.Append('\r'); builder.Append(_line2Text); // Line 3 builder.Append('\r'); builder.Append("#点id"); builder.Append('\t'); builder.Append("程序不读"); builder.Append('\t'); builder.Append("点位置x,100倍"); builder.Append('\t'); builder.Append("点位置y,100倍"); for (var i = 0; i < maxNodeCount; i++) { builder.Append('\t'); builder.Append("可链接的节点"); builder.Append(i + 1); } // Data Lines for (var i = 0; i < sphereList.Count; i++) { var source = sphereList[i]; builder.Append('\r'); builder.Append(i); builder.Append('\t'); builder.Append("坐标"); builder.Append(i); builder.Append('\t'); builder.Append(Mathf.RoundToInt(sphereList[i].position.x * 100f)); builder.Append('\t'); builder.Append(Mathf.RoundToInt(sphereList[i].position.z * 100f)); var nodePos = 0; for (var j = 0; j < sphereList.Count; j++) { var target = sphereList[j]; if (_links.Find(a => a.first == source && a.second == target || a.first == target && a.second == source) != null) { builder.Append('\t'); builder.Append(j); nodePos++; } } for (var j = nodePos; j < maxNodeCount; j++) { builder.Append('\t'); builder.Append(-1); } } // Extra Line at End builder.Append('\r'); var fileName = GetFileName(); var directory = Path.GetDirectoryName(fileName); if (string.IsNullOrEmpty(directory)) Debug.LogError("文件路径错误 " + fileName); else { if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); var bytes = Encoding.Default.GetBytes(builder.ToString()); File.WriteAllBytes(fileName, bytes); } } private bool CheckAllPointPos() { var result = false; var sphereList = GetSphereList(); for (var i = 0; i < sphereList.Count; i++) if (!MarkPointPosition(sphereList[i])) result = true; return result; } private bool MarkPointPosition(Transform sphere) { var source = sphere.transform.position; source.x = Mathf.Floor(source.x * 100f) * 0.01f; source.z = Mathf.Floor(source.z * 100f) * 0.01f; source.y = 100f; var ray = new Ray(source, Vector3.down); RaycastHit rayHit; if (Physics.Raycast(ray, out rayHit, 200f, ActiveScene.terrainLayMask)) source = rayHit.point; else source.y = 0f; NavMeshHit navHit; var result = NavMesh.SamplePosition(source, out navHit, _navMeshSampleDist, NavMesh.AllAreas); if (result) { if ((source.RemoveY() - navHit.position.RemoveY()).sqrMagnitude > _navMeshOffsetMax.ToSquare()) result = false; else source = navHit.position; } sphere.position = source; var sphereRenderer = sphere.GetComponent(); if (sphereRenderer != null) { sphereRenderer.GetPropertyBlock(_materialProperty); _materialProperty.SetColor("_Color", result ? Color.green : Color.red); sphereRenderer.SetPropertyBlock(_materialProperty); } return result; } private Transform CreateSphereAt(int index, Vector3 pos) { var sphereObj = GameObject.CreatePrimitive(PrimitiveType.Sphere); sphereObj.name = "Point_" + index; var sphereCol = sphereObj.GetComponent(); if (sphereCol) DestroyImmediate(sphereCol); var sphere = sphereObj.transform; sphere.SetParent(_root); sphere.position = pos; MarkPointPosition(sphere); return sphere; } private List GetSphereList() { var result = new List(); if (_root != null) { foreach (Transform child in _root) result.Add(child); } return result; } private void BuildLinks() { _links.Clear(); var sphereList = GetSphereList(); for (var i = 1; i < sphereList.Count; i++) { for (var j = 0; j < i; j++) { var source = sphereList[i].position; var target = sphereList[j].position; if ((source.RemoveY() - target.RemoveY()).sqrMagnitude < linkLengthSqr) { NavMeshHit hit; if (!NavMesh.Raycast(source, target, out hit, NavMesh.AllAreas)) _links.Add(new MyTuple(sphereList[i], sphereList[j])); } } } } /// /// 初始化时,临时使用的数据 /// private class PathNodeData { public int index; public List linkTo; public Transform sphere; } }