442 lines
14 KiB
C#
442 lines
14 KiB
C#
|
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<MyTuple<Transform, Transform>> _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<ServerPathNodeGenerator>();
|
|||
|
}
|
|||
|
|
|||
|
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<MyTuple<Transform, Transform>>();
|
|||
|
_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<PathNodeData>();
|
|||
|
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<int>()};
|
|||
|
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<Transform, Transform>(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<Renderer>();
|
|||
|
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<Collider>();
|
|||
|
if (sphereCol)
|
|||
|
DestroyImmediate(sphereCol);
|
|||
|
var sphere = sphereObj.transform;
|
|||
|
sphere.SetParent(_root);
|
|||
|
sphere.position = pos;
|
|||
|
MarkPointPosition(sphere);
|
|||
|
return sphere;
|
|||
|
}
|
|||
|
|
|||
|
private List<Transform> GetSphereList()
|
|||
|
{
|
|||
|
var result = new List<Transform>();
|
|||
|
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<Transform, Transform>(sphereList[i], sphereList[j]));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// 初始化时,临时使用的数据
|
|||
|
/// </summary>
|
|||
|
private class PathNodeData
|
|||
|
{
|
|||
|
public int index;
|
|||
|
public List<int> linkTo;
|
|||
|
public Transform sphere;
|
|||
|
}
|
|||
|
}
|