using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
using IndianOceanAssets.Engine2_5D;
namespace IndianOceanAssets.EditorTools
{
///
/// 随机出生点生成器 —— 在地图边界内随机分布指定数量的 EnemySpawnPoint。
/// 复用 Sprite Physics Shape / PolygonCollider2D 的边界数据。
///
/// 使用方式:
/// 1. 场景中创建一个空物体,挂上 SpawnPointGenerator 组件
/// 2. 指定 groundSprite 或 polygonCollider(同 BoundaryWallGenerator)
/// 3. Inspector 中设置数量和参数,点 "Generate Spawn Points"
///
public class SpawnPointGenerator : MonoBehaviour
{
[Header("数据源")]
[SerializeField] SpriteRenderer groundSprite;
[SerializeField] PolygonCollider2D polygonCollider;
[Header("生成参数")]
[Tooltip("要生成的出生点数量")]
[SerializeField] int spawnCount = 50;
[Tooltip("出生点离边界的最小距离(防止刷在墙边)")]
[SerializeField] float borderMargin = 0.5f;
[Tooltip("出生点之间的最小间距(防止重叠)")]
[SerializeField] float minDistance = 1.0f;
[Tooltip("随机种子(0 = 每次随机)")]
[SerializeField] int seed = 0;
///
/// 从数据源提取边界点(世界坐标),与 BoundaryWallGenerator 一致。
///
public List> GetBoundaryPaths()
{
var paths = new List>();
if (polygonCollider != null)
{
for (int i = 0; i < polygonCollider.pathCount; i++)
{
Vector2[] path = polygonCollider.GetPath(i);
var worldPath = new List();
foreach (var p in path)
{
Vector3 world = polygonCollider.transform.TransformPoint(p.x, p.y, 0f);
worldPath.Add(world);
}
if (worldPath.Count >= 3)
paths.Add(worldPath);
}
}
else if (groundSprite != null && groundSprite.sprite != null)
{
Sprite sprite = groundSprite.sprite;
int shapeCount = sprite.GetPhysicsShapeCount();
for (int i = 0; i < shapeCount; i++)
{
var shape = new List();
sprite.GetPhysicsShape(i, shape);
var worldPath = new List();
foreach (var p in shape)
{
Vector3 world = groundSprite.transform.TransformPoint(p.x, p.y, 0f);
worldPath.Add(world);
}
if (worldPath.Count >= 3)
paths.Add(worldPath);
}
}
return paths;
}
public SpriteRenderer GroundSprite => groundSprite;
public PolygonCollider2D PolygonCollider => polygonCollider;
public int SpawnCount => spawnCount;
public float BorderMargin => borderMargin;
public float MinDistance => minDistance;
public int Seed => seed;
private void OnDrawGizmosSelected()
{
var paths = GetBoundaryPaths();
if (paths.Count == 0) return;
// 绘制边界多边形
Gizmos.color = new Color(0f, 1f, 0f, 0.8f); // 绿色
foreach (var path in paths)
{
for (int i = 0; i < path.Count; i++)
{
Vector3 cur = path[i];
Vector3 next = path[(i + 1) % path.Count];
Gizmos.DrawLine(cur, next);
Gizmos.DrawSphere(cur, 0.1f);
}
}
// 绘制包围盒
if (paths.Count > 0)
{
float minX = float.MaxValue, maxX = float.MinValue;
float minZ = float.MaxValue, maxZ = float.MinValue;
foreach (var path in paths)
{
foreach (var p in path)
{
if (p.x < minX) minX = p.x;
if (p.x > maxX) maxX = p.x;
if (p.z < minZ) minZ = p.z;
if (p.z > maxZ) maxZ = p.z;
}
}
Gizmos.color = new Color(1f, 1f, 0f, 0.3f); // 黄色
Vector3 center = new Vector3((minX + maxX) / 2, 0, (minZ + maxZ) / 2);
Vector3 size = new Vector3(maxX - minX, 0.1f, maxZ - minZ);
Gizmos.DrawWireCube(center, size);
}
}
}
#if UNITY_EDITOR
[CustomEditor(typeof(SpawnPointGenerator))]
public class SpawnPointGeneratorEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
var gen = (SpawnPointGenerator)target;
EditorGUILayout.Space();
// 边界信息
var paths = gen.GetBoundaryPaths();
if (paths.Count > 0)
{
int total = 0;
foreach (var p in paths) total += p.Count;
EditorGUILayout.LabelField($"边界: {paths.Count} 条路径, {total} 个点");
}
else
{
EditorGUILayout.HelpBox("未检测到边界数据,请指定 groundSprite 或 polygonCollider。", MessageType.Warning);
return;
}
if (GUILayout.Button("Generate Spawn Points", GUILayout.Height(36)))
{
GenerateSpawnPoints(gen, paths);
}
if (GUILayout.Button("Clear Spawn Points", GUILayout.Height(24)))
{
ClearSpawnPoints();
}
}
void GenerateSpawnPoints(SpawnPointGenerator gen, List> paths)
{
// 计算包围盒
float minX = float.MaxValue, maxX = float.MinValue;
float minZ = float.MaxValue, maxZ = float.MinValue;
foreach (var path in paths)
{
foreach (var p in path)
{
if (p.x < minX) minX = p.x;
if (p.x > maxX) maxX = p.x;
if (p.z < minZ) minZ = p.z;
if (p.z > maxZ) maxZ = p.z;
}
}
// 使用主路径(最大的路径)做点内测试
var mainPath = paths[0];
for (int i = 1; i < paths.Count; i++)
{
if (paths[i].Count > mainPath.Count)
mainPath = paths[i];
}
// 收集其他路径(可能是空洞/岛屿)
var otherPaths = new List>();
foreach (var path in paths)
{
if (path != mainPath)
otherPaths.Add(path);
}
// 调试:输出多边形信息
Debug.Log($"[SpawnPointGenerator] 共 {paths.Count} 条路径,主路径 {mainPath.Count} 个点");
for (int i = 0; i < paths.Count; i++)
{
Debug.Log($" 路径{i}: {paths[i].Count} 个点");
}
Debug.Log($" 包围盒: X=[{minX:F2}, {maxX:F2}] Z=[{minZ:F2}, {maxZ:F2}]");
int seed = gen.Seed;
if (seed == 0) seed = Random.Range(1, 999999);
var rng = new System.Random(seed);
float margin = gen.BorderMargin;
float minDist = gen.MinDistance;
int targetCount = gen.SpawnCount;
var generated = new List();
int maxAttempts = targetCount * 200; // 防止死循环
int attempts = 0;
while (generated.Count < targetCount && attempts < maxAttempts)
{
attempts++;
// 在包围盒内随机一个点
float rx = (float)(minX + rng.NextDouble() * (maxX - minX));
float rz = (float)(minZ + rng.NextDouble() * (maxZ - minZ));
Vector3 candidate = new Vector3(rx, 0f, rz);
// 检查是否在主多边形内(带边距)
if (!IsInsidePolygon(candidate, mainPath, margin))
continue;
// 检查是否在其他多边形(空洞)内 — 如果在则排除
bool inHole = false;
foreach (var otherPath in otherPaths)
{
if (IsInsidePolygon(candidate, otherPath, 0f))
{
inHole = true;
break;
}
}
if (inHole) continue;
// 检查与已有点的距离
bool tooClose = false;
foreach (var existing in generated)
{
if (Vector3.Distance(candidate, existing) < minDist)
{
tooClose = true;
break;
}
}
if (tooClose) continue;
generated.Add(candidate);
}
// 创建 GameObject
GameObject container = GameObject.Find("Generated_SpawnPoints");
if (container == null)
{
container = new GameObject("Generated_SpawnPoints");
Undo.RegisterCreatedObjectUndo(container, "Create SpawnPoints Container");
}
// 清除旧的
while (container.transform.childCount > 0)
{
Undo.DestroyObjectImmediate(container.transform.GetChild(0).gameObject);
}
for (int i = 0; i < generated.Count; i++)
{
GameObject sp = new GameObject($"SpawnPoint_{i}");
sp.transform.SetParent(container.transform, false);
sp.transform.position = generated[i];
var spComp = sp.AddComponent();
spComp.SetIndex(i);
Undo.RegisterCreatedObjectUndo(sp, $"Create SpawnPoint_{i}");
EditorUtility.SetDirty(spComp);
}
Debug.Log($"[SpawnPointGenerator] 生成 {generated.Count} 个出生点(尝试 {attempts} 次,种子 {seed})");
EditorUtility.SetDirty(container);
}
void ClearSpawnPoints()
{
GameObject container = GameObject.Find("Generated_SpawnPoints");
if (container != null)
{
Undo.DestroyObjectImmediate(container);
Debug.Log("[SpawnPointGenerator] 已清除所有出生点");
}
}
///
/// 判断点是否在闭合多边形内(XZ 平面),可选内缩边距。
/// 使用射线法(Ray Casting)。
///
bool IsInsidePolygon(Vector3 point, List polygon, float margin)
{
int count = polygon.Count;
// 先检查离每条边的距离,如果太近则排除
if (margin > 0f)
{
for (int i = 0; i < count; i++)
{
Vector3 a = polygon[i];
Vector3 b = polygon[(i + 1) % count];
float dist = PointToSegmentDistanceXZ(point, a, b);
if (dist < margin) return false;
}
}
// 射线法判断点是否在多边形内(XZ 平面,沿 X 轴发射射线)
bool inside = false;
for (int i = 0, j = count - 1; i < count; j = i++)
{
float zi = polygon[i].z;
float zj = polygon[j].z;
float xi = polygon[i].x;
float xj = polygon[j].x;
if ((zi > point.z) != (zj > point.z))
{
float intersectX = (xj - xi) * (point.z - zi) / (zj - zi) + xi;
if (point.x < intersectX)
inside = !inside;
}
}
return inside;
}
///
/// XZ 平面上点到线段的距离。
///
float PointToSegmentDistanceXZ(Vector3 p, Vector3 a, Vector3 b)
{
Vector3 ab = new Vector3(b.x - a.x, 0, b.z - a.z);
Vector3 ap = new Vector3(p.x - a.x, 0, p.z - a.z);
float t = Vector3.Dot(ap, ab) / ab.sqrMagnitude;
t = Mathf.Clamp01(t);
Vector3 proj = a + t * ab;
return Vector3.Distance(new Vector3(p.x, 0, p.z), new Vector3(proj.x, 0, proj.z));
}
}
#endif
}